A3E9 logoA3E9
All evidence

// AUDIT_SCHEMA · NO_LOGIN_REQUIRED

The Record Written Before the Hardware Is Touched.

Every ALLOW and every DENY is written to an HMAC-chained trail, including refusals that never reach a session — the decision is recorded whether or not the hardware ever sees it. This page is the schema of those records and the rule that chains them, so a reviewer knows what they are looking at before they have a file in hand.

// RECORD_SCHEMA

Field by Field.

This is the shape of one line in the downloadable .jsonl bundle. A reviewer with a bundle should be able to parse it from this table alone.
FieldTypeWhat it is for
seqintegerMonotonic sequence number. Walk the file in this order to check the chain.
tsRFC 3339When the record was written — before the hardware call it describes, not after.
levelstringAUDIT for compliance-relevant decisions. Policy denials are written at this level.
opstringThe RPC or handler name — Sign, GenerateKey, Verify. Not the wire method.
outcomeSUCCESS | FAILUREOnly meaningful on the return-value logging variants. See the caveat below — this is not a reliable ALLOW/DENY signal on its own.
file / func / linestring / string / intSource location that emitted the record.
detailstringStructured key=value text carrying request_id, key label, algorithm and the outcome marker. This is where a reviewer finds their own request.
duration_msinteger (optional)Present only on timed operations — Sign, Verify, Encrypt, Decrypt.
rvhex string (optional)PKCS#11 return code. 0x00000000 is CKR_OK.
prev_hmachex stringThe preceding entry's hmac. This is the link that makes the file a chain.
hmachex stringHMAC-SHA256(prev_hmac || entry_json) — the record's own tag.

Limit

The outcome field is not an ALLOW/DENY signal. Most log calls in this codebase do not pass an explicit PKCS#11 return code, so outcome defaults to SUCCESS even for a request that was rejected on policy. What tells you what actually happened is the marker inside detailrpc=completed versus rpc=rejected_pqc_policy. A reviewer who filters on outcome alone will miscount denials, so it is said here rather than in a footnote.

// SYNTHETIC_SAMPLE

Three Records, Fabricated on Purpose.

These are not from a run. They are hand-written to show the shape: a policy denial, the near-identical request succeeding, and a licence-gated key generation refused. The hmac values are visible placeholders rather than plausible digests, so that nobody mistakes them for something verifiable.

Synthetic — illustrative only, not a run record

{"seq":41,"ts":"2026-02-11T09:14:02Z","level":"AUDIT","op":"Sign",
 "outcome":"SUCCESS","file":"SigningServer.cpp","func":"Sign","line":645,
 "detail":"rpc=rejected_pqc_policy request_id=<yours> key=demo-mldsa-key alg=MLDSA65 mode=FULL_PQC",
 "prev_hmac":"<SYNTHETIC-prev>","hmac":"<SYNTHETIC-a>"}

{"seq":42,"ts":"2026-02-11T09:14:37Z","level":"AUDIT","op":"Sign",
 "outcome":"SUCCESS","file":"SigningServer.cpp","func":"Sign","line":645,
 "detail":"rpc=completed request_id=<yours> key=demo-mldsa-key alg=MLDSA65 mode=PQC_PRIMARY sig=3309B",
 "duration_ms":18,"rv":"0x00000000",
 "prev_hmac":"<SYNTHETIC-a>","hmac":"<SYNTHETIC-b>"}

{"seq":43,"ts":"2026-02-11T09:15:11Z","level":"AUDIT","op":"GenerateKey",
 "outcome":"FAILURE","file":"KeyManager.cpp","func":"GenerateKey","line":212,
 "detail":"rpc=denied reason=licence_suspended request_id=<yours> class=signing",
 "rv":"0x00000000",
 "prev_hmac":"<SYNTHETIC-b>","hmac":"<SYNTHETIC-c>"}

Note what the first two records demonstrate together: a post-quantum request refused on the server’s operating-mode policy, then the same key and algorithm accepted under a permitted mode. The denial is a record at AUDIT level with its own place in the chain — refusals are evidence, not silence.

// CHAIN_RULE

What the Chain Proves, and What It Does Not.

Each entry’s hmac is HMAC-SHA256(prev_hmac || entry_json), and each entry carries the previous entry’s tag in prev_hmac. Both fields travel in every record of the bundle, so the structure is checkable offline without re-contacting anything.

Checkable without the key

Continuity and ordering. Walk the file in seq order and confirm each entry's prev_hmac equals the previous entry's hmac. A missing or reordered record breaks that link, and the break is visible with nothing but the file.

Not checkable without the key

Forgery. Recomputing an entry's own hmac requires the HMAC signing key, which stays operator-held and is never served by the portal. An adversary with that key could re-chain a modified file and an offline check would not detect it. Stated plainly because the distinction decides how much weight the artifact carries.

A chain that starts mid-stream is normal

The downloadable bundle is a tail of the live log, so it begins wherever the retained window begins. A chain that does not start at seq 1 is expected — it is not a break, and a reviewer should not record it as one.

Breaks are counted, not just detectable

The running service exports a chain-break counter, so a break is an operational signal rather than something discovered only when someone thinks to check. A non-zero value indicates a missing or modified entry.

Limit

A rotated or truncated log is not evidence that an event did not happen. These trails can roll, and a search of the current file proves only what that file contains — if it begins after the window you care about, it has told you nothing. Check the retained window’s first timestamp against your own before concluding a record is absent.

// WHAT_A_TOKEN_ADDS

A token turns this schema into your own evidence: submit a request with a request_id you choose, then find that exact id in the downloaded audit bundle. The schema tells you what to look for; only the live trail contains your record.

Everything above is checkable without contacting anyone, and is meant to be read first. The token exists because the remainder needs a provisioned environment — not because the evidence is being held back.