From 38c8b0cfa18c2363c7f03f693ec4d1987b1dc2d1 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Thu, 21 May 2026 11:27:26 -0500 Subject: [PATCH] docs: post-reshape + role-redesign refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Catches the website up to the v0.0.21 server contract: - Project structure (reference.html §9): archive/ is the only physical project-root directory; the in-flight lifecycle (working/staging/reviewing) now lives PER-PARTY under archive//. Six top-level URLs (ssr/mdl/rsk/working/ staging/reviewing) are virtual aggregators synthesised from each party's content. - Retired the staging↔working mirror language — drafting a response transmittal now walks the in-flight ratchet through Plan Review's scaffold at archive//reviewing//. - Role descriptions (§10): document_controller is no longer subtree-admin anywhere. Authority cascades from the auto-own .zddc written at each archive// folder, which grants both the creator email AND the document_controller role `rwcda` (via auto_own_roles in the defaults). Multi-DC deployments work without admin status because the role itself is named in every party's auto-own grant. - Added the `observer` role (third standard role) with a pure-read-only intent for external auditors. - Documented the in-flight ratchet (working → staging → issued) as a one-way handoff that downgrades the prior role's modify rights at each step. - Clarified that the `a` verb is the .zddc-edit verb, distinct from the elevation-bypass sudo channel (root admins: list). - Dropped `on_plan_review:` from the cascade-keys reference (the key was retired when Plan Review hardcoded the scaffold convention); added `auto_own_roles:` and `auto_own_fenced:`. Co-Authored-By: Claude Opus 4.7 (1M context) --- index.html | 2 +- reference.html | 127 ++++++++++++++++++++++++++++++------------------- 2 files changed, 78 insertions(+), 51 deletions(-) diff --git a/index.html b/index.html index 7c3f71c..fa96275 100644 --- a/index.html +++ b/index.html @@ -134,7 +134,7 @@

