Make broker authorization fail closed and add reusable provenance primitives #261
Labels
No labels
burndown-2026-06
sunday-sprint
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/umbra#261
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Outcome
Make cli-guard's reusable authorization defaults fail closed and define provenance primitives that higher layers can use without embedding Forgejo or CoilyCo policy.
Parent program: coilysiren/inbox#280
Boundary
Ward owns whether a Forgejo issue or comment is admitted for automation. cli-guard does not sanitize prompts and must not hardcode organization names, bot accounts, email domains, or Forgejo-specific collaborator policy.
cli-guard can still make omitted authorization and missing provenance difficult to misuse.
Current evidence
WriteOpswhen the caller supplies no tighter policy.Scope
Fail-closed broker policy
Reusable provenance
Sandbox claims
Acceptance
Codex, via advisor surface
pre-commit run --all-fileson main: README cap, SVG trailing-whitespace, golangci #298Landed on
mainas8ec7a0c.make testandpre-commit run --all-filesare both green on that tree.Fail-closed broker policy
The zero value was worse than the issue's wording suggested: both halves failed open, not just the owner one.
Ownersempty meant any owner, and a nilOpsmeantWriteOps, soPolicy{}was simultaneously the easiest policy to construct and the most permissive one available.Ownersempty now denies, unless the newAnyOwneropts in by name. This is the "deliberately named opt-in" the scope asked for. An empty owner is refused even under it.Opsempty now denies.WriteOpsis passed explicitly for the full tier.Policy.Validateis new, so a consumer fails at startup rather than at its first refused request.Caller audit result: the only callers in this repo were tests. Every one is migrated to an explicit
testPolicy(). Ward is the out-of-tree caller and it pins a commit, so per the AGENTS.md contract ("downstream bumps are the consumers' job") the migration lands there on bump. Filed as ward#1701 so the break is not discovered by a green build going quietly permissive.Reusable provenance
pkg/provenanceis new:Envelope{Actor, Source, SourceID, ContentHash, ObservedAt, Verification}, transport-neutral and policy-free. Every field is opaque, so nothing names a forge, organization, bot account, or email domain.The design rule is that ignorance never reads as trust:
VerificationisUnknown, deliberately distinct fromUnverified. Never running a check and running one that came up short are different facts, and collapsing them is how a missing check becomes a passing one.Completenames every missing field at once rather than the first.Trustedrequires a complete envelope andVerified, and is documented as an input to the consumer's trust decision rather than the decision.CoversContentre-hashes the bytes in hand, so the claim covers the content rather than an earlier revision of the same object.Sandbox claims - the scope item was based on a stale premise
Worth stating plainly, because it changes what "done" means here. The issue says:
There is no sandbox in this repo. A search across the Go tree for
sandbox|landlock|seccomp|unshare|bwrap|nsjail|chroot|jailreturns three hits, all of them comments inexamples/treebuilderssaying umbra is not one:So nothing degrades, because there is no isolation to degrade. Making a fallback "visible to callers and audit logs" has no code to attach to. The honest deliverable was the documentation half, and
docs/provenance.mdnow separates the four things a caller can conflate: command-construction safety (pkg/policy), execution isolation (none, stated outright), provenance, and application trust policy. That discharges "document which operations and platforms are actually isolated" with the true answer rather than an inventory of an absent feature.This also means #302 should not expect to find a sandbox instance to classify. Its "one other known instance" inherits the same stale premise from this issue. Noting it there.
Acceptance
TestPolicyFailsClosedacross five under-declared shapes.TestPolicyFailsClosed,TestPolicyAnyOwner,TestEachMissingFieldIsNamed,TestOnlyVerifiedIsTrusted.docs/provenance.mdplus the new fail-closed section indocs/broker.md.pkg/provenancenames one.Drive-by, called out rather than buried
TestServerRejectsProtocolMismatchcould not run on darwin at all. The socket path built fromt.TempDirembeds the test's own name, which on a 49-characterTMPDIRoverruns the 104-bytesun_pathlimit and surfaced as an opaquebind: invalid argument. Fixed with a short scratch dir plus an explicit budget check, so a future overrun names the limit instead of hiding behind errno. This is what was flagged from #298.Angie, engineer seat
Correction to the comment above: the consumer migration is ward#1674, not ward#1701. I wrote the number before filing.
Angie, engineer seat