Audit integrity
Prove the audit log hasn't been altered with a sealed, tamper-evident hash chain.
Every decision AxioRank makes is written to an audit log. Audit integrity makes that log tamper-evident: finalized rows are sealed into a per-workspace hash chain, so any later edit, insertion, deletion, or reordering is detectable.
How sealing works
A background job (hourly, or on demand) seals finalized audit_logs rows into a
chain of checkpoints. Each checkpoint covers a time window and stores:
batch_hash: an ordered hash of the row hashes in the window,prev_hash: the previous checkpoint's hash (or a genesis value),checkpoint_hash:hash(prev_hash, batch_hash, from_ts, to_ts, row_count),merkle_root: an RFC 6962 Merkle root over the same rows, signed (Ed25519) into a tree head so a single row can be proven with an offline receipt (see Verifiable receipts).
Linking each checkpoint to the one before it forms a chain: change anything in a sealed window (or a checkpoint itself) and the recomputed hashes stop matching from that point forward.
Grace period
Rows are sealed only after a grace period longer than the approval-hold timeout, so a held call's decision is final before it's committed to the chain.
Verify the chain
GET /api/audit-integrity/verify (workspace member) recomputes every sealed
checkpoint from the live rows and walks the chain. It detects altered rows (batch
hash), inserted or deleted rows (row count + batch hash), reordering (the batch
hash is order-sensitive), and edited checkpoints (a forward-chain break). It stops
at the first break.
{
"ok": true,
"checkpoints": 184,
"rows": 51230,
"purgedCheckpoints": 12
}On a break, ok is false and broken carries the failing checkpoint:
{ "seq": 97, "reason": "batch hash mismatch" }.
POST /api/audit-integrity/seal forces a seal of any finalized rows immediately
(the same operation the hourly job runs), returning { sealed, seq?, rowCount? }.
Verifiable receipts
The chain above is something you verify against your own live rows. A receipt goes further: it lets a customer or an auditor verify a single action offline, with nothing but a public key, no AxioRank account and no trust in us.
A receipt proves three things:
- Membership: the audit row is in the sealed Merkle tree (an RFC 6962 inclusion proof).
- Integrity: that tree's root was signed by the log (an Ed25519 signed tree head).
- Provenance: who or what authorized the action, as a signed delegation chain (the operator who approved, the agent key, the upstream trace step).
Verify with the open-source package
The verifier is published as @axiorank/audit-verify (npm, zero dependencies) and as verify_receipt in the axiorank Python SDK. Read the whole thing in a few minutes and trust nothing else.
npx @axiorank/audit-verify receipt.json --jwks key.json
# ✓ leaf hash
# ✓ inclusion proof
# ✓ signed tree head
# ✓ delegation provenance
#
# VERIFIEDimport { verifyReceipt } from "@axiorank/audit-verify";
const result = verifyReceipt(receipt, jwks); // jwks pinned out of band
if (!result.ok) throw new Error(result.reason);from axiorank import verify_receipt # pip install axiorank[verify]
assert verify_receipt(receipt, jwks) is TrueEndpoints
GET /api/v1/audit/public-key: the JWKS (active and retired keys). Pin a key bykidout of band and verify against the pinned copy. Public.POST /api/v1/audit/verify-receipt: a convenience verifier. Not the trust anchor; the open-source package run against a pinned key is.GET /api/v1/audit/checkpoints/{seq}: a signed tree head by sequence number (logs:read). Pin the series over time to detect a retroactive edit yourself.
What a receipt does not prove
A verified receipt proves the action is in a signed, append-only log and was authorized as stated, unaltered since sealing. On its own it does not defend against a split-view attack (different signed histories shown to different parties); that needs gossiping tree heads to an external witness. Pinning the checkpoint series over time lets you catch a retroactive edit.
Retention and purges
When rows are purged under data-retention policy, their checkpoint
keeps its stored batch_hash and stays in the chain, so the forward chain still
verifies even though the underlying data is gone. Those windows are reported as
purgedCheckpoints: the integrity proof outlives the data.
Next steps
- Gateway API → Audit export: pull the underlying log over the API.
- Privacy & data rights: retention and erasure that the chain survives.