docs: lead with folder purpose; surface RBAC + WORM on federal page
All checks were successful
Deploy content to live site / deploy (push) Successful in 3s

reference.html § 9: rewrite the canonical-folder tree so each line leads
with what the folder is FOR (drafting space, "about to issue" lane,
permanent record per counterparty, planned deliverables list, review
queue) rather than mechanics. The lifecycle stage of a document is now
visible from its location alone. Mechanics (lazy creation, case-fold
matching, virtual user home, paired delete on issue) demoted to a
single trailing paragraph so a reader can grasp the layout without
needing to track them.

federal.html: surface the access-control features that landed since the
page was written —

- Role-based access control as a first-class shipped feature, with the
  AC-2 / AC-3(7) mapping called out.
- Verb-based least privilege (r/w/c/d/a) under AC-6, with the rc
  shape used by immutable archives flagged explicitly.
- WORM enforcement on archive/<party>/{received,issued}/ under AU-9
  and MP-5, including the at-the-WORM-folder grant pattern that lets
  doc controllers drop transmittals without giving them overwrite.
- Cascade tracer (/.profile/effective-policy) under AC-3 reviewability.
- OPA wire-format detail (input shape + cache TTL + fail-open).

Move "Role-based access control" out of the "what you'd add for ATO"
table now that it's shipped; replace with "Identity-provider role
sync" — the integrator's job is wiring AD/Okta/EntraID groups into
the existing role members: list, not building RBAC from scratch.
Update "Policy export" to acknowledge the per-path tracer that already
ships and frames the missing piece as the batch-export companion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-05-07 09:42:45 -05:00
parent f81fb4e769
commit 6aae181d19
2 changed files with 51 additions and 36 deletions

View file

