From 465d2f605c828c4e8bbc3a7fd4f9af504cb5d65c Mon Sep 17 00:00:00 2001 From: ZDDC Date: Mon, 18 May 2026 09:17:44 -0500 Subject: [PATCH] feat(policy): IsActiveAdmin field + AllowActionFromChainP entry point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lays the rails for the consolidation refactor — the decider gains a single admin-bypass branch at the top of InternalDecider.Allow, and a new principal-aware entry point computes IsActiveAdmin from chain + Principal.Elevated. No caller uses the new path yet, so behavior is unchanged; lock-in tests stay green. AllowInput.User.IsActiveAdmin bool // caller-computed bypass flag AllowActionFromChainP(ctx, d, chain, p, path, action) (bool, error) The decider's branch: if input.User.IsActiveAdmin { return true, nil } is the ONLY admin escape hatch in the package. Strict-ancestor rule for .zddc edits is preserved inside AllowActionFromChainP via IsAdminForChain(chain, email, excludeLeaf=true) when action==ActionAdmin. Email-only entry points (AllowFromChain, AllowActionFromChain) leave IsActiveAdmin=false implicitly — they're for read-path callers that don't need admin bypass (directory listing, archive index, profile read endpoints). Next commits: migrate authorizeAction and plan-review's pre-flight to AllowActionFromChainP, then delete the scattered IsAdmin/ IsSubtreeAdmin/CanEditZddc early-outs. Co-Authored-By: Claude Opus 4.7 (1M context) --- zddc/internal/policy/policy.go | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/zddc/internal/policy/policy.go b/zddc/internal/policy/policy.go index 4fab3b3..52c26d2 100644 --- a/zddc/internal/policy/policy.go +++ b/zddc/internal/policy/policy.go @@ -73,6 +73,20 @@ import ( type AllowInput struct { User struct { Email string `json:"email"` + // IsActiveAdmin is true when the caller has admin authority on + // THIS chain AND has opted into admin powers for this request. + // The CALLER is responsible for computing it (it depends on the + // chain and on Principal.Elevated, both known at the call site); + // the decider consults it as a short-circuit at the top of + // Allow. This is the single bypass point — every write that + // should ignore WORM/ACL for an elevated admin flows through it, + // every read that should ditto. + // + // Callers using the email-only entry points (AllowFromChain, + // AllowActionFromChain) get IsActiveAdmin=false implicitly — + // they're saying "ignore admin bypass for this lookup." Callers + // that want the bypass use AllowActionFromChainP. + IsActiveAdmin bool `json:"is_active_admin,omitempty"` } `json:"user"` Path string `json:"path"` Action string `json:"action,omitempty"` @@ -219,6 +233,17 @@ func (d *InternalDecider) Allow(_ context.Context, input AllowInput) (bool, erro verb := actionVerb(input.Action) email := input.User.Email + // Single admin-bypass site. The caller has already verified that + // the principal (a) holds an admins: grant somewhere in this chain + // and (b) has opted into admin powers (Elevated). When both are + // true, any action is permitted — WORM zones included — preserving + // the human escape hatch for mis-filed documents. No other site in + // the codebase grants admin authority; every write that should + // bypass ACL/WORM flows through this one branch. + if input.User.IsActiveAdmin { + return true, nil + } + // WORM zone: a directory whose cascade declares `worm:` (see // defaults.zddc.yaml — archive//received and issued carry // `worm: {}`) is write-locked. Inside it, the effective verbs @@ -354,6 +379,31 @@ func AllowActionFromChain(ctx context.Context, d Decider, chain zddc.PolicyChain return d.Allow(ctx, in) } +// AllowActionFromChainP is the principal-aware entry point. Computes +// IsActiveAdmin from the chain + Principal.Elevated and threads it +// into AllowInput, so the decider's single admin-bypass branch fires +// 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. +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) + in := AllowInput{Path: path, Action: action, PolicyChain: chainToSerializable(chain)} + in.User.Email = p.Email + in.User.IsActiveAdmin = isAdmin + return d.Allow(ctx, in) +} + // cachingDecider wraps another Decider with a small per-decision cache. // Designed for the external-OPA hot path: a single .archive listing or // directory enumeration can hit the same (email, dir-policy) tuple