Reading a hash-chained change ledger, and re-verifying it yourself
A chained record is worth what a second person can check on their own. What one row holds, what the chain proves by itself, and what it hands to other controls. Then how an auditor re-verifies an export with their own tools.
A change ledger earns its name by being readable by somebody who was not in the room. Every governed change writes one line. The line is deliberately dull.
When, and who
A timestamp. The email address of the person who acted. The role they held at that moment. The role is written down at the time, not looked up later. Roles change, and this record is about the day it happened.
Where, and what
The surface the change was made on. The action taken. The target it acted on. The semantic layer, the relay, provider keys, script runs and the admin console all write here.
The state, as hashes
A content hash of the state before, and one of the state after. The values themselves stay out of the ledger. A record of a credential change can exist with the credential nowhere near it.
The link
The previous entry hash, and this entry’s own hash. Those two fields are the chain.
Details
A small dictionary of context. It passes through the same scrubber the operator audit trail uses. Secret-shaped keys are removed before anything reaches the file.
It sits alongside the operator audit trail rather than replacing it. Two records, two readers. The audit trail is the console history an administrator scans during the week. The ledger is what somebody reads a year later, with no memory of any of it.
What the chain proves
Each entry hash is a SHA-256 over two things joined together. First, the canonical form of the entry with its own hash removed. Second, the previous entry hash. Canonical means the keys sorted into one fixed order, with no stray whitespace. The same entry then always serialises to the same bytes. The first entry links to sixty-four zeros.
What that construction buys is worth stating precisely. Alter one character of a recorded field, delete a line, or swap two lines. Every link from that point onward stops matching. Verification walks the whole file. It reports the index of the first bad row and the reason. That reason is one of three: a malformed line, a broken link, or a hash that disagrees with its own content.
Naming the first bad row is most of the value. Something is wrong somewhere in fifty thousand lines is an alarm. Row 8,214, hash mismatch, is a place to start. It also bounds the question, because everything before it verified.
The function is plain SHA-256 with no secret key. That is a deliberate trade. A keyed construction would prove the record came from us. An unkeyed one lets anybody holding the export check it with thirty lines of their own script. For an auditor, that is the more useful property by a distance.
Questions for other controls
A chain answers one question well, and several others not at all. Knowing which is which matters. It keeps a control from being trusted for work it was never doing.
Whether a change was recorded at all. A chain says nothing about an entry that was never written. Writes are serialised and append only. A write that fails logs loudly and returns nothing, without interrupting the administrator’s action. That is the right trade for a live console. It does mean completeness rests on the write path, not the chain. Ask what happens when a write fails, and how you would find out.
What the change actually was. Before and after are content hashes. The ledger confirms that state changed, and it matches a state you already hold. Reading an old value back out of a hash is beyond what a hash can do. That is exactly why secrets can be kept out of the ledger entirely.
Whether the whole file was rewritten. An unkeyed chain detects a partial edit. Anybody able to rewrite every line could recompute every link. What answers that is the append-only write path and the file permissions. Above all, it is the copies already held by somebody else.
Whether the chain restarted. If the tail cannot be read at start-up, appending continues from the genesis link. Verification flags the join and never smooths it over. A broken link at a restart boundary usually has a dull explanation. It still deserves the question.
A vendor who answers all four cheerfully is more useful than one who says the chain covers them.
Re-verifying an export yourself
The export comes as JSON or CSV, taken from the ledger console. It carries whatever filters were applied. For verification, take the JSON. The CSV flattens the details column so a person can read it, and the hash was computed over a different shape.
Start with a previous hash of sixty-four zeros.
For each row in order, copy the object and remove its entry hash field.
Serialise what remains as JSON. Sort the keys, leave no spaces between tokens, and escape non-ASCII characters.
Append the previous hash to that text. Encode the result as UTF-8. Take the SHA-256 digest in lowercase hexadecimal.
Compare it with the recorded entry hash. Compare the row’s recorded previous hash with the one you carried in. Then carry this row’s own hash forward.
Stop at the first row where either comparison fails. Record its index, and which comparison failed.
A recomputation that disagrees on every row usually points at the serialiser, not the ledger. That includes the very first row. Key order, whitespace and unicode escaping each change the bytes being hashed. Getting one of them wrong gives a uniform, confident and completely wrong answer.
Keep the script. A verification you can re-run on demand is a control. A verification somebody performed once, in a meeting, is an anecdote.
Why the export travels further
The console shows a verification badge, computed over the whole file on every page load. A break surfaces the next time anybody looks. It is also, unavoidably, the writer reporting on itself.
The screen is a view in the ordinary sense as well. It is filtered by surface, by actor and by date range. It is sorted newest first and capped at a working number of rows. The export applies the same filters with a far larger cap. Before you draw a conclusion from a count, know which of the two you are reading.
An export can leave the building, and a screen cannot. An export taken today and held by your auditor pins the chain at today. Any later disagreement between their copy and yours shows up in a plain comparison. It asks for trust in neither party. That is the property worth designing the whole routine around.
An export gains value with age and with distance. One copy, in your own hands, on the same machine, is a backup. A series of copies is evidence. Take them on a schedule, and hold them where the product has no write access.
A routine you can repeat
At the end of each period, export the full range as JSON. Apply no filters.
Verify it with your own script, and keep the export and the script together.
Compare the new export against the previous one. Every row they share should be identical, character for character. This is the check that catches a rewrite, which the chain alone leaves open.
Spot-check a handful of rows against changes somebody remembers making. Check the actor and the role recorded beside them.
Store each export where the product has no write access.
Record who took it and when, beside the file, not in a separate system.
The third step turns a self-consistent file into an audited one. It costs one comparison command. Everything else on this page is preparation for running it.
An assertion is a sentence. Evidence is something a second person can check without asking you. What an audit trail has to contain, what a hash actually proves, and what to require of any tool that claims to produce one.
Four things get called governance: certification, ownership, stale content and change control. Here each one is written as a check you can run, not an aim you can state. It also covers the one change that passes a visual review and is still wrong.
The approve to run rule, in plain words. The assistant reads, explains and proposes, and a person owns the write path. What that costs, what it buys, and three questions to ask of any AI feature.