The incident
Earlier prototype. This page documents a demonstration built on the deprecated agent_governor, the predecessor of the current Constellation components. It is kept for the record; it is not the current demo or installation path. Start from Constellation, or see the archive.
A credential valid at check, expired at action. The classic check-then-act race condition, with a receipt that says no. Written for ops, SRE, and platform readers; the deeper descent is at A stale yes. Here, custody means the layer that controls whether a claim may become an operational premise at action time.
t=40 check OK — expires after 10s — t=50 expired — 1s — t=51 action REFUSED
What happened
consume_capacity arrives at the executoreffect_count=0The credential was valid when it was checked. It expired before it was exercised. The conventional path often either does not re-check at exercise time, or re-checks without preserving the original witness basis, typed bound, and downstream-verifiable receipt — so the expiry gets compared against the wrong clock, or nothing at all.
This is TOCTOU (time-of-check to time-of-use) where the racing entity is the credential's own freshness, not a competing writer. Logs see it after the fact; policy engines don't see it at all unless something hands them the gap as input. Nothing in the conventional path attests that the check was still in scope at the moment of the spend.
Why the conventional path says allow
default allow := false
allow if {
input.credential.status == "valid"
input.credential.role == "operator"
input.action == "consume_capacity"
}
The input asserts status: "valid". Nothing in the input says when that was true, or what clock measures “still”.
Both lanes are correct over what they were handed. The policy is right that the input it sees says “valid.” The custody lane is right that the input it sees has lapsed. The disagreement is about which input you should be handing the policy.
What the custody layer does instead
The premise (credential valid) and the action (spend capacity) are connected by a boundary check. The check verifies three things and refuses if any of them fails:
- the gap between observation (t=40) and exercise (t=51)
- on a typed clock basis — here
process_monotonicwithepoch=boot:demo-single-host, not wall time (wall time can move sideways under NTP and isn’t the basis the expiry was issued on) - against the bound the credential declares (10s)
When the gap exceeds the bound, the check emits a refusal receipt before any capacity is consumed:
Fields read from a run on 2026-07-29 against agent_governor at 485e557. The full artifact, with the raw JSON, is on the descent.
What the receipt buys you
- Reproducible refusal. Same inputs under the same receipt schema produce the same id — a content hash, not a log line. Run the demo today and you’ll get
3f8b93c1…. It wasdda5a1e5…before the schema gained fields; the id tracks the schema, the behaviour it attests did not change. - Stale under which clock. The basis is named:
process_monotonic, single epoch, start and end in ns. “The credential expired” with a named witness, not a vibe. - Walkable evidence chain.
governor why <id>walks from refusal → admission → permission check — and terminates honestly at any missing evidence. No silent inference of facts you don’t have. effect_count=0. Nothing was spent. The refusal happened before capacity moved. This is the field that distinguishes “we noticed” from “we blocked.”
Why your existing stack misses it
OPA / Rego: decides over the input you hand it. If you can feed it the gap and the bound, it can decide on freshness — but nothing in the standard OPA pattern establishes the custody of its own inputs. The policy is right; the input is stale; the disagreement isn’t a policy bug.
Audit logs: emitted after something happens. They describe behavior; they don’t gate it. A log entry that says “spend at t=51 after check at t=40” is a postmortem artifact, not a refusal.
TTL on the credential: the right shape, wrong place. A TTL the issuer enforces only works if the consumer rechecks at exercise time, against the same clock the issuer used. The whole custody story is making that recheck structural — impossible to skip and impossible to satisfy with the wrong clock.
Token introspection at the API gateway: closer, but typically against wall time and without a typed bound. It catches gross lapses; it doesn’t catch a 1-second overrun against a stated freshness contract, and it doesn’t emit a receipt the next system over can verify.
Run it
From a cold clone to the refusal above, about five minutes:
git clone https://github.com/unpingable/agent_governor.git cd agent_governor python3 -m venv .venv && . .venv/bin/activate && pip install -e . ./demo/refused-spend.sh # the incident above ./demo/opa-contrast.sh # the same incident, OPA’s view ./demo/interrogate.sh # walk the receipt chain
The demo’s exit code is the claim: 0 only if the refusal happened for the right reason and spent nothing. The receipt id is deterministic under a given schema version; today that is 3f8b93c1…, the one cited above.
agent_governor is the deprecated classic predecessor. It still runs, and this historical refusal still means what it says; new authority work lives in Constellation AG and governed execution in Constellation Docket. See the current Constellation documentation.
Where this is the wrong tool
If your incidents are cheap to diagnose after the fact, or your inputs are attested by construction, or the credential’s blast radius is small, the conventional stack is cheaper and you should use it. Custody trades steady runtime cost for fewer catastrophic incident-time costs — the same bet as TLS or structured logging. Limits spells out the rest.
If you only need an authorization check, use an authorization check.
for the formal-methods curious — the refusal class, machine-checked
The refusal kind standing_before_spendability_not_bounded is licensed by Admissibility.Freshness.expired_not_fresh:
theorem expired_not_fresh (h : ¬ (now ≤ expires + skew)) : ¬ Fresh now issued expires skew maxDiv
An observation whose exercise time is past its expiry is not Fresh. The receipt’s gap_ns > bound_ns is definitionally the theorem’s hypothesis — this refusal is the one the kernel licenses, not a discretionary denial. The theorem proves the class; the receipt attests the instance. Both are narrower than “proven safe” — see limits.