@ -54,8 +54,12 @@
<p>These controls ship in every release today. No federal-specific build required to use them.</p> <p>These controls ship in every release today. No federal-specific build required to use them.</p>
<ul class="feature-list"> <ul class="feature-list">
<li><strong>Hardened TLS posture.</strong> The TLS 1.2 cipher suite list, curve preferences, and HSTS header are pinned to the federal TLS guidance (NIST SP 800-52 Rev. 2). Only AEAD ciphers; only forward-secret key exchanges; only modern curves. Weak suites cannot be negotiated even if a client offers them.</li> <li><strong>Hardened TLS posture.</strong> The TLS 1.2 cipher suite list, curve preferences, and HSTS header are pinned to the federal TLS guidance (NIST SP 800-52 Rev. 2). Only AEAD ciphers; only forward-secret key exchanges; only modern curves. Weak suites cannot be negotiated even if a client offers them.</li>
<li><strong>Pluggable policy engine.</strong> Access decisions can be delegated to an external <a href="https://www.openpolicyagent.org/" rel="noopener">Open Policy Agent</a> server with the customer's own audited Rego rules. The default in-process engine and the OPA-delegated engine speak the same wire format, so customers swap by setting one environment variable.</li> <li><strong>Role-based access control (NIST AC-2 / AC-3).</strong> A <code class="inline">.zddc</code> file declares named roles whose members are email patterns; permissions reference role names rather than pasting the same wildcards across every directory. Role definitions cascade and child files shadow ancestors for that subtree, so federation between an organisation-wide role definition and a project-specific override is built in. Maps cleanly to AC-3(7) "role-based" enforcement and AC-2 account-management workflows.</li>
<li><strong>Verb-based least privilege (NIST AC-6).</strong> Every access decision evaluates one of five explicit 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. A grant of <code class="inline">rc</code> means "this principal can read existing files and create new ones, but cannot modify or delete what's already there" — exactly the permission shape an immutable archive needs. Empty grants (<code class="inline">""</code>) are explicit denies that beat any other grant at the same level.</li>
<li><strong>Write-once-read-many archive folders (NIST AU-9, MP-5).</strong> Files placed under <code class="inline">archive/&lt;party&gt;/issued/</code> or <code class="inline">archive/&lt;party&gt;/received/</code> are protected by a server-enforced verb mask: ancestor grants are reduced to read-only when crossing the WORM boundary, regardless of what an upstream <code class="inline">.zddc</code> says. A separate <code class="inline">.zddc</code> placed at the WORM folder itself can grant <code class="inline">rc</code> to specific principals (the doc-controller dropping a fresh transmittal) — that survives the mask. Root admins bypass the mask only as the deliberate escape hatch for mis-filed documents, with bypasses visible in the audit log.</li>
<li><strong>Pluggable policy engine.</strong> Access decisions can be delegated to an external <a href="https://www.openpolicyagent.org/" rel="noopener">Open Policy Agent</a> server with the customer's own audited Rego rules. The server POSTs request user, path, action, and the full <code class="inline">.zddc</code> cascade chain to <code class="inline">/v1/data/zddc/access/allow</code>; the customer's OPA returns allow/deny. Default in-process engine and OPA-delegated engine speak the same wire format, so customers swap by setting one environment variable. Failures fail closed by default; <code class="inline">ZDDC_OPA_FAIL_OPEN=1</code> flips it for the rare case where availability outranks confidentiality.</li>
<li><strong>Strict-least-privilege policy variant available out of the box.</strong> A parity-tested federal-mode Rego that enforces NIST AC-6 (parent denies are absolute; no leaf-level override) ships embedded in the binary. <code>zddc-server --print-rego=federal</code> emits it for use with the customer's OPA.</li> <li><strong>Strict-least-privilege policy variant available out of the box.</strong> A parity-tested federal-mode Rego that enforces NIST AC-6 (parent denies are absolute; no leaf-level override) ships embedded in the binary. <code>zddc-server --print-rego=federal</code> emits it for use with the customer's OPA.</li>
<li><strong>Cascade tracer for reviewers (NIST AC-3 reviewability).</strong> Admins can hit <code class="inline">/.profile/effective-policy?path=&lt;url&gt;</code> on a running server to see the resolved ACL chain at any path — every directory's grants, the role evaluation, and the final verb-set returned for the requesting user. A security reviewer can confirm "yes, this person has exactly these rights at this path" without reading the source. Helpful during accreditation and for incident response.</li>
<li><strong>Structured audit logging.</strong> Every request is logged with the authenticated email, method, path, status, response size, and duration. Logs are JSON-line, ready for fluentd / Vector / SIEM pipelines.</li> <li><strong>Structured audit logging.</strong> Every request is logged with the authenticated email, method, path, status, response size, and duration. Logs are JSON-line, ready for fluentd / Vector / SIEM pipelines.</li>
<li><strong>Documented vulnerability-disclosure process.</strong> A <a href="https://codeberg.org/VARASYS/ZDDC/src/branch/main/SECURITY.md">SECURITY.md</a> covering supported versions, reporting channel, response timeline, embargo workflow, and CVE assignment.</li> <li><strong>Documented vulnerability-disclosure process.</strong> A <a href="https://codeberg.org/VARASYS/ZDDC/src/branch/main/SECURITY.md">SECURITY.md</a> covering supported versions, reporting channel, response timeline, embargo workflow, and CVE assignment.</li>
<li><strong>Cascading access control with explicit trust boundaries.</strong> The <code class="inline">.zddc</code> ACL model has documented invariants — root-only admin escalation gate, subtree-author authority limited to their subtree, default-deny when any <code class="inline">.zddc</code> exists. The <a href="https://codeberg.org/VARASYS/ZDDC/src/branch/main/zddc/README.md#access-control-the-zddc-cascade">access-control reference</a> includes a five-minute verify-it recipe a security reviewer can run on their own deployment.</li> <li><strong>Cascading access control with explicit trust boundaries.</strong> The <code class="inline">.zddc</code> ACL model has documented invariants — root-only admin escalation gate, subtree-author authority limited to their subtree, default-deny when any <code class="inline">.zddc</code> exists. The <a href="https://codeberg.org/VARASYS/ZDDC/src/branch/main/zddc/README.md#access-control-the-zddc-cascade">access-control reference</a> includes a five-minute verify-it recipe a security reviewer can run on their own deployment.</li>
@ -109,12 +113,12 @@
<td style="padding: var(--spacing-sm); vertical-align: top;">Today the proxy and zddc-server trust each other via network isolation. For federal, the recommended path is a signed forwarding token (JWT) — proxy signs each request with its private key, zddc-server verifies with the published public key. mTLS available as an alternative when the customer already operates a private CA.</td> <td style="padding: var(--spacing-sm); vertical-align: top;">Today the proxy and zddc-server trust each other via network isolation. For federal, the recommended path is a signed forwarding token (JWT) — proxy signs each request with its private key, zddc-server verifies with the published public key. mTLS available as an alternative when the customer already operates a private CA.</td>
</tr> </tr>
<tr style="border-bottom: 1px solid var(--color-border);"> <tr style="border-bottom: 1px solid var(--color-border);">
<td style="padding: var(--spacing-sm); vertical-align: top;"><strong>Role-based access control</strong></td> <td style="padding: var(--spacing-sm); vertical-align: top;"><strong>Identity-provider role sync</strong></td>
<td style="padding: var(--spacing-sm); vertical-align: top;">The current <code class="inline">.zddc</code> model grants access by email pattern. Federal AC-3(7) wants role-based grants populated from the customer's identity provider. The cascade syntax extends to express role allow/deny alongside email allow/deny; roles flow through the same OPA hook so customers running their own Rego pick them up automatically.</td> <td style="padding: var(--spacing-sm); vertical-align: top;">Role-based grants are already in the <code class="inline">.zddc</code> model — see the &quot;Role-based access control&quot; row above. What an integrator adds for federal is a sync layer that populates a role's <code class="inline">members:</code> list from the customer's identity provider (Active Directory groups, Okta, EntraID) on a schedule, so role membership tracks the IdP rather than being maintained by hand. The cascade format already accepts role members; this is the wiring that keeps them in sync.</td>
</tr> </tr>
<tr style="border-bottom: 1px solid var(--color-border);"> <tr style="border-bottom: 1px solid var(--color-border);">
<td style="padding: var(--spacing-sm); vertical-align: top;"><strong>Policy export for change control</strong></td> <td style="padding: var(--spacing-sm); vertical-align: top;"><strong>Policy export for change control</strong></td>
<td style="padding: var(--spacing-sm); vertical-align: top;">A command that walks the served tree and emits every directory's resolved access policy in JSON / Markdown / CSV. The change-control workflow checks the export into a Git repo; every <code class="inline">.zddc</code> change produces a diff that reviewers approve before deploy. NIST CM-3.</td> <td style="padding: var(--spacing-sm); vertical-align: top;">The cascade tracer (<code class="inline">/.profile/effective-policy</code>) already returns a resolved policy for any single path. What's missing is a batch export — a command that walks the entire served tree and emits every directory's resolved access policy in JSON / Markdown / CSV. The change-control workflow checks the export into a Git repo; every <code class="inline">.zddc</code> change produces a diff that reviewers approve before deploy. NIST CM-3.</td>
</tr> </tr>
<tr> <tr>
<td style="padding: var(--spacing-sm); vertical-align: top;"><strong>Code-signed tool fetches</strong></td> <td style="padding: var(--spacing-sm); vertical-align: top;"><strong>Code-signed tool fetches</strong></td>

