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