Commit graph

5 commits

Author SHA1 Message Date
1e0e403f1e feat(zddc): retire defaults.zddc.yaml; .zddc.zip is the policy carrier (phase 6)
Completes the migration. The embedded per-depth tree (internal/zddc/defaults/)
is now the sole source of the shipped baseline; defaults.zddc.yaml is deleted.

  - EmbeddedDefaults() assembles the tree (no yaml). show-defaults now emits a
    .zddc.zip (per-depth, "*" wildcard members) via EmbeddedDefaultsZip() —
    operators redirect it to <ROOT>/.zddc.zip (or any directory) and edit/add/
    delete individual members.
  - Dropped EmbeddedDefaultsBytes; reworked the dumpable test to validate the
    emitted zip; removed the now-redundant tree-vs-yaml oracle (the Layer-2
    matrix is the ongoing behavioral guarantee, and it stays green).
  - Swept stale "defaults.zddc.yaml" comment references to the embedded tree.
  - GRAMMAR.md §1/§6 updated: .zddc.zip is a policy bundle mountable at ANY
    directory (subtree mount; inherit:false + acl.inherit:false = island); the
    shipped baseline is the embedded bundle at the root.

Net of the 6-phase migration: policy is per-depth .zddc files in a .zddc.zip
that an operator can drop at any level to override the cascade; the engine
(Assemble + the unchanged walker) enforces it. Full Go suite + matrix green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:35:21 -05:00
7d462ab7a4 test(zddc): update internal/zddc suite for flat-peer layout
Repoint default-tool/history/canonical-folder/auto-own/virtual/declared,
role-grant, and WORM-zone expectations to the top-level peer layout: archive
is now blanket-WORM (DC = rc there), the workspace/register peers carry the
DC grants directly, and incoming/working/staging/reviewing/mdl/rsk/ssr are
physical peers. ensure_test repointed to top-level paths + the virtual-reject
test inverted (peers are physical now).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:47:04 -05:00
f196205622 refactor(audit): pre-release cleanup pass
Single audit pass that removes pre-release back-compat, consolidates the
admin-policy decider, and fixes the .zddc write path.

Field removal — acl.allow / acl.deny:
- Drop ACLRules.Allow / Deny struct fields and mergeLegacyACL().
- Remove walker / lookups / validate / decider branches that read them.
- Migrate every test fixture (YAML strings and ACLRules struct literals)
  to acl.permissions: { principal → verb-set }.
- Rewrite both bundled Rego policies (access.rego, access_federal.rego)
  to traverse level.acl.permissions; rewrite parity-test helpers.
- Update create-project form (profile page) to collect permissions
  instead of allow/deny lists.

Admin decider consolidation:
- Delete zddc.CanEditZddc — strict-ancestor rule retired. Subtree admins
  own their own .zddc; the policy decider's IsActiveAdmin short-circuit
  is the single bypass site.
- Migrate tablehandler.ServeTable to AllowActionFromChainP — closes the
  same Forbidden bug already fixed for /browse.html.
- Drop AccessView.EditableParentChoices and treeEntry.CanEdit (always
  true after the retirement). Profile page renders AdminSubtrees
  directly for both lists.
- Drop the excludeLeaf parameter from AdminLevelInChain /
  IsAdminForChain — no production caller passed true.

Dead code removed:
- policy.AllowWriteFromChain (zero production callers, zero tests).
- zddc.AllowedWithChain (zero production callers; tests deleted).

ModeStrict retirement — federal posture is OPA-only:
- Delete cascade_mode.go / cascade_mode_test.go and the ModeStrict
  branches in cascade.go and acl.go.
- Drop --cascade-mode flag, CascadeMode config field, and the
  InternalDecider.Mode field.
- Drop the mode parameter from every cascade helper:
  GrantedVerbsAtLevel, AllowedAction, EffectiveVerbs,
  EffectiveVerbsRange, RoleMembers, MatchesPrincipal,
  MatchingPrincipals, WormZoneGrant, PolicyChain.VisibleStart.
- Strip cascade_mode from /.profile/config and
  /.profile/effective-policy responses.
- Refresh README / ARCHITECTURE.md to describe federal posture as
  "deploy OPA with access_federal.rego" (NIST AC-6); the bundled Rego
  is the parent-deny-is-absolute variant. The in-process Go evaluator
  implements only the commercial cascade.

Legacy redirects + .admin.css fallback:
- Drop /<dir>/.zddc.html → ?file=.zddc redirect and its test.
- Drop ?zip=1 retired comment + legacy test (handled by the
  .zip virtual-URL path; covered by TestServeSubtreeZip).
- Drop .admin.css fallback in profile_assets.go — only .profile.css now.
- Refresh stale "retired" / "back-compat" / "legacy" comment markers.

