Loading

Audit Chain

Hash-chained audit entries sealed on a schedule, so that altering or removing history is detectable rather than merely discouraged.

How the Audit Chain Works

An audit log that can be edited proves nothing. The audit chain is what turns the log from a record into evidence.

Where to find it

Architect Panel → Activity:

  • Audit Chain — the chain head and verification state for each datastore
  • Activity Log — the entries the chain is built over

Architect Panel → Automation:

  • Tasks — Audit Chain Sealing, every 5 minutes

The problem it solves

Anybody with sufficient database access can change a row. If the audit log is just another table, then somebody who can alter a record can alter the entry recording that they altered it — and the log's account of events becomes exactly as trustworthy as the access controls around it.

That is a weak position to be in when the log is the thing you are relying on to demonstrate what happened.

How chaining works

Each audit entry carries:

  • A row hash — a fingerprint of the entry's own content.
  • A sequence number — its position in the chain for that datastore.
  • The previous entry's hash.
  • A chain hash — computed from this entry and the one before it.
  • A sealed timestamp.

Because each entry's hash incorporates the previous entry's, the entries form a chain in which every link depends on all the links before it.

Why that makes tampering detectable

Change the content of any historical entry and its row hash no longer matches. That breaks the chain hash of the entry after it, and every entry after that.

Delete an entry and the sequence has a gap and the following entry's previous-hash points at something that is no longer there.

Neither can be quietly repaired without recomputing every subsequent entry — and the sealed head, recorded separately, will still disagree.

Sealing

The Audit Chain Sealing task runs every five minutes and advances the sealed head for each datastore, recording the current sequence number and chain hash.

Sealing is what fixes history in place. Once a range of entries is sealed, altering any of them contradicts a hash recorded at a known time, so the window in which undetected tampering is even theoretically possible is the few minutes since the last seal.

The task ships disabled. Enable it under Automation → Tasks — an unsealed chain still detects internal inconsistency, but it loses the independent timestamped anchor, which is most of the value.

It is evidence, not prevention

Worth being precise about: the chain does not stop anybody from changing the database. It makes the change detectable.

That is the appropriate goal. Prevention is the job of access control; the chain's job is to ensure that if prevention failed, you can demonstrate it — and to remove the argument that the log might simply have been edited.

Per datastore

Each datastore has its own chain and its own head, so a problem in one is contained and identifiable rather than casting doubt over everything.

Worked example

An organisation with a regulatory obligation to evidence its record-keeping enables sealing at go-live. Two years later, asked to demonstrate that a set of case records has not been retrospectively altered, they show a verified chain over the relevant datastore with a continuous sealed history — which is a different quality of answer from asserting that nobody would have done so.

Recommendations

  • Enable the sealing task at go-live, not when you first need it.
  • Understand it as detection, and keep access control doing the prevention.
  • Do not modify audit tables directly, for any reason — a legitimate correction still breaks the chain.
  • Record when sealing was enabled; it bounds the period you can evidence.

Verifying the Chain and Reading Gaps

A chain is only useful if it is checked. Verification walks the entries, recomputes the hashes and confirms they still agree.

Where to find it

Architect Panel → Activity:

  • Audit Chain — the head, last verified time, state and message for each datastore
  • Activity Log — the entries themselves
  • Error Log — where a failed verification run is likely to surface

What the chain head records

  • Datastore — which chain this is.
  • Head sequence and head hash — the current end of the chain.
  • Last sealed — when the head was last advanced.
  • Last verified — when it was last checked.
  • Verify state and message — the result, and what it was.

Reading the state

A good state means the entries recomputed to the hashes they carry, and the head matches. That is a positive statement about the whole history, not just the recent part.

A failure means the recomputation disagreed somewhere. The message identifies where, which is the starting point for everything that follows.

Check last-verified, not just the state

A chain showing a good state but last verified eight months ago is telling you about the world as it was in December. The state and the date have to be read together — this is the single most common misreading of the screen.

Gaps

Detected gaps are recorded rather than glossed over. A gap means the sequence skips, which is what deletion looks like from inside the chain.

Not every gap is sinister — a restore from backup that replayed part of a table, or a maintenance operation done outside the application, can produce one. But every gap needs an explanation that somebody wrote down at the time, because an unexplained gap is indistinguishable from a covered track.

When verification fails

  1. Do not correct anything. Repairing the chain destroys the evidence of what happened to it.
  2. Record the state, the message and the timestamp as you found them.
  3. Establish what has direct database access, and check whether a restore, migration or manual operation happened around that sequence.
  4. Escalate — a genuine chain failure is a security incident, not a maintenance task.

The instinct to tidy it up is strong and it is exactly wrong. A broken chain that has been repaired tells you nothing at all.

Verify on a schedule

Regular verification narrows the window in which a problem could have arisen. Verified monthly, a failure is localised to a month; verified once a year, it is localised to a year — and a year is usually long enough that nobody can reconstruct what happened.

Operations outside the application break chains

This is the practical warning. A bulk update run directly against the database, a partial restore, a data migration executed in SQL — all legitimate operations, all capable of breaking a chain.

Plan for it: do such work through the application where possible, and where it is not, record what you did and when, so the resulting gap has a contemporaneous explanation rather than a reconstructed one.

Worked example

A monthly verification returns a failure on one datastore. Nothing is touched. The message points at a sequence range matching a weekend when a partial restore was performed after a storage incident, and the change record for that work confirms it. The explanation is attached to the incident record, verification is re-run to establish a new clean baseline, and the event is closed as understood rather than as unexplained.

Recommendations

  • Verify monthly and read the last-verified date, not only the state.
  • Never repair a broken chain.
  • Document any operation done outside the application, at the time.
  • Treat an unexplained gap as an incident until it is explained.