Documentation
The part that does not rest on trusting us.
An audit trail is only worth what somebody outside your company can confirm about it. Everything here is written so that they can confirm it without your help.
Verifying a seal yourself
Each day closes with a Merkle root over every event in it, and each checkpoint carries the hash of the checkpoint before it. Altering one record a year ago means recomputing every seal since — including the ones you have already been shown.
| Walk the chain | Settings → Ledger recomputes the links on every load and names the first day that does not match. Nothing is cached, so the page cannot show you a stale "all clear". |
| Verify one day | Recomputes that day’s root from what is stored now and compares it with what was recorded at seal time. It reports both the root and the event count, because a deletion changes one and an edit changes the other. |
| Prove one event | Produces an inclusion proof: the event’s leaf hash and the handful of sibling hashes that reproduce the day’s root. A few dozen bytes, checkable without reading the rest of the day and without trusting us. |
Days are sealed after a grace period, two hours by default. Events are buffered and batched, so one that occurred at 23:59:58 can land minutes later; sealing at midnight would leave it outside the root meant to cover it and every check afterwards would report tampering that never happened.
And the part that does not rest on trusting us
The chain above shows that nobody altered an event without also altering the checkpoint covering it. Both live in databases we run, so read strictly, that is a statement about our own bookkeeping — and a sceptical auditor will say so.
So each seal is also timestamped by an RFC 3161 authority that has never heard of us. The token says the checkpoint hash existed on that date, signed by a key we do not hold. Rewriting a year of history would now mean obtaining backdated tokens from a public authority, which is a different order of problem from editing two of our tables.
| It verifies offline | The token carries its own certificate, so the check needs the file and nothing else — no network, no account with us. Every evidence pack ships the tokens for the days it covers. |
| It is checked, not just stored | Settings → Ledger verifies each token against the checkpoint it is filed against. A token that attests to something else reads as a failure, not as a tick. |
| An outage never costs a seal | If the authority cannot be reached the day is still sealed and still chained; the anchor is added on a later pass. A public service being down must not leave a hole in your evidence. |
# Any RFC 3161 authority. The processor is the only process that stamps.
Ledger__TimestampAuthorityUrl=https://freetsa.org/tsr
Off until you give it a URL, like everything else here that reaches outside the deployment. Any public authority works, and several are free at this volume.
A copy you hold
Each newly sealed day is also sent to whichever channel you already pointed the digest at — Slack, Teams, a webhook of your own. The message carries the day, the event count, the Merkle root and the checkpoint hash. Keep it. It is the same seal in a place we have no access to at all, so a dispute later does not come down to two databases we happen to run.
Checking the whole ledger at once
The dashboard verifies one day at a time and the evidence pack carries instructions for an auditor. Neither answers the question an operator asks after restoring a backup or surviving an incident: is all of it still here, and does it still hash to what we published? One command recomputes every sealed day from the events actually in the store and compares each root with its checkpoint.
reldavi verify --tenant acme --control-plane "$RELDAVI_CONTROLPLANE" --clickhouse "$RELDAVI_CLICKHOUSE"
Evidence packs
What an auditor asks for is not a CSV. It is the records, the seals that prove they were not altered, the certificates for anything since destroyed, and a page explaining how to check all of it. One button produces the archive.
evidence-2026-08.zip
├── events/events.csv the records, as a spreadsheet opens them
├── events/events.json the same records, machine readable
├── ledger/checkpoints.json the Merkle seal for every day covered
├── destruction/certificates.json what was destroyed, and under which policy
├── manifest.json SHA-256 of every file above
└── HOW-TO-VERIFY.txt how to check all of it without us
Every file is digested into the manifest, so the pack is tamper-evident as a unit — it travels by email and gets opened months later by somebody who was not in the room when it was produced. Reports → Download produces one for any range; turning on monthly packs writes the previous month automatically, a few days in, so events that occurred at the end of the month and landed in the first minutes of the next one are inside it.
Legal hold
Retention deletes on schedule, and that is the feature. Then you are served notice of an investigation and have to preserve everything touching one counterparty — for longer than your policy, indefinitely, and only for that subset.
| A hold overrides the schedule | Placed on a matter, optionally narrowed to a resource type or a date range. Nothing it covers is archived, dropped or deleted while it stands. |
| It keeps the whole month | Retention drops months as a metadata operation, so a hold touching any part of one keeps all of it. Preserving more than asked is the right failure here; deleting around held records is not. |
| The certificate explains the gap | A month a hold spared is named on the destruction certificate with the hold’s reference, so a gap in the schedule of deletions reads as a decision rather than as a malfunction. |
| Lifting is recorded, not erased | A released hold stays listed with who lifted it and when. It is the only explanation for why those months outlived their window, and that is a question somebody asks years later. |
Placed and lifted from Settings → Destruction, by an administrator, with the person recorded on the row. The question afterwards is never what the policy was; it is who stopped the deletion, when, and on whose instruction.
Subject access requests
Somebody writes in and asks what you hold about them and what you have done with it. KVKK article 11 and GDPR article 15 give them the right, and give you thirty days. Today that answer is assembled by hand: somebody queries several systems, exports spreadsheets, redacts them and writes a letter. It takes days per request, it is error-prone, and getting it wrong is a fineable offence.
| Both halves of the question | What they did, and what was done to their records. "What do you hold about me and what have you done with it" is two questions, and a pack that answers one of them invites a second request. |
| Written for a member of the public | "shop.order updated" is accurate and is not an answer. Each line reads as a sentence — "Someone at the company changed your delivery address" — with the exact technical record beside it for anybody who wants to check. |
| It says what it does not contain | This is a record of changes, not a copy of every record. And values masked at capture stay masked, which the pack explains — that is not redaction applied to their request, those values were never stored in full. |
| In both languages, always | Whichever was asked for, the explanation page carries English and Turkish. A pack that arrives in a language the person cannot read has not answered them. |
| Making one is itself recorded | It appears in Who read the trail as an export, like any other. A regulator asking how you handled a request gets an answer rather than an assurance. |
Reports → Subject access request. The pack streams rather than building in memory, so a person with ten years of history is a slow download rather than a failed one.
The audit room
An external auditor asks to see your change history for a period. You have two options today and both are bad: give them a real account — a seat, a password, a leaver to remember, and your whole trail rather than the quarter they asked about — or email them spreadsheets, which stop being verifiable the moment they leave the building.
| The period is on the row, not in the link | An auditor who edits the dates in the URL is an auditor looking at the period you gave them. The link carries a lookup and a secret and nothing else. |
| The proof is on the same page as the records | Each sealed day in the period is verified while they read it — its Merkle root recomputed from the records in front of them. A spreadsheet cannot do that, and neither can a screenshot. |
| You can close it in one click | Which is why this is a stored grant rather than a signed token. An audit ends, or a link reaches somebody it was not meant for, and neither of those waits for an expiry. |
| You can see what they looked at | Every visit is counted, and every read appears in Who read the trail like any other. "What did the auditor actually see" is a question boards ask and almost nobody can answer. |
| Closing it does not erase it | The grant stays as the record that somebody had access at all. Deleting it would destroy the answer to "who could see this last March". |
Open one at Settings → Who read the trail. The link is shown once, so copy it then — like an API key, and for the same reason: we keep a digest of it and not the thing itself.
What to read next
The schema, the SDK sources and the full architecture notes are open. If something is unclear, that is a bug in our documentation. Get in touch.