docs: post-reshape + role-redesign refresh
All checks were successful
Deploy content to live site / deploy (push) Successful in 2s
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:
parent
06916e5884
commit
38c8b0cfa1
2 changed files with 78 additions and 51 deletions
|
|
@ -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>
|
<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">
|
<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/<party>/{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/<your-email>/</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/<party>/{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/<party>/working/<your-email>/</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>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>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:
|
<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:
|
||||||
|
|
|
||||||
117
reference.html
117
reference.html
|
|
@ -1001,47 +1001,69 @@ date = 4DIGIT "-" 2DIGIT "-" 2DIGIT
|
||||||
project/
|
project/
|
||||||
.zddc ← the only file an operator must create
|
.zddc ← the only file an operator must create
|
||||||
|
|
||||||
working/ ← Where staff draft. Each person has their own
|
archive/ ← The only PHYSICAL project-root directory.
|
||||||
subfolder here (named by email) and works on
|
Everything party-scoped (records, lifecycle,
|
||||||
documents in private until they're ready to
|
immutable archives) lives uniformly under
|
||||||
issue. The browse tool handles everything here
|
archive/<party>/.
|
||||||
— file management, in-place markdown editing
|
|
||||||
with live preview, on-demand format conversion.
|
|
||||||
|
|
||||||
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.
|
{party-name}/ ← One per counterparty; one for ourselves.
|
||||||
We treat our own organisation as just another
|
We treat our own organisation as just another
|
||||||
party so internal deliverables get the same
|
party so internal deliverables get the same
|
||||||
tracking treatment as external ones.
|
tracking treatment as external ones.
|
||||||
mdl/ ← The party's Master Deliverables List —
|
mdl/ ← The party's Master Deliverables List —
|
||||||
what they're going to produce, planned and
|
what they're going to produce. Opens as
|
||||||
in-flight. Opens as a grid editor; rows are
|
a grid editor; rows are individual .yaml
|
||||||
individual yaml files, one per deliverable.
|
files, one per deliverable.
|
||||||
incoming/ ← Where that party drops things for us. Acts
|
rsk/ ← Risk register for this party. Same grid
|
||||||
as a quality-check buffer before content
|
editor as mdl/.
|
||||||
|
incoming/ ← Where the counterparty drops things for us.
|
||||||
|
A quality-check buffer before content
|
||||||
becomes part of the permanent record.
|
becomes part of the permanent record.
|
||||||
received/ ← What we've accepted from that party. Immutable;
|
received/ ← What we've accepted from that party.
|
||||||
the historical record of inbound documents.
|
Immutable (WORM); the historical record
|
||||||
issued/ ← What we sent to that party. Immutable; the
|
of inbound documents.
|
||||||
historical record of outbound 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.
|
||||||
|
|
||||||
|
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.
|
||||||
</div>
|
</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><your-email>/</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/<party>/…</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>
|
<p><strong>5-step transmittal workflow:</strong></p>
|
||||||
|
|
||||||
|
|
@ -1093,12 +1115,12 @@ project/
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Drafting a response transmittal</h3>
|
<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>
|
<ol>
|
||||||
<li>Create <code>staging/<YYYY-MM-DD>_<tracking> (RSA) - <title>/</code>. The date is the planned issue (response-due) date.</li>
|
<li>Right-click the submittal in <code>archive/<party>/received/<tracking>/</code> and pick <strong>Plan Review</strong>. The server scaffolds a workflow folder at <code>archive/<party>/reviewing/<tracking>/</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 server mirrors the folder name into <code>working/</code>; reviewer notes and the response payload are drafted there.</li>
|
<li>The party's working subfolder under <code>archive/<party>/working/<your-email>/</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>The <code>reviewing/</code> virtual surface lists each in-progress response paired with the source submittal in <code>archive/<party>/received/</code>.</li>
|
<li>When the response is ready, files move from <code>working/</code> into <code>archive/<party>/staging/</code> for sign-off. Project team's permission on staged files downgrades to <code>cr</code> — the doc controller takes over.</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/<party>/issued/</code>. Both the staging and working folders are deleted at issue time.</li>
|
<li>The doc controller cuts the package from <code>staging/</code> into <code>archive/<party>/issued/</code> via the standard 5-step transmittal flow. The reviewing scaffold is deleted at issue time.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -1126,7 +1148,7 @@ project/
|
||||||
|
|
||||||
<h3>Step 2 — per-project <code><project>/.zddc</code></h3>
|
<h3>Step 2 — per-project <code><project>/.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"
|
<div class="code-block">title: "Project Phoenix"
|
||||||
roles:
|
roles:
|
||||||
|
|
@ -1135,17 +1157,22 @@ roles:
|
||||||
- dc1@burnsmcd.com
|
- dc1@burnsmcd.com
|
||||||
project_team:
|
project_team:
|
||||||
members:
|
members:
|
||||||
- alice@burnsmcd.com
|
- '*@burnsmcd.com' # all internal staff
|
||||||
- '*@acme.com' # external counterparty (glob)
|
observer: # optional — external audit
|
||||||
|
members:
|
||||||
|
- auditor@external.com
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>That's it. The embedded cascade does the rest:</p>
|
<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;">
|
<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>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> 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>document_controller</code> creates party folders at <code>archive/</code>; when they do, the auto-own <code>.zddc</code> written at <code>archive/<party>/</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>
|
</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>
|
<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>
|
<h3>Schema essentials</h3>
|
||||||
|
|
@ -1189,7 +1216,7 @@ roles:
|
||||||
<li><code>w</code> write (overwrite existing files)</li>
|
<li><code>w</code> write (overwrite existing files)</li>
|
||||||
<li><code>c</code> create (new files, new directories)</li>
|
<li><code>c</code> create (new files, new directories)</li>
|
||||||
<li><code>d</code> delete</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>
|
</ul>
|
||||||
|
|
||||||
<p>An empty bits string (<code>''</code>) is an explicit deny.</p>
|
<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>
|
<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>
|
||||||
|
|
||||||
<!-- Section 11: Tools -->
|
<!-- Section 11: Tools -->
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue