Meridian handbook
Deploying

Data protection duties

The data protection work that falls to whoever runs an install, answering a subject access request, generating the impact assessment, and turning on and verifying the audit log's hash chain, with what the chain cannot detect.

You hold the data, so you are its controller: the duties below are yours, and these commands do the mechanical part. All three are in both binaries; what they cover follows the modules your binary carries.

DutyCommandHow often
Answer a subject access requestengine-dmo sarWhen someone asks; the law allows a month
Keep an impact assessmentengine-dmo dpiaOnce, then after any configuration change
Show the audit log has not been alteredengine-dmo audit verifyOn a schedule you choose, with audit.hash_chain on
Erase a personengine-dmo crm eraseWhen someone asks (engine-dmo only)

Subject access requests

engine-dmo sar --email someone@example.com --out sar.zip
Run it on the host, with the same environment as the engine. --out must not exist yet. The address is trimmed and lower-cased before it is matched.
Read the archive. It is written readable by its owner only. An entry in the audit log or a note on a timeline can name other people as well, and what to do about those is your decision as controller.
Hand it over on a channel you trust, then delete your copy.

The archive holds a README.txt saying what each part holds, a request.json with the records the address was found to belong to and the counts, and one JSON file per section under sections/. A section exists only when your binary carries its module.

FromWhat it holds
Every installThe staff account and its sessions; audit entries the person acted in, that are about one of their records or that name the address; timeline entries; notifications; revisions authored (not the content, which is the organisation’s); mail sent (not the bodies, which are not kept); suppression; agent connections
CRMThe person’s record with every custom field, their organisations, consents and broadcasts
Member portalSessions; passkeys (name and dates only); invitations; first-session steps; the enquiries they made or handled
ClaimsClaims made by the address or naming the person, their checks and letters
FormsSubmissions whose data holds the address, or that the CRM person made
NewsletterThe subscriber, with consents, preferences and campaigns received
AISuggestions and assistance the staff account asked for or accepted, suggestions about one of the person’s records, and usage by day: what, when and for which document, never the text

No password, second factor, token or key is ever included. An address that matches nothing still produces a bundle of whatever names it. Each run writes sar.export to the audit log with the records found and the counts, never the address.

The impact assessment

engine-dmo dpia --out dpia.md

This writes a data protection impact assessment in the shape of the ICO’s template, as Markdown. It reads engine.yaml and the binary’s modules, never the database, and shows a configured address by its host, never a credential. Generate it again after a configuration change, since it states the retention periods and processors the install runs with.

Filled in for youLeft for you, marked To complete by the controller
The personal data held and about whom, the typical lawful basis, each retention period, where it is storedWhy a DPIA is needed; scope; context; purposes
Each processor and what it receivesThe contract and transfer mechanism for each processor; who you consulted
How the install supports each rightWhy each kind of data is necessary, and why those periods suit it
The controls, including whether the audit log is hash chainedEach risk with its likelihood and severity; where backups are kept; sign off
An annex per module that has one, such as the AI data flow statement

Check each row against how you run the install before you rely on it: the lawful bases are typical ones, and yours are your own to confirm.

The audit chain

The audit log is append-only to application code, but someone with access to the database could change it. The optional hash chain makes that detectable: each row takes the next link in its realm’s chain (staff, member or system), and each link is a hash of the row and of the link before it.

Turning it on

audit:
  hash_chain: true

Restart the engine on every replica. The engine writes the setting to the database when it starts, and rows are chained from then on; earlier rows stay outside the chain. It costs a lock: each realm’s audit rows are written one transaction at a time, held until that transaction commits. It is off by default for that reason.

With the chain on, a daily audit.anchor task logs each realm’s newest link as audit chain head, with realm, seq and hash. Ship your logs off the host and you hold a record of the chain outside the database (Monitoring).

Verifying it

engine-dmo audit verify
flowchart TD
  run["audit verify"] --> any{"Any chained rows?"}
  any -->|no| off["Exit 3: nothing is chained"]
  any -->|yes| walk["Walk each realm in link order"]
  walk --> ok{"Any break?"}
  ok -->|no| intact["Exit 0: intact<br/>keep the printed heads"]
  ok -->|yes| broken["Exit 1: each break listed"]
What it printsMeansWhat to do
Links starting above 1Retention pruned the oldest rowsExpected
rewritten by erasureA person’s erasure replaced their details with a tombstone. The row keeps its linksExpected
written while the chain was offaudit.hash_chain was off for a while. Those rows are not protectedExpected, if you turned it off
BREAK contentA row changed with the database trigger offTreat as an incident
BREAK rewriteA row rewritten in a transaction that recorded no erasureTreat as an incident
BREAK linkA forged or recomputed linkTreat as an incident
BREAK gapRows deleted from inside the chainTreat as an incident

For an incident, keep the database and your logs as they are, and compare with your backups and the heads you recorded.

What it cannot detect

Residual risk

Someone with the database owner’s rights can disable the triggers and rebuild the whole chain, and audit verify will then report it intact. Only a head recorded outside the database detects that: a later run or log line showing the same link with a different hash means the chain was rebuilt. Keep the audit chain head log lines, or a printout of audit verify, somewhere that person cannot reach.

A site leaving the install

engine-dmo site export keeps the site’s audit rows without their links, because a subset of a chain would report gaps for ever, and it does not carry the setting. The new install’s chain starts at link 1 once its own audit.hash_chain is on.