Saltar al contenido principal

The audit hash chain

PTRS's own record of its changes is append-only and chained: each entry carries a numeric fingerprint built from the entry before it plus part of its own content, so the sequence cannot be edited without breaking every fingerprint that comes after the edit. A Verify Integrity button replays the chain and reports the first entry that no longer matches.

This page sets out exactly what that proves. It is written for whoever will be asked to stand behind it, in front of an inspector or in an internal review.

What is written, and when

One part of PTRS watches every save made anywhere in the product, in every module, and runs on every one of them.

Two tracks

The first track is automatic. Whenever a record is created, changed or removed, one entry is written for it, holding: what happened, the kind of record and its own id, the values before and after for a change or removal, who made the request, where the request came from, and when it happened.

The second track is explicit. Any part of PTRS can log a specific action by name, with its own description. These are gathered during the request and written alongside the automatic entries from the same save.

Written after the fact, separately from the change itself

Every entry is captured at the moment of the save and actually written only after that save has already succeeded, through a separate connection. So a failure to record an entry can never undo the change it was meant to record.

One comment in the code claims that separate connection has nothing else watching it, to explain why it cannot recursively audit itself. That particular claim is not accurate: every connection in PTRS is watched the same way. What is actually true, and is why nothing recurses, is that the audit record itself is not the kind of record that gets audited, and its own buffer of pending entries is already empty by the time it is written.

What the fingerprint covers

The fingerprint is built from five pieces: the previous entry's own fingerprint, the kind of record, the record's own id, what happened, and when. Each entry stores that value, and the previous entry's own value alongside it.

Five inputs. The saved record itself has fifteen columns.

Covered by the fingerprintNot covered
The previous entry's own fingerprintOrganisation
Kind of recordSite
Record idWho made the request
What happenedTheir email
WhenThe written description
The values before the change
The values after the change
Where the request came from
A trace id
Which part of PTRS made it

What the chain would catch

Because each fingerprint depends on the one before it, any change to the sequence itself is visible:

An entry removed: the next entry's own record of "what came before it" no longer matches anything.

An entry inserted: the same, from that point onward.

Two entries swapped: both fingerprints are now wrong.

The kind of record, the record's id, what happened, or when, changed on any one entry: that entry's own fingerprint no longer recomputes correctly, and every fingerprint after it is thrown off too.

That is a real property, and a useful one. Removing an inconvenient entry from this record is not possible without leaving visible evidence behind.

What the chain would not catch

On an entry that stays exactly where it is, with its five covered fields untouched, someone could still change:

Who made the request and their email, reattributing an action to a different person. The audit screen's own User column reads exactly this field.

The values before and after, rewriting what actually changed. This is the substance of the record: the fact that something changed on a particular child's record at a particular time survives; what it actually changed to does not.

The written description, which for an activity entry is the only place its real, specific name exists at all.

Where the request came from, removing the origin of the action.

Which organisation the entry belongs to, moving it between organisations.

So the chain proves the sequence of events was not edited. It proves nothing about the content of any one event. For an inspection, that is the material distinction: it can show that no record of a change was removed, and it cannot show that the recorded change is what actually happened.

This is a property of the design, not a flaw in how it was built. The behaviour matches exactly what its own documentation says it does.

What sits entirely outside this record

Nothing without a signed-in person's own request behind it is captured here at all. The comment in the code explains this as a way to avoid recording PTRS's own starter data being built, and the actual effect reaches much further than that single case.

Left uncaptured: all twelve of PTRS's scheduled background jobs, covering compliance evaluation, OCCL deadline checks, credential expiry, background-check expiry, training compliance, overdue onboarding, personnel file checks, ratio readings, licence expiry, temporary-closure monitoring, document expiry and health-document renewal, plus both of its continuously running background workers.

The ratio-reading job is the sharpest example. It writes the very figures the ratio banner and the compliance score are both built from, and there is no record anywhere that it ran, or what it actually wrote.

