refactor(policy): drop strict-ancestor rule for .zddc edits

The rule said: an admin granted in /<dir>/.zddc can edit deeper
.zddc files but NOT the one that grants their own authority.
Intended to prevent self-elevation, peer-addition, and delegator-
removal.

Three problems:

- "Add peers" isn't an attack — it's the common collaboration case.
  Project creator can't grant a teammate access without bothering a
  super-admin every time.
- "Remove the delegator" doesn't work. Root admin authority lives
  in the ROOT .zddc and cascades down regardless of what's in
  /<dir>/.zddc; subtree admins can't touch it.
- "Self-elevation" within a subtree is meaningless. They already
  have rwcda there.

Replacement model: admins in /<dir>/.zddc OWN /<dir>/ and everything
beneath, including the .zddc itself. They can add collaborators,
modify ACLs, even remove themselves. Self-removal is a recoverable
footgun — root super-admins always retain authority via the root
cascade and can restore.

What stays:
- The admins: field as a load-bearing key (drives IsActiveAdmin
  + sudo-style elevation + WORM bypass).
- Bootstrap via root .zddc hand-editing.
- IsAdminForChain(chain, email, excludeLeaf bool) signature —
  ModeStrict / NIST AC-6 deployments can still opt into the strict-
  ancestor walk if they need it.

Tests flipped to match the new contract; ProjectCreate flow now
gives the creator real control over their project root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-05-18 10:47:04 -05:00
parent b80b11c99f
commit df19a63853
3 changed files with 32 additions and 22 deletions

View file

@ -157,26 +157,34 @@ func TestInvariant_ElevatedSubtreeAdminBlockedOutsideScope(t *testing.T) {
// ── Invariant 4 — .zddc strict-ancestor self-elevation prevention ─────────
func TestInvariant_SubtreeAdminCannotEditOwnSubtreeZddc(t *testing.T) {
// alice's authority comes from /Project-1/working/.zddc. She must
// NOT be able to edit that file — strict-ancestor rule prevents
// her from adding peers, removing the delegator, or otherwise
// self-elevating.
// Strict-ancestor was retired — a subtree admin owns their .zddc.
// These tests pin the post-change contract: an elevated admin
// granted in /<dir>/.zddc CAN edit that file (add collaborators,
// adjust ACLs, even — accidentally — remove themselves). Footgun
// is recoverable via super-admin restore.
func TestInvariant_SubtreeAdminCanEditOwnSubtreeZddc(t *testing.T) {
cfg, _ := invariantsFixture(t)
p := zddc.Principal{Email: "alice@example.com", Elevated: true}
dir := filepath.Join(cfg.Root, "Project-1/working")
if zddc.CanEditZddc(cfg.Root, dir, p) {
t.Fatalf("subtree admin can edit own .zddc — strict-ancestor rule bypassed")
chain, err := zddc.EffectivePolicy(cfg.Root, dir)
if err != nil {
t.Fatalf("EffectivePolicy: %v", err)
}
if !zddc.IsAdminForChain(chain, p.Email, false) {
t.Fatalf("subtree admin lost authority to edit own .zddc — strict-ancestor wasn't supposed to apply")
}
}
func TestInvariant_SubtreeAdminCanEditDeeperZddc(t *testing.T) {
// alice's authority over Project-1/working/ should let her create
// or edit .zddc files in deeper subtrees (e.g., per-user homes).
cfg, _ := invariantsFixture(t)
p := zddc.Principal{Email: "alice@example.com", Elevated: true}
dir := filepath.Join(cfg.Root, "Project-1/working/eve@example.com")
if !zddc.CanEditZddc(cfg.Root, dir, p) {
chain, err := zddc.EffectivePolicy(cfg.Root, dir)
if err != nil {
t.Fatalf("EffectivePolicy: %v", err)
}
if !zddc.IsAdminForChain(chain, p.Email, false) {
t.Fatalf("subtree admin blocked from editing deeper .zddc")
}
}

View file

@ -1559,7 +1559,7 @@ body.is-elevated {
</svg>
<div class="header-title-group">
<span class="app-header__title" id="table-title">ZDDC Table</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-18 15:25:14 · fd4f03a-dirty</span></span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-18 15:46:59 · b80b11c-dirty</span></span>
</div>
</div>
<div class="header-right">

View file

@ -395,19 +395,21 @@ func AllowFromChainP(ctx context.Context, d Decider, chain zddc.PolicyChain, p z
// when (and only when) the caller actually holds elevated admin
// authority on this chain.
//
// Strict-ancestor rule for .zddc edits: action == ActionAdmin signals
// a .zddc write, and IsAdminForChain is called with excludeLeaf=true
// so the leaf .zddc's own admins entry cannot authorize editing the
// file that grants it. Other actions use the full chain walk.
//
// Use this entry point in write-path handlers (file API, plan-review,
// accept-transmittal). Read-path callers that don't need admin
// bypass can stay on AllowActionFromChain / AllowFromChain — they
// implicitly leave IsActiveAdmin=false.
// Strict-ancestor rule: NOT applied by default. A subtree admin whose
// admins: entry lives in <dir>/.zddc CAN edit that file — they own
// the directory and everything it grants. Footgun: they can also
// remove themselves from the admins list (recoverable: a super-admin
// always retains authority via the cascade from the root .zddc and
// can restore the grant). The prior strict-ancestor mode protected
// against peer-addition / delegator-removal but was always partial
// (deeper .zddc files were freely editable) and made the common
// case — "project creator wants to add a collaborator" — friction-y
// enough to be unusable. IsAdminForChain still accepts excludeLeaf
// for any caller that wants strict mode; the default path doesn't
// fire it.
func AllowActionFromChainP(ctx context.Context, d Decider, chain zddc.PolicyChain, p zddc.Principal, path, action string) (bool, error) {
excludeLeaf := action == ActionAdmin
isAdmin := p.Elevated && p.Email != "" &&
zddc.IsAdminForChain(chain, p.Email, excludeLeaf)
zddc.IsAdminForChain(chain, p.Email, false)
in := AllowInput{Path: path, Action: action, PolicyChain: chainToSerializable(chain)}
in.User.Email = p.Email
in.User.IsActiveAdmin = isAdmin