The audit spine and the hash chain
One append-only events table per organization, each row sealed with a SHA-256 digest of the row before it, verifiable end to end by a single function.
Append-only, with no escape
events, auth_events, security_events and agent_actions have BEFORE UPDATE and BEFORE DELETE triggers that raise. There is no flag, role or setting that turns them off — the function is three lines and takes no arguments. Every business action writes an event: a phase change, a document received, a step satisfied, a case closed, a handover, a plan change.
The chain
On insert, a trigger takes the organization's latest row_hash under a per-organization lock, and sets the new row's prev_hash to it and row_hash to hex(sha256(prev_hash | seq | ts | org_id | event_type | entity_id | payload)), fields joined with a vertical bar. lt_verify_event_chain(org) walks the organization's events in sequence and returns whether it is intact, how many rows it checked, and the first broken sequence number if any.
Because the digest is a stated formula over exported columns, the chain can be re-verified outside the platform from the CSV in the everything-export. That is the point: trust the arithmetic, not us.
- What a superuser can do
- Read it. The stress suite shows a superuser's UPDATE and DELETE on an event are refused.
- What survives a tenant closing
- The spine. Files and access are removed; events are sealed and unreachable. Stated in the terms.
- Corrections
- Never in place. A correction is a new event that references what it corrects.
- · db/001, db/041, db/069, db/011
- · test/audit-chain.test.mjs, test/stress.test.mjs
- · /api/export/audit and the events.csv in /api/portability/export
File names refer to the codebase. A customer under agreement can be walked through any of them.