View file

@ -917,42 +917,53 @@ date = 4DIGIT "-" 2DIGIT "-" 2DIGIT
<!-- Section 9: Project layout & transmittal workflow --> <!-- Section 9: Project layout & transmittal workflow -->
<section id="transmittal-workflow"> <section id="transmittal-workflow">
<h2>9. Project layout &amp; transmittal workflow</h2> <h2>9. Project layout &amp; transmittal workflow</h2>
<p>A project is a directory containing a single <code>.zddc</code> file. The server materialises the canonical folders below on the first write into them — drop a <code>.zddc</code> in an empty directory, point <code>zddc-server</code> at it, and the layout comes to life as content arrives. Folder names are matched case-insensitively, so a manually-created <code>Working/</code> is reused rather than shadowed by a new <code>working/</code>.</p> <p>A ZDDC project mirrors the natural lifecycle of an engineering deliverable: <strong>drafted in private, lined up for issue, formally exchanged, kept as record</strong>. Each canonical folder maps to one of those stages, so file location alone tells you where a document is in its lifecycle. An operator only needs to create one file — a <code>.zddc</code> in an empty directory — and the rest of the layout populates as work happens.</p>
<div class="code-block"> <div class="code-block">
project/ project/
.zddc ← the only file an operator must create .zddc ← the only file an operator must create
working/ ← user-owned drafting workspace; mdedit
for markdown, browse for uploads. working/ ← Where staff draft. Each person has their own
Each authenticated user with access subfolder here (named by email) and works on
sees a virtual <em>&lt;your-email&gt;/</em> documents in private until they're ready to
subfolder; first write materialises it. issue. Markdown opens in mdedit; arbitrary
staging/ ← outbound-transmittal preparation. A files are dropped in via the browse tool.
transmittal-named mkdir here also
creates a same-named drafting folder staging/ ← The "about to issue" lane. A folder in here
under working/ as a sibling space. declares a planned outbound transmittal: its
reviewing/ ← virtual cross-reference of in-progress name carries the planned issue date and tracking
review responses; never on disk. number, so anyone can see what's queued up and
Notes live in working/&lt;rs-name&gt;/. when. Files in staging are the finalised set;
archive/ ← formal record of issued/received drafting still happens in working/.
transmittals, organised by party.
{party-name}/ ← one per counterparty AND one for reviewing/ ← One place to see everything we owe a response
ourselves; self-folder is symmetric. on. Each row pairs an inbound submittal we've
mdl/ ← Master Deliverables List for that accepted with our in-progress response draft —
party. Visiting the folder opens the saves staff from hunting across per-party
table editor (<code>mdl.table.html</code>). folders to find what's open.
Default schema is built into the
server; per-party overrides via a archive/ ← The permanent record. Everything we've ever
tables: { mdl: ./mdl.table.yaml } formally exchanged with a counterparty lives
entry in the party's .zddc. here, organised one folder per party.
incoming/ ← that party's drop point. Auto-own {party-name}/ ← One per counterparty; one for ourselves.
.zddc grants the creator of each new We treat our own organisation as just another
subfolder full control. party so internal deliverables get the same
received/ ← permanent record of incoming we've tracking treatment as external ones.
accepted (WORM — write-once read-many). mdl/ ← The party's Master Deliverables List —
issued/ ← permanent record of what we sent to what they're going to produce, planned and
that party (WORM). 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.
</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>&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><strong>5-step transmittal workflow:</strong></p> <p><strong>5-step transmittal workflow:</strong></p>
<table> <table>