prev_hash, the
hash of receipt n − 1, with seq inside the hashed content. Change what one receipt says,
delete one from the middle, or swap two, and the chain no longer verifies at that point.
ctrlrun receipts --verify-chain reports the break by seq and by name.
What it detects
seq, a broken link between two, a gap where a seq is
missing, a duplicate seq, a head that does not match the last receipt, and an unchained
receipt where the chain expected one. Each names the seq so a reader can go and look at that
row rather than being told the log is bad.
What it does not prove
Alteration is not authorship. Receipts are not signed. The chain says the log was not edited after the fact; it says nothing about who wrote any receipt, and nothing here is evidence of origin. Signing is not in this version. It is not tamper-proof. The head lives in the same database as the receipts, so an administrator who can rewrite every row can recompute the chain and it will verify. What the chain raises is the cost of changing history while keeping it: rewriting one receipt means rewriting every receipt after it and the head. It does not detect erasing the end. Deleting the last receipts and rewinding the head is two statements and leaves a chain that verifies. An anchor outside the database would close that, and there is none. This is asserted by a test rather than argued, so the sentence cannot quietly become optimistic. It does not prove every action wrote a receipt. A receipt whose write failed leaves no gap inseq and is invisible to the chain by construction. The events log is where that is
reconciled.
Where it is checked
ctrlrun receipts --verify-chain, on either store backend, including through--store-url.ctrlrun verify’s G11, an altered receipt is detected, against your own configuration.- On Postgres,
put_receipttakes the head row’s lock first, so concurrent writers cannot interleave into a chain that then fails to verify.
What to do with it
Run--verify-chain where you already run integrity checks, and treat a break as an incident
about the store rather than about CTRLRun: the chain is the alarm, not the lock. If you need
evidence of origin rather than of integrity, sign or anchor the receipts outside the database
yourself; they are portable JSON, one object per line, for exactly that reason.
Next
- Receipts and evidence: what a receipt holds.
- Threat model: what remains open, in full.
- Get started · Why.