You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The maintainer directive on #2716 puts auditable second only to "the best agent loop":
every tool call's authorization decision is a value with a rule-by-rule trace (Decision + Trace), reconstructable after the fact from the journal
PR #3281 landed the decision as a value. The trace is not there. AuthzDecision::Deny { reason: String } carries one prose sentence, so "why was this denied" is answerable only if the gate author remembered to write a good sentence — and "which rules were evaluated, which matched, and which one decided" is not recoverable at all.
It was left out of #3281 on purpose rather than stubbed: a Trace type with nothing reading it is the unwired-field expedient CLAUDE.md forbids. This issue is where it gets a consumer.
What to build
An AuthzTrace — an ordered list of evaluated rules, each with its identifier, whether it matched, and what it contributed. resolve.ts's Trace shape in oxagen-platform is the reference; copy the shape, not its defects.
AuthzGate::check returns the trace alongside the decision. Keep the Result<_, AuthzEvalError> split exactly as it is — the trace is not an excuse to soften an evaluation failure into a partial answer.
A consumer, or this issue is not done. The natural one is the journal: emit the (principal, contract, decision, trace) triple so a host can attribute every effect after the fact. That means an AgentEvent variant, which under invariant fix(config): drop duplicate vertex/bedrock providers (unbreak cargo test) #10 must declare what reads it in crates/stella-protocol/src/event/consumers.rs — the row is part of the work, not a follow-up.
Constraint that shapes the design
Invariant #2: no I/O in the engine. Policy resolvers must stay pure functions over prefetched data, returning the decision plus the trace as a value. That is what makes "why was this denied" a thing a test asserts on rather than a log line somebody greps.
Invariant #3: the trace must not become an egress hole. If it ever rides a telemetry path, rule identifiers are fine and tool inputs are not — crates/stella-store/src/content_free.rs is the reviewed allowlist and a new key there needs a human to answer "is this content?".
Witness
A gate with three rules, of which the second denies: the emitted trace names all three, marks the second as deciding, and survives a serde round trip.
The maintainer directive on #2716 puts auditable second only to "the best agent loop":
PR #3281 landed the decision as a value. The trace is not there.
AuthzDecision::Deny { reason: String }carries one prose sentence, so "why was this denied" is answerable only if the gate author remembered to write a good sentence — and "which rules were evaluated, which matched, and which one decided" is not recoverable at all.It was left out of #3281 on purpose rather than stubbed: a
Tracetype with nothing reading it is the unwired-field expedient CLAUDE.md forbids. This issue is where it gets a consumer.What to build
AuthzTrace— an ordered list of evaluated rules, each with its identifier, whether it matched, and what it contributed.resolve.ts'sTraceshape in oxagen-platform is the reference; copy the shape, not its defects.AuthzGate::checkreturns the trace alongside the decision. Keep theResult<_, AuthzEvalError>split exactly as it is — the trace is not an excuse to soften an evaluation failure into a partial answer.(principal, contract, decision, trace)triple so a host can attribute every effect after the fact. That means anAgentEventvariant, which under invariant fix(config): drop duplicate vertex/bedrock providers (unbreakcargo test) #10 must declare what reads it incrates/stella-protocol/src/event/consumers.rs— the row is part of the work, not a follow-up.Constraint that shapes the design
Invariant #2: no I/O in the engine. Policy resolvers must stay pure functions over prefetched data, returning the decision plus the trace as a value. That is what makes "why was this denied" a thing a test asserts on rather than a log line somebody greps.
Invariant #3: the trace must not become an egress hole. If it ever rides a telemetry path, rule identifiers are fine and tool inputs are not —
crates/stella-store/src/content_free.rsis the reviewed allowlist and a new key there needs a human to answer "is this content?".Witness
A gate with three rules, of which the second denies: the emitted trace names all three, marks the second as deciding, and survives a serde round trip.
Refs #2716, #2701, #3281.