The Receipt Chain
Last reviewed · content updated
IntermediateWhat you'll learn
~16 min- Explain why a hash-chained receipt log catches an edited or deleted row, and why the same check alone cannot catch a chain rewritten end to end
- State what an anchor stored outside the receipt log adds, and where this training's own substrate keeps one
- Place SCITT, SLSA, and in-toto correctly against a house receipt schema without treating any of them as a ready-made standard
Before the detail — Decision: keep the record of what an agent attempted somewhere its own credentials cannot reach, and prove afterward that the record itself was not touched. Outcome: a receipt log a reviewer can trust without trusting the agent that produced it. Artifact: a hash-chained log plus an anchor stored outside it, verified by a checker that names exactly what moved. Status of what follows: reusable guidance — no receipt-format standard exists yet, and 1.4’s nine-field receipt schema already built this training’s own row.
Prompt first: ask whether the log can mark its own homework
For every agent that writes an action log, or has its actions loggedon its behalf, answer three questions with a command's output, not adescription:
1. Run the chain verifier right now. Does it report clean?2. In a copy, change one field in the middle of the log by hand, and run the verifier again. Does it name the row?3. In that same copy, delete the row entirely and recompute every hash after it so the chain is internally consistent again. Run the verifier a third time. What does it say - and is there anything outside the log itself that would still catch this?
If question 3 comes back clean, and nothing outside the log wouldcatch it, the log is evidence of a claim, not proof of history.The whole argument of this lesson is question 3: a hash chain proves nothing was changed without the chain noticing — it does not prove nothing was changed. The prompt orders the three checks deliberately, because the gap between “the log says it’s fine” and “an independent fact says it’s fine” is not obvious until the third check has passed.
A chain catches an edited row and a deleted row
Every row in this training’s substrate carries two hashes: prev_hash, copied from the row before it, and row_hash, computed from the row’s own content. Change one byte — the build’s own gate check does exactly this, editing a parameter inside a committed receipt row — and the verifier reports that the row’s row_hash no longer matches its content: this row was edited. Exit 1. Delete a row instead, and the next row’s prev_hash no longer matches anything, and its sequence number skips one: a row was edited, inserted, or removed. Also exit 1.
Both failures come from the same two checks — content hash and chain link — because editing and deleting are the same kind of damage from the chain’s point of view: a row that no longer connects to its neighbor. Neither requires a second tool or a manual comparison; one command names the row and says why.
What a chain alone cannot catch
Take that same log and, instead of editing one row in place, rewrite every row’s hashes from scratch after quietly changing a value near the top — a patient, complete forgery rather than a sloppy one. Run the verifier against it and it reports clean: receipts intact. A chain check only confirms that each row agrees with its neighbor, and a full rewrite makes every row agree with its new neighbor just as well as the old one did. The substrate’s own build gate proves this on purpose, in a copy, and the result really is the honest answer: chain-only verification cannot tell an untouched log from one that was rewritten in its entirety, because both are internally consistent.
The fix is a fact kept somewhere the rewrite cannot reach: an anchor. The verifier’s --anchor-file option reads the last verified chain-head hash from a file under control/anchors/ — outside receipts/, next to the kill switch — and checks the recomputed chain’s own ending hash against it. The same rewritten log that just verified clean now fails: its tail no longer matches the anchor, because the anchor was written before the rewrite happened and the rewrite had no way to reach it. This is, in the verifier’s own words, tamper evidence, not tamper prevention — anyone who can rewrite the file can recompute the whole chain. What an anchor buys is a second, separately-stored fact to check the chain against, not a wall around the first one.
A chain that can be silently rewritten and still verify clean is not a fact a contract dispute or an audit finding can be settled against — an anchor kept outside the log is what makes it one.
No write access is a property of the account, not a request
None of this holds if the agent that acted can also rewrite the log about what it did. This training’s substrate excludes receipts/, control/, and policy/ by name from the paths any agent action may target — an attempt aimed at any of them is denied by the envelope itself, not by a rule the agent is asked to follow. Appends to the chain also go through a single serialized writer under an exclusive lock, so two runs in flight at the same moment cannot each compute the same prev_hash and produce a chain that does not connect.
In this NON-PRODUCTION lab the anchor is outside receipts/, and a guarded action targeting it is denied by allowed_state_paths; that models separation but is not an operating-system credential boundary. Production still requires separate credentials or append-only storage for the log and anchor.
The joint guidance Careful adoption of agentic AI services (2026-04-30) asks for more than this lab models: it calls for agents to run “in enclaves with no write access to logs,” and for organizations to “quarantine requests to delete logs or audit records until reviewed and approved by a human” — a request to delete a log is itself a signal to route to a person, never a call an agent’s own tooling gets to satisfy on its own. This training does not build the pipeline that carries a log to somewhere the agent truly cannot reach at scale — Zero Trust 5.1 (a separate training in this series) routes and retains that estate-wide, and Operating in Production 5.2 depends on a retention window set elsewhere. What this lesson owns is narrower: the policy-level deny this lab enforces, and the honest line between that and full custody.
The verifier needed a patch too
The verifier is itself a small program, and small programs have bugs. cosign — a signing tool much of the supply-chain-attestation world already runs — shipped version 3.1.3 on 2026-08-06 to fix, in its own advisory’s words, “a verification bypass vulnerability”: the tool whose entire job was catching a forged signature had a way to be fooled about one. Treat a chain verifier the same way — review it like code, because it is code, and a verifier nobody has re-checked recently is one more thing quietly trusted past its own warranty.
No format built specifically for an action receipt exists yet. SCITT (a transparency-log standard for supply-chain claims, RFC 9943, June 2026) comes closest in shape: a registry that confirms a policy is met before it records a signed statement. It is built for software artifacts moving through a supply chain, not for what an agent attempted at 19:41 on a Tuesday, so it needs adapting rather than adopting. SLSA (a framework that attests how software was built) says nothing about what a running agent did, which is the wrong shape entirely.
in-toto’s Statement format, wrapped in a DSSE envelope (a generic container for a signed statement), is the closest usable shell, and it still needs a predicate someone writes for “this was an agent action,” which is exactly what 1.4’s nine fields already are, in this training’s own vocabulary.
Stop and escalate when the chain verifier reports a tamper and no independent anchor exists to say which version of the log is the real one — that is not a build to quietly fix; it is a possible incident in its own right, and it routes to whoever owns the receipt store’s custody before any other action on that agent runs again.
A quarterly review recomputes an agent's entire receipt chain from a backup, and every hash checks out clean. A teammate calls this proof the log was never tampered with. What is missing from that conclusion?
Practice status — action-receipt formats, commercial and federal
| Practice | Status | Also called |
|---|---|---|
| SCITT (RFC 9943, June 2026) | reference-shop | supply-chain transparency log; signed-statement registry |
| SLSA provenance | reference-shop | build-provenance attestation |
| in-toto Statement + DSSE envelope | reference-shop | signed-statement format |
Scale: required | common baseline | strong optional | reference-shop (seen only at organizations that publish their own practice) | emerging
Key takeaway
A hash-chained receipt log catches the two kinds of tampering an editor or a deleter can attempt in the moment: an edited row breaks its own hash, a deleted row breaks the next row’s link. Catching a full, patient rewrite takes a second fact kept where the rewrite cannot reach it — an anchor under control/anchors/, never inside receipts/ — and even the tool that checks all of this needs the same discipline any other code gets, which is exactly what cosign’s own advisory proved about itself. 5.2 turns a chain verified this way into an answer to a specific question: what did the agent do, and when.
LEADERSHIP DECISION every agent's action log gets a verifier run on a schedule AND an anchor stored somewhere the agent's own credentials cannot reach - one without the other is unfinishedPRACTITIONER ACTION run the verifier with --anchor-file before trusting any query against a receipt chain, and route a failed verification to whoever owns the receipt store, not to a rerunSUCCESS MEASURE zero receipt chains in production without a matching anchor on file - an audit finding avoided