Skip to main content

The delegation-chain audit model

The problem​

"Who authorized this tool call, and through which agents did the authority travel?" should be a query, not a log-correlation project across four services. That requires two things most audit logs don't guarantee together: that every decision is actually recorded, and that the recording can't be reordered relative to the decision it describes.

Committed before dispatch, not logged after​

The PDP's allow/deny is written to the append-only Audit Log and committed before the enforcement point is told the outcome — not in request teardown, not asynchronously afterward. This closes a specific race: if the commit fails, the call is treated as a deny (AUDIT_UNAVAILABLE, not recoverable) rather than dispatched with a promise to log it later. A privileged action and its audit row can never diverge, because the row exists before the action is allowed to happen.

The Token Exchange Service reinforces this from the other side: it mints a narrowed token only after re-checking that a committed allow exists for the decision it's citing — it runs in a separate request and can only ever see committed rows, so there's no window where a token is minted for a decision that turns out not to have been recorded.

Why the chain query is O(1), not a walk​

Each hop's token carries the signed action_id of the allow that minted it. When the next hop's decision is recorded, that row is written as parent.chain + [this hop] — the full chain is assembled at write time, hop by hop, not reconstructed at query time. Answering "what's the full delegation chain for this action" is then one primary-key read plus a join to the originating consent, at any depth. See Delegation-Chain Query API for the exact query.

A link that can't be established is recorded as a break, not silently dropped — a chain either reports complete: true or names exactly where it broke and why (an untraceable parent, a parent whose own chain was already broken, a pre-audit legacy token). Completeness is a property you can query, not an assumption.

The same rows answer a second question: did we drift​

The rule that decides whether an edge is allowed (services/edge_rules.py) is the same rule the CI Drift Action runs offline against a recorded test trace — not a reimplementation that could disagree with the runtime. Drift detection and delegation audit are two reads of one append-only source, which is why "drift caught in CI vs. only in production" is a meaningful, comparable metric rather than two different notions of correctness.

Where to see it​