iso-compliant's audit log is a hash-chained ledger: every public.audit_logs row stores its own SHA-256 hash, computed over the concatenation of the previous row's hash plus the canonicalised current row payload. Any retroactive edit to a past row breaks every subsequent hash and is detectable by a single chain-walk verification.
The chain head — the hash and chain_seq of the most recent row — is the auditor's verification anchor. The evidence-pack export (POST /v1/evidence/export, mirrored at app/dashboard/evidence/_actions.ts:generateEvidencePack) bundles the head into the manifest so an auditor receiving the manifest can independently re-walk the chain back to a known historical anchor and confirm no row was edited in between.
The hash function is SHA-256 — chosen for ubiquity, hardware acceleration, and SOC2 / ISO 27001 acceptance. The canonicalisation is the same recursive-sort algorithm used for the evidence-pack manifest (see canonicalise() in both apps/api/src/routes/evidence.ts and app/dashboard/evidence/_actions.ts) so an external auditor can re-canonicalise the row payload byte-for-byte.
The chain is per-tenant — every tenant has its own chain head — so a tenant's auditor never sees another tenant's hashes. The audit-write helper at apps/api/src/lib/audit-log.ts (fireAudit) writes every guarded route call into the chain with the canonical request + response hash.
The trust model: this hash chain proves "no row was edited after it was written"; it does not prove "this row was written at the claimed timestamp" — for that, the chain head can be periodically anchored to an external timestamping authority (RFC 3161) or to a blockchain (Bitcoin OP_RETURN, Ethereum log). Today iso-compliant does not anchor externally; the chain-head publication is the trust boundary.