SKU #6 Defense Ledger · Zero-trust verification
Cryptographic MLRO signoff. Verifiable by anyone.
Every MLRO signature on an OilFlow Defense Pack is cryptographically bound to the underlying evidence, the decision basis, and the attestation. The binding algorithm is published verbatim. A regulator can verify a pack's integrity directly from the raw row data without trusting OilFlow's API to tell them the truth.
Why this matters
Audit trails that survive scrutiny
Under FCA SMCR and the MAS individual-accountability regime, MLROs can be personally fined or banned from regulated roles for financial-crime control failures. The defense file an MLRO attaches to a contested decision is, in those regimes, the artifact that determines whether the personal-liability question is even serious.
In October 2024, the FCA fined Starling Bank £28.96m for AML and sanctions-screening control failures. Its Final Notice reinforced what regulators look for: clear audit trails of remediation and a senior manager who is accountable for financial-crime controls. That enforcement action made clear that the quality of a firm's defense file is itself a regulatory test point, not just the decision recorded in it.
Most compliance tools that produce defense files produce them as editable Word documents or PDF exports. There is no cryptographic binding between "the evidence at the moment of decision" and "the signature attached to it". A defense file modified six months after signing is functionally indistinguishable from one signed at the original time.
The OilFlow Defense Pack ledger fixes this. The signoff hash is computed at the moment of signing and stored on the row. Any later edit to the evidence, the narrative, the decision basis, the attestation, the signer name, or the signoff timestamp will break the hash. The regulator does not need to trust OilFlow; the verification can be performed independently from the raw row data.
The hash
SHA-256 over a canonical JSON serialization
At sign time, the POST /api/v1/defense/[request_id]/sign endpoint constructs a payload object from the following ordered fields:
- 01request_id
- 02counterparty_normalized
- 03decision
- 04decision_basis_summary
- 05evidence_pack
- 06narrative
- 07signoff_by
- 08signoff_at
- 09signoff_attestation
The payload is canonicalized using a deterministic JSON serialization (recursive key-sort on objects, original order on arrays, standard JSON encoding for primitives). The SHA-256 digest of the canonical byte sequence is stored in the signoff_hash column on the defense_packs table (migration 164). The algorithm is published below; the source file is at platform/src/app/api/v1/defense/[request_id]/sign/route.ts.
canonicalize + signoff_hash
function canonicalize(value):
if value is null or primitive:
return JSON.stringify(value)
if value is array:
return "[" + value.map(canonicalize).join(",") + "]"
// object: sort keys alphabetically, then recurse
keys = sorted(value.keys())
parts = keys.map(k => JSON.stringify(k) + ":" + canonicalize(value[k]))
return "{" + parts.join(",") + "}"
signoff_hash = sha256_hex(
canonicalize({
request_id, counterparty_normalized, decision,
decision_basis_summary, evidence_pack, narrative,
signoff_by, signoff_at, signoff_attestation
})
)Published verbatim — identical implementation ships in the sign route and the verify route
The approach is the same idea as RFC 8785 (JSON Canonicalization Scheme) at a small scale. We control both producer and verifier so we do not need the full RFC; we only need "two identical payloads always produce identical bytes." The canonical serializer ships in both the sign route and the verify route, with identical implementations for cross-checking.
Verifying a pack
Three paths, each independently trustworthy
Path 1 · API verify endpoint (fast)
Call GET /api/v1/defense/[request_id]/verify with a DEFENSE-scope API key. OilFlow recomputes the canonical hash from the live row, compares to the stored signoff_hash, and returns { hash_match: true | false, computed_hash, stored_hash }.
Trust model: this path requires you to trust that OilFlow's implementation honestly reports divergence. For audit-grade verification under regulator pressure, use Path 2 or Path 3.
Path 2 · Open-source library (zero-trust)
Fetch the raw row via the standard read endpoint, run the canonical-JSON algorithm above on the fields listed in signoff_hash_inputs (also stored on the row, so the field list is self-describing), compute SHA-256, compare. Any modern language has a SHA-256 implementation in its standard library; the canonical-JSON implementation is ~10 lines of code (see the pseudocode above).
Trust model: requires only that the regulator trust their own SHA-256 implementation. No trust in OilFlow.
Path 3 · Regulator escrow (long-term)
For banks that need defense-trail durability beyond the subscription window: signed packs can be exported as a self-contained JSON bundle (evidence + narrative + signoff + hash) and escrowed with the bank's own audit-trail system. Re-verification at any future date does not require OilFlow to still exist.
Trust model: zero ongoing dependency on OilFlow. The cryptographic binding survives whether OilFlow does or not.
Rescinding a signed pack
Append-only, original signoff preserved
MLROs occasionally need to rescind a previously-signed pack — new evidence surfaces, a counterparty is later sanctioned, an escalation was missed. The rescind path on the OilFlow Defense Ledger is intentionally append-only.
Rescinding flips signoff_status to rescinded and stores the rescission attestation. It does NOT touch the original signoff_hash, signoff_by, or signoff_at. The cryptographic binding from the original moment of decision survives the rescission. A regulator can ask "at the time this MLRO signed this pack, what evidence did they have?" and get an answer that is independently verifiable years later, even if the underlying counterparty was subsequently re-classified.