.zddc write path fix:
- Dispatcher: route only GET/HEAD on .zddc URLs to ServeZddcFile; carve
  .zddc out of the dot-prefix guard so PUT/DELETE/POST reach
  ServeFileAPI. Before this, .zddc writes 405'd at ServeZddcFile and
  the YAML editor's save flow had no live path.
- ServeFileAPI.resolveTargetPath: same .zddc-leaf carve-out so the file
  API accepts the path; intermediate dot dirs (.zddc.d/) stay reserved.
- Listing: compute Writable per-file with ActionAdmin for .zddc
  (matches the file API's gate) instead of ActionWrite for everything.
- Virtual .zddc placeholder: compute Writable via the same
  parentActiveAdmin || ActionAdmin path. Was always false before.
- browse YAML editor canSave: exempt virtual .zddc — the synthetic
  body is designed to materialize on PUT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 16:28:07 -05:00
2de2fdf92c refactor(zddc): worm: is a list of principals, not a {principal: verbs} map
Per design feedback: the verb string in a worm: entry was always
effectively "cr" (the key's whole job is to restore write-once-create
inside the locked zone, and you need read to see what you filed), so
spelling it out per-entry was redundant. worm: is now just a list of
principal patterns — email-globs, @role:name, or bare role names —
and every listed principal gets read + write-once-create. An empty
list ([]) still marks the WORM zone with no create-capable
principals.

Changes:
  - ZddcFile.Worm: map[string]string → []string
  - mergeOverlay: concat-dedupe (a deeper .zddc adds controllers);
    mergeStringSlicePreserveEmpty keeps `worm: []` non-nil through
    the overlay so it still marks the zone
  - WormZoneGrant: walks the list, grants VerbsRC to each matching
    principal; result is always ⊆ {r, c}
  - ValidateFile: validates each entry as an email-glob (role refs
    skipped — validated by the role machinery)
  - defaults.zddc.yaml: received/ and issued/ carry `worm: []`
  - tests updated to the list form (worm_test.go, fileapi_test.go)

All Go tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:40:15 -05:00
918f330a6f feat(zddc): WORM as a cascade key (worm:), retiring hardcoded path predicates
WORM (write-once-read-many) is no longer a special folder type keyed
off the literal names "received"/"issued". It's a cascade key —
`worm:` on any directory's .zddc — with the ACL-shaped semantics the
user described.

Schema:
  worm:
    "doc-control@example.com": cr   # email-glob or @role:name → verbs ⊆ {r, c}
  # an empty map ({}) is a WORM zone with no create-capable principals

Effect inside a WORM zone (any cascade level declares worm:), applied
AFTER the normal cascade ACL and BEFORE the admin escape hatch:
  - w / d / a stripped for everyone
  - c survives only via the worm: map
  - r survives via the normal ACL OR the worm: map (so a document
    controller who isn't in the project ACL still gets read+create)
  - worm: grants UNION across the cascade — deeper .zddc can name
    more controllers
  - admins (root / subtree) bypass entirely — handler does the
    IsAdmin check before the policy evaluator

defaults.zddc.yaml: archive/<party>/received and archive/<party>/issued
carry `worm: {}` (WORM zone, no controllers — the deployment names
its document controller by adding a deeper .zddc with
`worm: {<principal>: cr}`). The canonical convention is unchanged;
the difference is an operator can now mark any directory WORM, or
rename received/issued, without a code change.

Removed (hardcoded path predicates, superseded by the cascade walk):
  zddc.IsWormPath
  zddc.WormFolderLevelIndex
  zddc.splitPathSegments  (only IsWormPath used it)
Kept: zddc.WormMask (generic verb-set primitive), zddc.VerbsRC.

New:
  zddc.WormZoneGrant(chain, email, mode) → (verbs, inWormZone)
    Walks the chain for worm: declarations; unions the principal's
    grants masked to {r, c}.
  policy.InternalDecider.Allow: WORM block rewritten to consult
    WormZoneGrant instead of IsWormPath/WormFolderLevelIndex.
  ValidateFile: worm: keys validated as email-glob (or @role:name);
    values validated as verb strings ⊆ {r, c}.

Tests:
  - new worm_test.go covers the embedded convention, operator-granted
    controller, w/d masking, cross-cascade union.
  - special_test.go's TestIsWormPath / TestWormFolderLevelIndex
    retired; TestWormMaskStripsWDA kept.
  - fileapi_test.go's WORM tests updated: the doc-controller grant is
    now `worm: { _doc_controller: cr }` at issued/.zddc, not
    `acl.permissions: { _doc_controller: cr }`.
  - federal-parity and admin-bypass tests unchanged — the WORM mask
    still strips w/d/a and admins still bypass.

All Go tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:29:11 -05:00