The honest sentence is: every change made through PTRS by a signed-in person is chained; changes made by a scheduled process are not recorded at all.

What Verify Integrity actually checks

The check:

  1. Reads this organisation's own entries, oldest first.
  2. Walks them, starting from nothing.
  3. Recomputes each entry's fingerprint using the exact same method the original save used, not a separate reimplementation of it.
  4. Stops at the first entry whose recorded "what came before" or whose own recomputed fingerprint does not match.
  5. Reports whether the chain held, the total entries, how many were actually verified, and the id and time of the first break, if any.

This is the first integrity control found anywhere across this documentation site that is both fully built and reachable from a screen. It is not a placeholder.

Three conditions that report a break that is not tampering

The record is written as one sequence across the whole installation; the check runs one organisation at a time.

The part that writes a new entry takes the previous fingerprint from the newest entry in the entire record, with no separation by organisation at all, because this particular record type does not carry PTRS's usual automatic organisation boundary.

The check itself only replays one organisation's own entries.

So a second organisation's first entry points back at the first organisation's last one, the check expects nothing there instead, and it fails immediately. On an installation used by a single organisation the two agree and the check is sound. On one shared by more than one, it reports a false break.

Entries written together share a timestamp.

The moment used for every entry from one save is captured once and reused for all of them, so a save that creates a parent record and two related ones produces three entries with an identical time.

The chain across those three is correct as written, because it is built in order in memory first. But the part that writes a new entry picks the previous one by newest timestamp, and the check replays in timestamp order too, and the storage underneath makes no promise about the order of rows that share an exact timestamp. Either side can see a tied group in a different order than it was actually written in.

Every save that creates a record together with related ones produces a tied group, so this is the ordinary path, not an edge case. A lasting fix would chain and check against a strictly increasing counter instead of the clock.

A development copy's starter data has no fingerprints at all.

The starter data any development copy of PTRS is built with is written directly into the record, specifically so that a fresh development copy does not start with an empty audit screen. None of those rows is given a fingerprint.

The check computes a real fingerprint for the very first entry, compares it against nothing, and reports a verified count of zero with a break at the very start. On a development copy, the very first press of Verify Integrity reports the whole trail as tampered with.

What would make this guarantee stronger

Not scheduled work, and included here because an evaluator will ask what the gap actually is.

GapShape of a fix
Ten of fifteen fields sit outside the fingerprintCover the whole entry, at minimum who made the request, the written description, and the values before and after
The record is global; the check is per organisationEither chain per organisation, or check across the whole installation
Timestamp tiesChain and check against a strictly increasing counter, not the clock
Starter data has no fingerprintsRun starter data through the same fingerprinting path as real activity
Scheduled jobs are not recordedGive background work its own recorded context
Nothing actually enforces append-onlyThe record's own documentation says it must never be changed or removed after the fact; nothing in the database itself currently enforces that. The chain is the only thing that would notice

Summary for an evaluator

What is true: PTRS records every insert, update and removal made through its own interface by a signed-in person, including the values before and after, and chains each record to the one before it with a cryptographic fingerprint. The chain can be checked from inside the product by anyone who can read it, using the exact same method that built it. Removing, inserting or reordering an entry is detectable. The record is written outside the save it describes, so it cannot silently take the change down with it if the recording itself fails, and every read of it is explicitly limited to the requester's own organisation.

What is not true:

The chain does not cover who performed the action, what the values were, or the written description, only the kind of record, its id, what happened, and when.

The record does not contain anything a scheduled background process did, which is twelve jobs and two continuously running workers.

The check runs per organisation while the record itself is written as one continuous sequence, so on an installation shared by more than one organisation, a red result is expected and means nothing on its own.

Nothing besides the chain itself stops the underlying record from being edited directly.

The sentence to use: PTRS keeps a tamper-evident, append-only record of every change made through its own interface, and can verify from inside the product that no entry has been removed, inserted or reordered. Anything stronger than that is not supported by what the product actually does.

Checked against PTRS on 7 September 2026.