Audit Log & Security
Why this exists
The decision a regulator will eventually ask about is the one made months ago, by a model that has since been retrained, by a deployer whose dashboard has since changed. The audit trail's job is to make that decision still readable when none of the original conditions hold. Adjudon's hash chain is built around one promise: the bundle on the auditor's laptop, plus the published verification algorithm, is sufficient evidence even if Adjudon is unreachable on audit day. The chain hashes the trace; the trace describes the decision; the verification recomputes both byte-for-byte and returns one of three verdicts. Anything else — UI, dashboards, even our own running service — is convenience, not evidence.
The model: two parallel chains
Adjudon runs two independent SHA-256 hash chains. Both are append-only by construction (Cardinal Rule 5: no chain entry is ever updated or hard-deleted). Both have separate schemas, separate write paths, and separate audit consumers.
| Chain | What it logs | Read by | Schema |
|---|---|---|---|
| Decision Hash Chain | Every DecisionTrace ingested via POST /api/v1/traces | Auditors, regulators, the deployer's compliance team | HashChainEntry |
| Operations Audit Log | Every admin action: policy edits, user invitations, API-key rotation, login events, SCIM provisioning, trace exports | The customer's CISO and compliance team | AuditLog |
The decision chain is the one a BaFin examiner, a TÜV auditor, or a Data Protection Authority will replay against the published algorithm. The operations log is the one a customer's internal compliance team reads when investigating "who changed which policy at what time."
Decision Hash Chain
Every entry is one document on the HashChainEntry collection. The
fields the verification algorithm needs are:
{
"organizationId": "65b1...",
"sequence": 17493,
"traceId": "trace_aBcD1234",
"prevHash": "a3c1...0f9b",
"payloadDigest": "9e2d...4c7a",
"chainHash": "f1b0...2dde",
"createdAt": "2026-05-06T10:14:22.317Z"
}
Per-org indexes on (organizationId, sequence) and
(organizationId, chainHash) are both UNIQUE. The sequence field
is monotonic per organization, starting from 1; chains never mix
across tenants (Cardinal Rule 1).
The chain formula is published verbatim:
chainHash = sha256(prevHash || payloadDigest || sequence || createdAt)
payloadDigest = sha256(canonicalJson(traceView))
The first entry per organization uses GENESIS_HASH — sixty-four
zeros — in place of prevHash. Subsequent entries chain back
through every preceding entry's chainHash:
Entry n−1 Entry n Entry n+1
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ sequence: n−1 │ │ sequence: n │ │ sequence: n+1 │
│ traceId: t_n−1 │ │ traceId: t_n │ │ traceId: t_n+1 │
│ prevHash: H_p ├──────┐ │ prevHash: H_n−1 ├────┐ │ prevHash: H_n │
│ payloadDigest:D_n−1│ │ │ payloadDigest: D_n │ │ │ payloadDigest:D_n+1│
│ chainHash: H_n−1│ │ │ chainHash: H_n │ │ │ chainHash: H_n+1│
└──────────────────┘ │ └──────────────────┘ │ └──────────────────┘
│ ▲ │ ▲
└────────────┘ └───────────┘
link: H_n−1 fed into link: H_n fed into
entry n's prevHash entry n+1's prevHash
Modify any entry's payload, sequence, or createdAt and the recomputed
chainHash no longer matches the stored value at that entry. Modify
the stored chainHash and the next entry's prevHash no longer
matches. Either way, verification halts at that sequence number.
Operations Audit Log
The operations chain has its own AuditLog document and its own
genesis sentinel (the literal string "0"). The schema records who
(user, role, organization), what (one of an enum: auth.login,
policy.update, scim.user.create, trace.export, etc.), when
(createdAt), and where (request context, sub-resource ids). Read
access requires the admin or owner role.
How it behaves
Atomic append
The Decision chain's append path runs an atomic single-writer-lock
per organization: Organization.findByIdAndUpdate increments
hashChainCounter by 1 and reads back the updated value, all under
Mongo's document-level lock. Concurrent appends serialize cleanly; no
two entries can claim the same sequence number for the same
organization.
Performance contract: chain append is ≤ 5 ms p95 in isolation, and
the full ingestion pipeline (PII scrub + Confidence + Policy + chain)
holds p95 < 25 ms end-to-end.
Verification
The verify path is a four-step algorithm. For every entry in
sequence: recompute chainHash from prevHash + payloadDigest + sequence + createdAt, compare to the stored value, walk forward.
A passing replay returns:
{
"verified": true,
"ok": true,
"totalChecked": 17493,
"lastValidSequence": 17493,
"brokenAtSequence": null,
"brokenReason": null,
"durationMs": 842,
"verifiedAt": "2026-05-06T10:14:22.317Z"
}
A failing replay returns:
{
"verified": false,
"ok": false,
"totalChecked": 17493,
"lastValidSequence": 12047,
"brokenAtSequence": 12048,
"brokenReason": "chain-hash-mismatch",
"durationMs": 841,
"verifiedAt": "2026-05-06T10:14:22.317Z"
}
brokenReason is one of prev-hash-mismatch (a chain link is broken)
or chain-hash-mismatch (an entry was altered after the fact). There
is no third state. Tampering is loud.
Offline replay
The export endpoint produces a self-contained JSON bundle:
curl https://api.adjudon.com/api/v1/hash-chain/export \
-H "Authorization: Bearer $ADJUDON_API_KEY"
Every entry, every hash, every sequence number is in the bundle. The
auditor recomputes each row's chainHash against the published
algorithm above, on a laptop, against the downloaded bundle. No
Adjudon login. No Adjudon endpoint. No Adjudon network. The chain
remains valid evidence even if Adjudon is unreachable between the
export and the audit.
Retention & GDPR Art. 17
Retention is configurable up to 3,650 days (10 years exactly). On
a GDPR Article 17 right-to-erasure request, the source DecisionTrace
PII fields (inputContext, outputDecision, metadata, rationale)
are nullified; the chain shell stays. The payloadDigest was
computed before erasure and remains stable, so the chain still
verifies end-to-end after the nullification.
The chain hashes whatever the trace contained at ingestion. For medtech deployments the trace contains pseudonyms, not patients; for financial deployments the trace contains scrubbed input contexts, not customer PII. The chain hashes the pseudonym, not the patient.
What it is not
- Not a blockchain. No consensus mechanism, no mining, no public ledger. SHA-256 is a hash function; the chain is a Merkle-style link, single-writer per organization.
- Not tamper-proof. The chain is tamper-evident: it detects modification loudly via the verify endpoint; it does not prevent the write attempt.
- Not an external evidence store you have to ingest from us. The export bundle is the evidence; you keep it on your side. We do not hold the only copy of your chain.
- Not an LLM-generated audit trail. The hash, the sequence, and the algorithm are deterministic; no model output sits in the verification path.
Regulator mapping
| Regulator | Article | Why this concept satisfies it |
|---|---|---|
| EU AI Act | Art. 13 (transparency) | The chain is the byte-exact record of every decision; replay is the regulator-readable answer to "how was this produced?" |
| EU AI Act | Art. 12 (record-keeping, roadmap Q3 2026) | Trace storage + chain-anchored evidence is the underlying mechanism |
| DORA | Art. 28(3) (ICT TPSP register) | The chain export bundle + geographic anchor + sub-processor list is the evidence the bank's register entry is built from |
| DORA | Art. 30 (exit plan) | The chain is replay-verifiable offline against the published algorithm — effective even if the vendor becomes unavailable |
| MDR | Annex IX (clinical evaluation) | Per-decision artefacts + the chain proves the audit log itself has not been altered |
| GDPR | Art. 17 (right to erasure) | Payload nullification preserves the chain shell; both rights survive |
| GDPR | Art. 32 (security of processing) | AES-256 at rest, TLS 1.2+ in transit, SHA-256 chain — three layers, three concerns |
Where to go next
- Hash Chain API — the full
endpoint surface (
/status,/verify,/entry/:traceId,/entries,/export,/bundle,/anchors) - DORA Compliance — the Article 28-30 responsibility split that depends on this chain
- Data Residency & GDPR — the Article 17 erasure mechanism in operational detail
- Multi-Clock Incidents — five regulators in parallel off the same incident, with each checkpoint linked back to a chain entry
- Architecture Overview — the full ingestion pipeline that produces every chain row
- Scheduled Chain Export — recipe for periodic export-and-archive for the implantable-MDR 15-year retention case