Skip to main content

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.

ChainWhat it logsRead bySchema
Decision Hash ChainEvery DecisionTrace ingested via POST /api/v1/tracesAuditors, regulators, the deployer's compliance teamHashChainEntry
Operations Audit LogEvery admin action: policy edits, user invitations, API-key rotation, login events, SCIM provisioning, trace exportsThe customer's CISO and compliance teamAuditLog

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:

HashChainEntry
{
"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:

POST /api/v1/hash-chain/verify (success)
{
"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:

POST /api/v1/hash-chain/verify (broken)
{
"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
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

RegulatorArticleWhy this concept satisfies it
EU AI ActArt. 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 ActArt. 12 (record-keeping, roadmap Q3 2026)Trace storage + chain-anchored evidence is the underlying mechanism
DORAArt. 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
DORAArt. 30 (exit plan)The chain is replay-verifiable offline against the published algorithm — effective even if the vendor becomes unavailable
MDRAnnex IX (clinical evaluation)Per-decision artefacts + the chain proves the audit log itself has not been altered
GDPRArt. 17 (right to erasure)Payload nullification preserves the chain shell; both rights survive
GDPRArt. 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