Change one record and the chain breaks. That includes us.
Every decision is hash-chained, and the signed checkpoint over that chain is held outside the deployment: your own archive, an auditor, a counterparty. Edit a decision afterwards and it stops matching the checkpoint they hold. A log is a file anyone with database access can edit. This runs on your hardware and we never receive a copy, so we could not edit it either. No chain catches someone restoring the data and the record together, ours or anyone else's. The copy your auditor holds does.
A service you deploy.
What one decision looks like
event_id evt_01J9Z4T2K8 event_type transfer_intent entity_id wallet_7f31c2 verdict rejected winning_rule daily_volume_exceeded matched_rules_count 3 signals.day_total_after_this 51420.00 signals.sanctions_hit false ruleset_code web3-wallet ruleset_version_code v7 (frozen) finished_at 2026-07-14T09:22:41Z duration_ms 20 chain_key web3-wallet prev_record_hash sha256:4f2a...c118 record_hash sha256:9d07...ab63 leaf_index 2841
Three of these fields do the work. winning_rule names the rule that decided, so the answer to "why was this blocked" is a rule name rather than a theory. matched_rules_count says three rules matched, so the winner was not the only one that saw this. ruleset_version_code points at a frozen, immutable version, so the rules behind this decision are the rules you can read today.
And duration_ms is there because someone always asks.
Three records, and the question each one answers
| Question | Stream |
|---|---|
| Who did what? | the operations audit — every operation, the caller behind it, what it touched |
| What changed in the data? | the changes audit — the row before and after, so an overwritten value is recoverable |
| What was decided? | the decision record above, one per evaluated event |
A reviewer's decision is recorded against the case, with the person and their note. If your deployment sends that decision back through the engine, it lands as its own event with its own record.
Where it goes
A rule sends it to your syslog collector in RFC 5424, or calls anything else over a webhook. The records stay in your Postgres.
You choose what gets it
Tamper-evidence is a property you switch on per table, and it applies from the moment a row stops changing.
Three things carry it out of the box. Both audits. Decisions, for live traffic rather than a backtest, because nobody needs to prove a rehearsal. And a decided review case: the write that decides it creates its record and freezes the row. Who decided this case and when is what a regulator asks for, and a later edit to that row is visible.
Beyond those, you declare it: our tables or your own, wherever a row stops changing. The condition is yours to write, so "settled" means whatever it means in your operation.
Proof that does not rest on our word
The checkpoint is a Merkle root over everything the deployment has recorded, signed at a set interval. It is small enough to send in an email, and it carries none of your data — only the proof.
The signing key is yours. The private half stays in your environment and never touches the database, because the threat here is someone editing rows in the database directly, going around the application. You hand the public half to your auditor and they verify without us in the room.
Verification runs over a scope and a period you choose, and reports what it found rather than a silent pass. Records that retention removed are reported as removed by retention, and told apart from records that should be there and are not.