docs: post-reshape + role-redesign refresh
All checks were successful
Deploy content to live site / deploy (push) Successful in 2s

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/<party>/. 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/<party>/reviewing/<tracking>/.
  - Role descriptions (§10): document_controller is no longer
    subtree-admin anywhere. Authority cascades from the auto-own
    .zddc written at each archive/<party>/ 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) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-05-21 11:27:26 -05:00
parent 06916e5884
commit 38c8b0cfa1
2 changed files with 78 additions and 51 deletions

View file

@ -134,7 +134,7 @@
<p style="margin-top: var(--spacing-md);"><strong><code class="inline">zddc-server</code></strong> is a small Go binary purpose-built to serve ZDDC archives. <em>Any</em> web server gives you online mode; <code class="inline">zddc-server</code> adds things a generic web server can't:</p>
<ul class="feature-list">
<li><strong>Lazy folder creation, case-fold matching.</strong> Drop a <code class="inline">.zddc</code> file into an empty directory and the canonical project layout (<code class="inline">working/</code>, <code class="inline">staging/</code>, <code class="inline">archive/&lt;party&gt;/{mdl,incoming,received,issued}/</code>) materialises on the first write into each path — never on bare reads. Folder names are matched case-insensitively, so an existing <code class="inline">Working/</code> is reused rather than shadowed by a new <code class="inline">working/</code> sibling. Each authenticated viewer sees a virtual <code class="inline">working/&lt;your-email&gt;/</code> entry; first write makes it real.</li>
<li><strong>Lazy folder creation, case-fold matching.</strong> Drop a <code class="inline">.zddc</code> file into an empty directory and the canonical project layout (<code class="inline">archive/&lt;party&gt;/{mdl,rsk,incoming,received,issued,working,staging,reviewing}/</code>) materialises on the first write into each path — never on bare reads. <code class="inline">archive/</code> is the only physical project-root child; <code class="inline">ssr</code>, <code class="inline">mdl</code>, <code class="inline">rsk</code>, <code class="inline">working</code>, <code class="inline">staging</code>, <code class="inline">reviewing</code> sit beside it as virtual aggregators that synthesise listings across parties. Folder names are matched case-insensitively, so an existing <code class="inline">Working/</code> is reused rather than shadowed by a new <code class="inline">working/</code> sibling. Each authenticated viewer sees a virtual <code class="inline">archive/&lt;party&gt;/working/&lt;your-email&gt;/</code> entry; first write makes it real.</li>
<li><strong>Virtual <code class="inline">.archive</code> URL space.</strong> <code class="inline">GET /Project/.archive/123-XYZ.html</code> resolves to the canonical revision file at request time. Computed from filenames; no cache, no separate index file.</li>
<li><strong>Access control via <code class="inline">.zddc</code> files.</strong> Behind a reverse proxy that authenticates users and sets an <code class="inline">X-Auth-Request-Email</code> request header, <code class="inline">zddc-server</code> consults YAML <code class="inline">.zddc</code> files at every directory along the path. The cascade walks root→leaf; the closest match wins. Five verbs (<code class="inline">r</code> read, <code class="inline">w</code> overwrite, <code class="inline">c</code> create, <code class="inline">d</code> delete, <code class="inline">a</code> admin / edit ACL) gate every operation. An empty grant (e.g. <code class="inline">"*@vendor.com": ""</code>) is an explicit deny. A subtree that wants to start fresh — vendor folder, regulated workspace — can declare <code class="inline">inherit: false</code> 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 <a href="https://codeberg.org/VARASYS/ZDDC/src/branch/main/zddc/README.md#access-control-the-zddc-cascade">access-control reference</a>. No database, no admin UI.</li>
<li><strong>Roles for human-readable grants.</strong> A <code class="inline">.zddc</code> may declare named roles whose members are email patterns; permissions then reference the role name instead of pasting the same wildcard everywhere:

View file

