Two layers shipped together since the second builds on the first. LAYER 1 — reviewing/ + Plan Review scaffolding - reviewing/ is now a real folder under each project, populated by the Plan Review composite endpoint. The old reviewing/ virtual aggregator handler is retired. - POST /<project>/archive/<party>/received/<tracking>/ with X-ZDDC-Op: plan-review scaffolds physical workflow folders under reviewing_root and staging_root, each carrying .zddc.received_path pointing back at the canonical submittal. Idempotent re-runs match by received_path and re-converge the ACL. - Virtual received window: when listing or writing under <workflow>/received/, the server resolves through the canonical archive/<party>/received/<tracking>/ via the workflow's .zddc.received_path. Writes get rewritten to <workflow>/<base>+C<n><suffix> so review comments land in the workflow folder and never touch the WORM archive. - Cascade defaults declare on_plan_review per project so the reviewing_root and staging_root are configurable. LAYER 2 — browse context-menu workflows - Accept Transmittal: right-click a transmittal folder in archive/<party>/incoming/ → validates ZDDC folder + filename conformance, atomic-renames the folder to archive/<party>/received/<tracking>/ (WORM zone), and optionally chains into Plan Review in the same composite request. Re-acceptance with a different revision merges file-by-file; WORM forbids overwrite of an existing filename. - Stage / Unstage: right-click files in working/<…>/ → "Stage to…" with picker of existing staging transmittal folders + inline "New transmittal folder…" create; right-click files in staging/<…>/ → "Unstage to working/" defaulting to the user's working/<email>/ home. Reuses the file-API move primitive. - Create Transmittal folder: right-click the staging/ pane → prompts for a ZDDC-conforming folder name with live validation; mkdir, then navigate to the new folder URL where the transmittal tool serves the editor. - Supporting infrastructure: new CanonicalFolderAt cascade lookup + X-ZDDC-Canonical-Folder response header so the browse SPA can scope-gate menu items without re-implementing the cascade client-side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
256 lines
12 KiB
YAML
256 lines
12 KiB
YAML
# defaults.zddc — embedded baseline configuration for every ZDDC
|
|
# deployment. Baked into the binary via //go:embed in defaults.go,
|
|
# loaded as the bottom-most level of the cascade. Operators override
|
|
# at the on-disk root /.zddc (or any deeper level); to ignore this
|
|
# file entirely, set `inherit: false` on an on-disk .zddc.
|
|
#
|
|
# To export an editable copy for an operator:
|
|
#
|
|
# zddc-server show-defaults > /var/lib/zddc/root/.zddc
|
|
#
|
|
# That places this file at the on-disk root, where the operator can
|
|
# edit it freely. The new file then takes the place of the embedded
|
|
# one (both contribute to the cascade, on-disk wins per-field).
|
|
|
|
title: "ZDDC"
|
|
|
|
# Empty acl at this layer — rules come from on-disk .zddc files above.
|
|
# A deployment with no on-disk root .zddc grants no access (consistent
|
|
# with prior behaviour); operators bootstrap by editing the root file.
|
|
acl:
|
|
permissions: {}
|
|
|
|
# ── Standard roles ─────────────────────────────────────────────────────────
|
|
#
|
|
# Two roles ship empty (no members) — a fresh deployment grants
|
|
# nothing until an operator populates them. They're referenced by the
|
|
# project-scoped grants in paths: below.
|
|
#
|
|
# Role membership UNIONS across the cascade: an on-disk .zddc that
|
|
# defines `project_team` again with one extra member ADDS that member
|
|
# to the inherited role. To start fresh at a subtree (e.g. a project
|
|
# wanting its own team independent of a deployment-wide default), use
|
|
# `reset: true` on the role at that level — ancestor definitions above
|
|
# the reset are then excluded.
|
|
#
|
|
# document_controller — the people who file into archive/<party>/
|
|
# received/ and issued/ (WORM zones). They get read+write-once-
|
|
# create there (via the worm: lists below) and read/write
|
|
# elsewhere in a project, plus subtree-admin of working/ and
|
|
# staging/ so they can stand up new top-level folders and manage
|
|
# user/staging subtrees. They are NOT subtree-admin of archive/,
|
|
# so the WORM constraint still binds them in received/issued.
|
|
#
|
|
# project_team — everyone working on a project. Read-only across
|
|
# the project. Their own working/<email>/ home and anything they
|
|
# create under incoming/ get a creator-owned auto-own .zddc
|
|
# (rwcda) which wins via deepest-match, so "read-only except
|
|
# what I own" falls out of the cascade with no special rule.
|
|
roles:
|
|
document_controller:
|
|
members: []
|
|
project_team:
|
|
members: []
|
|
|
|
# Universal tool baseline. archive (record browser), browse (file
|
|
# tree, hosts the in-place markdown editor), and landing (project
|
|
# picker) work everywhere. Each canonical folder below adds its own
|
|
# context-specific tools (transmittal in staging/, etc.). The cascade
|
|
# unions available_tools across all levels — leaf restrictions don't
|
|
# drop ancestor entries — so this baseline propagates to every
|
|
# descendant.
|
|
available_tools: [archive, browse, landing]
|
|
|
|
# ── The slash / no-slash routing convention ────────────────────────────────
|
|
#
|
|
# Every directory URL has two forms, each served by a configurable
|
|
# tool:
|
|
#
|
|
# <dir>/ (trailing slash) → `dir_tool` — the directory view.
|
|
# Defaults to `browse` (file-tree
|
|
# navigator). This is the site-wide
|
|
# default; you rarely set it.
|
|
# <dir> (no slash) → `default_tool` — the "specialized
|
|
# app" for this folder (e.g. archive,
|
|
# transmittal, tables). If a folder
|
|
# declares no default_tool, the no-
|
|
# slash form just 302s to the slash
|
|
# form, so you land on `dir_tool`.
|
|
#
|
|
# JSON listing requests are unaffected by either key — they always get
|
|
# the raw directory listing, so the browse SPA (and any other client)
|
|
# can enumerate entries no matter what dir_tool/default_tool are.
|
|
#
|
|
# Both keys cascade leaf→root: a parent's default_tool applies to
|
|
# descendants unless a deeper level overrides it (browse set on
|
|
# working/ reaches working/alice/notes/ for free). The keys below set
|
|
# default_tool on the canonical folders; dir_tool is left unset
|
|
# everywhere, so the slash form is always `browse`.
|
|
#
|
|
# ── Canonical project structure ────────────────────────────────────────────
|
|
#
|
|
# Every ZDDC project lives at a top-level directory. Under it the
|
|
# convention is four canonical folders: archive (formal record),
|
|
# working (in-progress workspace), staging (outbound prep), reviewing
|
|
# (Plan-Review-managed draft workspaces). Under archive/<party>/ the
|
|
# convention is four more: mdl (deliverables list), incoming (counterparty
|
|
# drop zone), received (immutable submittals), issued (immutable responses).
|
|
#
|
|
# All of this is expressed via the recursive paths: schema. None of
|
|
# the directories need to exist on disk — the cascade walker resolves
|
|
# behaviour from this declaration, so a fresh project lands on
|
|
# usable empty views at every well-known URL.
|
|
#
|
|
# Operators override any of this by mirroring the structure in an
|
|
# on-disk .zddc and changing what they need; on-disk values win.
|
|
|
|
paths:
|
|
# First segment under root is the project name; "*" matches any.
|
|
"*":
|
|
# Project-scoped baseline ACL. project_team gets read across the
|
|
# project; document_controller gets read + overwrite-existing
|
|
# (so people can ask them to fix a stuck file). Neither gets
|
|
# `c` (create) at this level — that's granted only at the
|
|
# specific spots below (archive/, working/, staging/), so the
|
|
# doc controller can't make arbitrary folders. Grants here cap
|
|
# at deeper levels per deepest-match-wins, except where a deeper
|
|
# .zddc restates a fuller grant for the same principal.
|
|
acl:
|
|
permissions:
|
|
project_team: r
|
|
document_controller: rw
|
|
# Plan Review composite endpoint: the doc controller right-clicks
|
|
# archive/<party>/received/<tracking>/ in the browse app and gets
|
|
# a "Plan Review" item that scaffolds workflow folders under the
|
|
# paths below. Both keys required; omitting the block disables
|
|
# the menu item for this subtree.
|
|
on_plan_review:
|
|
reviewing_root: reviewing/
|
|
staging_root: staging/
|
|
paths:
|
|
archive:
|
|
default_tool: archive
|
|
# The doc controller can create party subfolders here
|
|
# (archive/<party>/). Restate the full grant — deepest-match
|
|
# is per-principal replacement, so we re-list rw + add c.
|
|
acl:
|
|
permissions:
|
|
document_controller: rwc
|
|
paths:
|
|
# Second segment under archive/ is the party name.
|
|
"*":
|
|
# When the doc controller creates a party folder, an
|
|
# auto-own .zddc grants them rwcda there (UNFENCED — so
|
|
# the project-level project_team:r still cascades through
|
|
# to received/issued). That lets them set up the
|
|
# counterparty's own .zddc afterward.
|
|
auto_own: true
|
|
paths:
|
|
mdl:
|
|
default_tool: tables
|
|
available_tools: [tables]
|
|
# The mdl folder is virtual by convention — the
|
|
# tables tool serves it from the embedded default
|
|
# spec even when the on-disk folder doesn't exist.
|
|
virtual: true
|
|
incoming:
|
|
# incoming/ is the COUNTERPARTY's drop zone. The flow:
|
|
# 1. the other party's document controller uploads
|
|
# files here (a deployment grants them cr at this
|
|
# path, e.g. acl: { permissions: { "*@acme.com": cr } }
|
|
# at archive/Acme/incoming/.zddc — or they mkdir a
|
|
# dated subfolder under incoming/ and own it via
|
|
# auto_own)
|
|
# 2. OUR document controller QCs them via classifier
|
|
# (rename in place) and moves them to received/
|
|
# (which needs delete here + worm-create there),
|
|
# ideally returning a signed transmittal in issued/
|
|
#
|
|
# The normal project_team has only read here (inherited
|
|
# from the project level — they have no c/w) so they can
|
|
# see what's been dropped but not touch it. The
|
|
# document_controller grant restates rwcd so the QC +
|
|
# transfer-out workflow has the delete bit it needs.
|
|
default_tool: classifier
|
|
available_tools: [classifier]
|
|
auto_own: true
|
|
drop_target: true
|
|
acl:
|
|
permissions:
|
|
document_controller: rwcd
|
|
# received/ and issued/ are WORM (write-once-read-many).
|
|
# The `worm:` list marks the zone:
|
|
#
|
|
# - write (w) and delete (d) are stripped for EVERYONE
|
|
# - create (c) is stripped for everyone EXCEPT the
|
|
# principals listed — they get read + write-once-
|
|
# create ("cr")
|
|
# - read for non-listed principals is whatever the
|
|
# normal cascade ACL granted; the WORM list does not
|
|
# itself confer read to outsiders
|
|
# - admins (root / subtree) bypass entirely — the
|
|
# human escape hatch for mis-filed documents
|
|
#
|
|
# The baseline is an empty list: WORM zone, no
|
|
# create-capable principals — filing is locked until a
|
|
# deployment names a document controller, e.g.
|
|
#
|
|
# worm: ["doc-control@example.com"]
|
|
#
|
|
# at received/ (or issued/, or archive/<party>/, or
|
|
# wherever scopes it right). worm: lists UNION across the
|
|
# cascade, so a deeper .zddc adds more controllers.
|
|
received:
|
|
default_tool: archive
|
|
# document_controller may file write-once into the
|
|
# WORM zone. Their project-level rw is masked here
|
|
# to r; worm: restores write-once-create.
|
|
worm: [document_controller]
|
|
issued:
|
|
default_tool: archive
|
|
worm: [document_controller]
|
|
working:
|
|
default_tool: browse
|
|
available_tools: [browse, classifier]
|
|
# working/ auto-owns the first creator + the per-user homes
|
|
# below.
|
|
auto_own: true
|
|
drop_target: true
|
|
# Doc controller is subtree-admin of working/ — full create
|
|
# + manage, including taking over a fenced per-user home if a
|
|
# user leaves. (Scoped here, not at the project root, so the
|
|
# WORM constraint in archive/<party>/received|issued still
|
|
# binds them.)
|
|
admins: [document_controller]
|
|
paths:
|
|
"*": # per-user home dir
|
|
default_tool: browse
|
|
available_tools: [browse, classifier]
|
|
auto_own: true
|
|
# Per-user home is private by default: the generated
|
|
# auto-own .zddc carries inherit:false so ancestor ACL
|
|
# grants don't reach inside. The user can edit the file
|
|
# to grant collaborators access.
|
|
auto_own_fenced: true
|
|
drop_target: true
|
|
staging:
|
|
default_tool: transmittal
|
|
available_tools: [transmittal, classifier]
|
|
auto_own: true
|
|
drop_target: true
|
|
# Doc controller is subtree-admin of staging/ too — same
|
|
# rationale as working/.
|
|
admins: [document_controller]
|
|
reviewing:
|
|
default_tool: browse
|
|
available_tools: [browse]
|
|
# reviewing/ is the doc-controller's draft-workspace area. The
|
|
# "Plan Review" composite endpoint (see on_plan_review at project
|
|
# level) scaffolds a physical folder here for each submittal
|
|
# under review, with a .zddc carrying received_path back to the
|
|
# canonical submittal in received/. Subtree-admin so the doc
|
|
# controller can author per-folder .zddc files (originator ACL,
|
|
# planned_date).
|
|
auto_own: true
|
|
drop_target: true
|
|
admins: [document_controller]
|