zddc-server is a small Go binary purpose-built to serve ZDDC archives. Any web server gives you online mode; zddc-server adds things a generic web server can't:

    -
  • Lazy folder creation, case-fold matching. Drop a .zddc file into an empty directory and the canonical project layout (working/, staging/, archive/<party>/{mdl,incoming,received,issued}/) materialises on the first write into each path — never on bare reads. Folder names are matched case-insensitively, so an existing Working/ is reused rather than shadowed by a new working/ sibling. Each authenticated viewer sees a virtual working/<your-email>/ entry; first write makes it real.
  • +
  • Lazy folder creation, case-fold matching. Drop a .zddc file into an empty directory and the canonical project layout (archive/<party>/{mdl,rsk,incoming,received,issued,working,staging,reviewing}/) materialises on the first write into each path — never on bare reads. archive/ is the only physical project-root child; ssr, mdl, rsk, working, staging, reviewing sit beside it as virtual aggregators that synthesise listings across parties. Folder names are matched case-insensitively, so an existing Working/ is reused rather than shadowed by a new working/ sibling. Each authenticated viewer sees a virtual archive/<party>/working/<your-email>/ entry; first write makes it real.
  • Virtual .archive URL space. GET /Project/.archive/123-XYZ.html resolves to the canonical revision file at request time. Computed from filenames; no cache, no separate index file.
  • Access control via .zddc files. Behind a reverse proxy that authenticates users and sets an X-Auth-Request-Email request header, zddc-server consults YAML .zddc files at every directory along the path. The cascade walks root→leaf; the closest match wins. Five verbs (r read, w overwrite, c create, d delete, a admin / edit ACL) gate every operation. An empty grant (e.g. "*@vendor.com": "") is an explicit deny. A subtree that wants to start fresh — vendor folder, regulated workspace — can declare inherit: false to fence off ancestor grants and roles, then list the principals it does want. Common shapes (paired open/closed projects, third-party-restricted vendor folders) are documented with worked examples in the access-control reference. No database, no admin UI.
  • Roles for human-readable grants. A .zddc may declare named roles whose members are email patterns; permissions then reference the role name instead of pasting the same wildcard everywhere: diff --git a/reference.html b/reference.html index db7b593..eed046d 100644 --- a/reference.html +++ b/reference.html @@ -1001,47 +1001,69 @@ date = 4DIGIT "-" 2DIGIT "-" 2DIGIT project/ .zddc ← the only file an operator must create - working/ ← Where staff draft. Each person has their own - subfolder here (named by email) and works on - documents in private until they're ready to - issue. The browse tool handles everything here - — file management, in-place markdown editing - with live preview, on-demand format conversion. + archive/ ← The only PHYSICAL project-root directory. + Everything party-scoped (records, lifecycle, + immutable archives) lives uniformly under + archive/<party>/. + {party-name}/ ← One per counterparty; one for ourselves. + We treat our own organisation as just another + party so internal deliverables get the same + tracking treatment as external ones. + mdl/ ← The party's Master Deliverables List — + what they're going to produce. Opens as + a grid editor; rows are individual .yaml + files, one per deliverable. + rsk/ ← Risk register for this party. Same grid + editor as mdl/. + incoming/ ← Where the counterparty drops things for us. + A quality-check buffer before content + becomes part of the permanent record. + received/ ← What we've accepted from that party. + Immutable (WORM); the historical record + of inbound documents. + issued/ ← What we sent to that party. Immutable + (WORM); the historical record of outbound + documents. + working/ ← Where this party's staff draft. Each + person has a private subfolder here + (named by email) and iterates until ready + to commit. The browse tool handles + everything here. + <your-email>/ ← Your private workspace under THIS + party. Fenced (inherit:false) by the + auto-own .zddc, so other team members + only see what you explicitly share. + staging/ ← The "about to issue" lane for this party. + Drop files here and the project_team + gives them up — only the doc-controller + can change a file after it lands. Each + sub-folder declares a planned outbound + transmittal (name carries the date + + tracking number). + reviewing/ ← One place per party to see everything we + owe a response on. Folders are scaffolded + by Plan Review, each pairing an inbound + submittal in received/ with the in-progress + response draft. - staging/ ← The "about to issue" lane. A folder in here - declares a planned outbound transmittal: its - name carries the planned issue date and tracking - number, so anyone can see what's queued up and - when. Files in staging are the finalised set; - drafting still happens in working/. - - reviewing/ ← One place to see everything we owe a response - on. Each row pairs an inbound submittal we've - accepted with our in-progress response draft — - saves staff from hunting across per-party - folders to find what's open. - - archive/ ← The permanent record. Everything we've ever - formally exchanged with a counterparty lives - here, organised one folder per party. - {party-name}/ ← One per counterparty; one for ourselves. - We treat our own organisation as just another - party so internal deliverables get the same - tracking treatment as external ones. - mdl/ ← The party's Master Deliverables List — - what they're going to produce, planned and - in-flight. Opens as a grid editor; rows are - individual yaml files, one per deliverable. - incoming/ ← Where that party drops things for us. Acts - as a quality-check buffer before content - becomes part of the permanent record. - received/ ← What we've accepted from that party. Immutable; - the historical record of inbound documents. - issued/ ← What we sent to that party. Immutable; the - historical record of outbound documents. + ssr/ ← Virtual aggregator: tables rollup of every + party's ssr.yaml row, with a synthesised + $party column. Never on disk. + mdl/ ← Virtual aggregator: tables rollup of every + party's mdl/*.yaml deliverables. + rsk/ ← Virtual aggregator: tables rollup of every + party's rsk/*.yaml risk rows. + working/ ← Virtual aggregator: browse folder-nav listing + parties with non-empty content in working/. + Per-party clicks 302-redirect to the canonical + archive/<party>/working/. + staging/ ← Same shape as working/ for the staging slot. + reviewing/ ← Same shape as working/ for the reviewing slot. -

    Mechanics: folders materialise on first write, names match case-insensitively, the staging↔working pairing is automatic, the immutable folders enforce write-once via an ACL mask, and a virtual <your-email>/ entry under working/ creates your personal subfolder on first save. None of that needs to be in your head when you're using the system — drop files where the lifecycle says they go and the layout takes care of itself.

    +

    Mechanics: folders materialise on first write, names match case-insensitively, the WORM zones (received/, issued/) enforce write-once via an ACL mask, and the six top-level aggregators (ssr/mdl/rsk/working/staging/reviewing) are virtual — they never materialise on disk but show up in listings, computed from archive/<party>/… at request time. Mkdir at the project root is restricted to archive + system names (_/.-prefixed) so the virtual names can never be shadowed by a physical folder. Drop files where the lifecycle says they go and the layout takes care of itself.

    + +

    The in-flight ratchet. working/staging/issued/ is a one-way handoff. project_team iterates freely in working/ (the auto-own-fenced subfolder gives each user a private rwcda workspace). When they drop a file into staging/ their access downgrades to cr — they can drop more, but only the document_controller can change what's already there. When DC publishes to issued/, the WORM mask downgrades even DC to cr (write-once). Each handoff is a commitment by permission-loss.

    5-step transmittal workflow:

    @@ -1093,12 +1115,12 @@ project/

    Drafting a response transmittal

    -

    Submittals from counterparties (tracking numbers containing -SUB- at status IFR or IFA) require a response transmittal whose status starts with RS (RSA, RSB, RSC, …). The drafting flow uses the same staging↔working pairing as a fresh outbound:

    +

    Submittals from counterparties (tracking numbers containing -SUB- at status IFR or IFA) require a response transmittal whose status starts with RS (RSA, RSB, RSC, …). The flow walks the ratchet:

      -
    1. Create staging/<YYYY-MM-DD>_<tracking> (RSA) - <title>/. The date is the planned issue (response-due) date.
    2. -
    3. The server mirrors the folder name into working/; reviewer notes and the response payload are drafted there.
    4. -
    5. The reviewing/ virtual surface lists each in-progress response paired with the source submittal in archive/<party>/received/.
    6. -
    7. When the response is ready, files move from working/ into staging/ for sign-off, then through the standard 5-step transmittal flow into archive/<party>/issued/. Both the staging and working folders are deleted at issue time.
    8. +
    9. Right-click the submittal in archive/<party>/received/<tracking>/ and pick Plan Review. The server scaffolds a workflow folder at archive/<party>/reviewing/<tracking>/ — its .zddc carries a received_path pointer back to the canonical submittal and a planned response date.
    10. +
    11. The party's working subfolder under archive/<party>/working/<your-email>/ is where reviewer notes and the response payload are drafted. The reviewing/ virtual aggregator at the project root surfaces all open reviews across parties.
    12. +
    13. When the response is ready, files move from working/ into archive/<party>/staging/ for sign-off. Project team's permission on staged files downgrades to cr — the doc controller takes over.
    14. +
    15. The doc controller cuts the package from staging/ into archive/<party>/issued/ via the standard 5-step transmittal flow. The reviewing scaffold is deleted at issue time.
    @@ -1126,7 +1148,7 @@ project/

    Step 2 — per-project <project>/.zddc

    -

    In each project, populate the document_controller and project_team role members:

    +

    In each project, populate role members:

    title: "Project Phoenix" roles: @@ -1135,17 +1157,22 @@ roles: - dc1@burnsmcd.com project_team: members: - - alice@burnsmcd.com - - '*@acme.com' # external counterparty (glob) + - '*@burnsmcd.com' # all internal staff + observer: # optional — external audit + members: + - auditor@external.com

    That's it. The embedded cascade does the rest:

      -
    • project_team gets read across the project
    • -
    • document_controller gets read+write project-wide, plus create authority on archive/, WORM filing rights on received/ and issued/, and subtree-admin of working/, staging/, and reviewing/
    • +
    • project_team gets read across the project plus the in-flight ratchet (cr in working/ + reviewing/ + staging/, with rwcda inside each user's auto-own-fenced home under working/).
    • +
    • document_controller creates party folders at archive/; when they do, the auto-own .zddc written at archive/<party>/ grants both their email AND the document_controller role rwcda — so any DC in the role has full authority at every party a peer created. Explicit rwcd grants at incoming//staging/ for the QC + transfer workflows, and WORM cr at received//issued/ for write-once filing.
    • +
    • observer is pure read-only across the project — intended for external auditors, regulators, and read-only viewers who must not contribute content.
    +

    A DC is typically also a project team member (the *@burnsmcd.com glob catches them). The embedded defaults restate document_controller: rwcda at every slot that grants project_team a narrower verb — within-level union of all matched principals gives DCs rwcdacr = rwcda, preserving full authority. Document controllers are not subtree-admins anywhere. Their power comes purely from cascade grants; admin elevation is reserved for the root admins: list (the human escape hatch).

    +

    Add a new project team member with one line; revoke by removing the line. No need to restate the cascade's grants — they're already in the embedded defaults that ship with zddc-server.

    Schema essentials

    @@ -1189,7 +1216,7 @@ roles:
  • w write (overwrite existing files)
  • c create (new files, new directories)
  • d delete
  • -
  • a admin (subtree-admin at this level and below)
  • +
  • a admin (modify the .zddc ACL at this level — distinct from the root admins: list, which is the elevation-bypass sudo channel)

An empty bits string ('') is an explicit deny.

@@ -1229,7 +1256,7 @@ request until you populate it..."
zddc-server show-defaults
-

That prints the embedded defaults.zddc.yaml with comments explaining every option (worm:, auto_own:, drop_target:, apps:, convert:, on_plan_review:, records:, available_tools:, default_tool:, dir_tool:, and more). Pipe it to a file and use it as the starting point for any deeper customization.

+

That prints the embedded defaults.zddc.yaml with comments explaining every option (worm:, auto_own:, auto_own_roles:, auto_own_fenced:, drop_target:, apps:, convert:, records:, available_tools:, default_tool:, dir_tool:, and more). Pipe it to a file and use it as the starting point for any deeper customization.