The audit chain
Each receipt links to the one before it by hash, so the log is tamper-evident: change an old receipt and every later link breaks.
Every gated action writes an Action Receipt. Within a session, each receipt records the hash of the one before it, so the receipts form an ordered chain. You can re-walk that chain offline and prove no past entry was altered, dropped, reordered, or inserted. The local chain is the source of truth; the cloud holds only a copy.
Why it matters
A single receipt proves you authorized one action under your policy. It says nothing about its neighbors: alone, it cannot tell you whether an earlier action was quietly deleted or a fake one was slipped in afterward. Chaining the receipts closes that gap and gives the whole log three properties.
- Ordering —
seqplus the back-reference fix where each action sits relative to the others. - Completeness — a missing receipt leaves a gap the next link cannot bridge.
- Tamper-evidence — editing a past receipt breaks every link after it, so anyone re-verifying the chain detects the change.
The chaining fields
Three fields in a receipt’s content carry the chain. See Action receipts for the full schema.
- session_idstring
- Groups receipts that belong to the same run or workflow. A chain is scoped to one session_id.
- seqnumber
- Zero-based position in the session. seq 0 is the first receipt; each later receipt links back to seq − 1.
- prev_receipt_hashstring
- The
action_hashof the receipt atseq − 1, ornullforseq 0. This is the link. - action_hashstringrequired
- This receipt’s own digest: BLAKE3 over its canonical bytes, 64 lowercase hex. The next receipt copies this value into its
prev_receipt_hash.
{
"content": {
"session_id": "vendor-payouts-2026-01-14",
"seq": 12,
"prev_receipt_hash": "0a77…42bd",
"action_hash": "9f2c…e1c0",
"action": { "verb": "payment", "tool_name": "stripe.transfer", "fields": {} },
"policy": { "rule_id": "payouts-floor", "decision": "require_approval" },
"trust_level": "L1"
}
}How a link is computed
Each receipt’s action_hash is a BLAKE3 digest over its canonical content (RFC-8785 / JCS), with the action_hash field itself removed before hashing. To extend the chain, the new receipt sets its prev_receipt_hash to the action_hash of the previous receipt and increments seq. Because prev_receipt_hashis part of the content that gets hashed, each receipt’s own action_hash commits to its predecessor.
# Each receipt's chain link, in order:
#
# seq 0 -> prev_receipt_hash = null (the first receipt in the session)
# seq n -> prev_receipt_hash = action_hash of the receipt at seq n-1
#
# action_hash = BLAKE3 over the canonical content (RFC-8785 / JCS),
# with the action_hash field itself removed before hashing.The BLAKE3 that hashes a receipt and the BLAKE3 that forms the chain link are the same implementation in the Rust core, called from Python, Node, and the browser WASM. A hash is byte-identical wherever it is derived, so there is no second implementation to drift.
Tamper-evidence
The chain is tamper-evident because prev_receipt_hash is part of the content that gets hashed. Four kinds of tampering all break the chain and are caught at verify:
- Alter — edit any field of a past receipt (say, lower a payment amount) and its canonical bytes change, so its
action_hashchanges. The next receipt’sprev_receipt_hashno longer matches. - Drop — remove a receipt and there is a gap: the following entry’s
prev_receipt_hashpoints at a receipt that is no longer there, andseqskips a number. - Reorder — swap two receipts and the
seqordering and back-references stop lining up. - Insert — splice in a forged receipt and it has no valid place in the sequence: its
prev_receipt_hashcannot match both sides.
Verification walks the chain from seq 0, recomputes each receipt’s action_hash, and checks that each entry’s prev_receipt_hashequals the previous receipt’s action_hash. The first seq that fails is the tampered entry, so you learn not just that the log changed but exactly where.
A receipt proves you authorized an action under your policy. It does not prove the action succeeded downstream. The chain adds that the log is internally consistent and unedited. It does not prove your copy is the only history: a chain you hold can still be a truthful prefix of a longer one, or a fork. For an external guarantee that two parties saw the same history, anchor the log in a transparency log.
Local chain vs. cloud mirror
The local BLAKE3-chained ledger is the source of truth. Gating, signing, and verification all run locally and need no API key. When you mirror a receipt with POST /v1/receipts, the cloud stores a non-authoritative copy and returns an { entry_hash, seq }; the call is idempotent on action_hash. The cloud holds no signing key, so it can never produce a receipt you did not sign — it can only echo what you sent.
Read the mirrored chain back with GET /v1/audit/chain; see the API reference for both endpoints. If the cloud copy and your local chain ever disagree, your local chain wins. To let an outside party confirm that a receipt sits in the same append-only history everyone else sees, use the public transparency log rather than the mirror.