@ -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/&lt;party&gt;/.
{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.
&lt;your-email&gt;/ ← 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/&lt;party&gt;/working/.
staging/ ← Same shape as working/ for the staging slot.
reviewing/ ← Same shape as working/ for the reviewing slot.
</div>
<p style="margin-top: var(--spacing-md); font-size: 0.95em; color: var(--color-text-soft);"><em>Mechanics:</em> 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 <em>&lt;your-email&gt;/</em> entry under <code>working/</code> 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.</p>
<p style="margin-top: var(--spacing-md); font-size: 0.95em; color: var(--color-text-soft);"><em>Mechanics:</em> folders materialise on first write, names match case-insensitively, the WORM zones (<code>received/</code>, <code>issued/</code>) enforce write-once via an ACL mask, and the six top-level aggregators (<code>ssr</code>/<code>mdl</code>/<code>rsk</code>/<code>working</code>/<code>staging</code>/<code>reviewing</code>) are virtual — they never materialise on disk but show up in listings, computed from <code>archive/&lt;party&gt;/&hellip;</code> at request time. Mkdir at the project root is restricted to <code>archive</code> + system names (<code>_</code>/<code>.</code>-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.</p>
<p style="margin-top: var(--spacing-md); font-size: 0.95em; color: var(--color-text-soft);"><em>The in-flight ratchet.</em> <code>working/</code><code>staging/</code><code>issued/</code> is a one-way handoff. <code>project_team</code> iterates freely in <code>working/</code> (the auto-own-fenced subfolder gives each user a private <code>rwcda</code> workspace). When they drop a file into <code>staging/</code> their access downgrades to <code>cr</code> — they can drop more, but only the <code>document_controller</code> can change what's already there. When DC publishes to <code>issued/</code>, the WORM mask downgrades even DC to <code>cr</code> (write-once). Each handoff is a commitment by permission-loss.</p>
<p><strong>5-step transmittal workflow:</strong></p>
@ -1093,12 +1115,12 @@ project/
</div>
<h3>Drafting a response transmittal</h3>
<p>Submittals from counterparties (tracking numbers containing <code>-SUB-</code> at status <code>IFR</code> or <code>IFA</code>) require a response transmittal whose status starts with <code>RS</code> (RSA, RSB, RSC, …). The drafting flow uses the same staging↔working pairing as a fresh outbound:</p>
<p>Submittals from counterparties (tracking numbers containing <code>-SUB-</code> at status <code>IFR</code> or <code>IFA</code>) require a response transmittal whose status starts with <code>RS</code> (RSA, RSB, RSC, …). The flow walks the ratchet:</p>
<ol>
<li>Create <code>staging/&lt;YYYY-MM-DD&gt;_&lt;tracking&gt; (RSA) - &lt;title&gt;/</code>. The date is the planned issue (response-due) date.</li>
<li>The server mirrors the folder name into <code>working/</code>; reviewer notes and the response payload are drafted there.</li>
<li>The <code>reviewing/</code> virtual surface lists each in-progress response paired with the source submittal in <code>archive/&lt;party&gt;/received/</code>.</li>
<li>When the response is ready, files move from <code>working/</code> into <code>staging/</code> for sign-off, then through the standard 5-step transmittal flow into <code>archive/&lt;party&gt;/issued/</code>. Both the staging and working folders are deleted at issue time.</li>
<li>Right-click the submittal in <code>archive/&lt;party&gt;/received/&lt;tracking&gt;/</code> and pick <strong>Plan Review</strong>. The server scaffolds a workflow folder at <code>archive/&lt;party&gt;/reviewing/&lt;tracking&gt;/</code> — its <code>.zddc</code> carries a <code>received_path</code> pointer back to the canonical submittal and a planned response date.</li>
<li>The party's working subfolder under <code>archive/&lt;party&gt;/working/&lt;your-email&gt;/</code> is where reviewer notes and the response payload are drafted. The <code>reviewing/</code> virtual aggregator at the project root surfaces all open reviews across parties.</li>
<li>When the response is ready, files move from <code>working/</code> into <code>archive/&lt;party&gt;/staging/</code> for sign-off. Project team's permission on staged files downgrades to <code>cr</code> — the doc controller takes over.</li>
<li>The doc controller cuts the package from <code>staging/</code> into <code>archive/&lt;party&gt;/issued/</code> via the standard 5-step transmittal flow. The reviewing scaffold is deleted at issue time.</li>
</ol>
</section>
@ -1126,7 +1148,7 @@ project/
<h3>Step 2 — per-project <code>&lt;project&gt;/.zddc</code></h3>
<p>In each project, populate the <code>document_controller</code> and <code>project_team</code> role members:</p>
<p>In each project, populate role members:</p>
<div class="code-block">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
</div>
<p>That's it. The embedded cascade does the rest:</p>
<ul style="margin: var(--spacing-md) 0; padding-left: 1.5rem; color: var(--color-text-muted); line-height: 1.65;">
<li><code>project_team</code> gets read across the project</li>
<li><code>document_controller</code> gets read+write project-wide, plus create authority on <code>archive/</code>, WORM filing rights on <code>received/</code> and <code>issued/</code>, and subtree-admin of <code>working/</code>, <code>staging/</code>, and <code>reviewing/</code></li>
<li><code>project_team</code> gets read across the project plus the in-flight ratchet (<code>cr</code> in <code>working/</code> + <code>reviewing/</code> + <code>staging/</code>, with <code>rwcda</code> inside each user's auto-own-fenced home under <code>working/</code>).</li>
<li><code>document_controller</code> creates party folders at <code>archive/</code>; when they do, the auto-own <code>.zddc</code> written at <code>archive/&lt;party&gt;/</code> grants both their email AND the <code>document_controller</code> role <code>rwcda</code> — so any DC in the role has full authority at every party a peer created. Explicit <code>rwcd</code> grants at <code>incoming/</code>/<code>staging/</code> for the QC + transfer workflows, and WORM <code>cr</code> at <code>received/</code>/<code>issued/</code> for write-once filing.</li>
<li><code>observer</code> is pure read-only across the project — intended for external auditors, regulators, and read-only viewers who must not contribute content.</li>
</ul>
<p style="margin-top: var(--spacing-md);">A DC is typically also a project team member (the <code>*@burnsmcd.com</code> glob catches them). The embedded defaults restate <code>document_controller: rwcda</code> at every slot that grants project_team a narrower verb — within-level union of all matched principals gives DCs <code>rwcda</code> <code>cr</code> = <code>rwcda</code>, preserving full authority. <strong>Document controllers are not subtree-admins anywhere.</strong> Their power comes purely from cascade grants; admin elevation is reserved for the root <code>admins:</code> list (the human escape hatch).</p>
<p>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 <code>zddc-server</code>.</p>
<h3>Schema essentials</h3>
@ -1189,7 +1216,7 @@ roles:
<li><code>w</code> write (overwrite existing files)</li>
<li><code>c</code> create (new files, new directories)</li>
<li><code>d</code> delete</li>
<li><code>a</code> admin (subtree-admin at this level and below)</li>
<li><code>a</code> admin (modify the <code>.zddc</code> ACL at this level — distinct from the root <code>admins:</code> list, which is the elevation-bypass sudo channel)</li>
</ul>
<p>An empty bits string (<code>''</code>) is an explicit deny.</p>
@ -1229,7 +1256,7 @@ request until you populate it..."</div>
<div class="code-block">zddc-server show-defaults</div>
<p>That prints the embedded <code>defaults.zddc.yaml</code> with comments explaining every option (<code>worm:</code>, <code>auto_own:</code>, <code>drop_target:</code>, <code>apps:</code>, <code>convert:</code>, <code>on_plan_review:</code>, <code>records:</code>, <code>available_tools:</code>, <code>default_tool:</code>, <code>dir_tool:</code>, and more). Pipe it to a file and use it as the starting point for any deeper customization.</p>
<p>That prints the embedded <code>defaults.zddc.yaml</code> with comments explaining every option (<code>worm:</code>, <code>auto_own:</code>, <code>auto_own_roles:</code>, <code>auto_own_fenced:</code>, <code>drop_target:</code>, <code>apps:</code>, <code>convert:</code>, <code>records:</code>, <code>available_tools:</code>, <code>default_tool:</code>, <code>dir_tool:</code>, and more). Pipe it to a file and use it as the starting point for any deeper customization.</p>
</section>
<!-- Section 11: Tools -->