Skip to content

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 chainSettings → 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 dayRecomputes 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 eventProduces 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.
Nothing is stored beside the events. A hash column in the event store can be edited by anybody who can edit the row it describes, so the checkpoints live in a different database with different credentials — and on a self-hosted install, one you own.

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 offlineThe 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 storedSettings → 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 sealIf 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"
It exits non-zero when a sealed day no longer matches, so it can gate a restart rather than be something somebody reads and interprets. This is the step that decides whether a restore worked. The events live in one database and the checkpoints that prove them in another, so a partial restore produces a trail that serves normally and proves nothing — and recomputing the roots is the only thing that will tell you.

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.

Scheduled packs are written to a volume you control. No bucket and no credential, on purpose: an evidence pack is the artefact a customer least wants to hand a third party a key for.

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.

Without a hold there are two options and both are bad: turn retention off entirely, which breaks every other commitment you have made about deleting data, or let the sweep destroy evidence you are legally required to keep. The second is an incident.
A hold overrides the schedulePlaced 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 monthRetention 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 gapA 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 erasedA 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.

You already hold the hard part. Every change touching that person's records, with who made it and when, is in one place. This turns it into a document written for them rather than for you — in their language, with the machine-readable version and a checksum beside it. It is a form with one field in it.
Subject access request Subject access request IN THE ZIP your-records/by-you.csv what you did yourself your-records/about-you.csv what was done to your records your-records/everything.json the same, with the full detail WHAT-THIS-IS.txt in English and Turkish manifest.json a checksum of every file about-you.csv — what happened 2026-03-14 09:12 Someone at the company changed your delivery address. 2026-02-02 16:40 Someone at the company looked at your invoice. 2025-11-20 11:05 You created your account. 2025-11-20 11:06 Someone at the company changed your payment card — last four digits. Values masked when recorded stay masked. They were never stored in full. Thirty days to answer under KVKK article 11 and GDPR article 15. This is a form with one field in it.
Both halves of the questionWhat 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 containThis 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, alwaysWhichever 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 recordedIt 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.
A language model may write one sentence per kind of event here, and nothing else. It is never shown a value, never shown an identifier, and never asked what belongs in the pack — it is given a resource type, a verb and a list of field names. The events come from a filter and the counts come from counting. A narrator that returned nothing at all would produce a pack with exactly the same events in it and plainer prose, which is what happens when no key is configured.

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.

This is the third option. One link, bounded by a period and optionally by resource type, which expires on a date you choose and stops working the moment you close it. No account, no seat, nowhere to wander.
Audit room SOC 2 2026 — Deloitte Records from 1 July to 30 September 2026 · this link stops working on 31 October Tamper evidence Each sealed day's Merkle root, recomputed from the records below just now. 30 Sep 4,120 · root matches 29 Sep 3,884 · root matches 28 Sep 4,509 · root matches 27 Sep 1,204 · root matches 26 Sep 1,096 · root matches Records updated billing.invoice Ayşe Yıldırım created billing.invoice Mehmet Öztürk deleted shop.order system updated billing.invoice Ayşe Yıldırım This visit is recorded, and the company you are auditing can see it.
The period is on the row, not in the linkAn 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 recordsEach 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 clickWhich 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 atEvery 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 itThe 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

Stuck on something?

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.