The ledger covers gateway-admitted calls and their recorded guardrail verdicts; SDK/OTLP-only spans are captured, not chained; the trace ledger badge shows whether that trace has a ledger record.On a trace, In the ledger means a ledger record was found; Captured, not chained means none was found. Ledger membership is not a cryptographic verification result. Anchor recorded identifies a recorded anchor, not a verified inclusion proof. Use the Audit page to check these rows.What is live. The SHA-256 hash chain is appended for every tenant (the
chain itself is universal). On every tier, each batch’s Merkle
root is additionally Ed25519-signed and best-effort anchored to the public
Sigstore Rekor v2 log (
log2025-1.rekor.sigstore.dev). Anchoring is per Merkle batch (every 100
events) — never per-event or per-trace, and coverage is not yet universal:
it grows as each batch closes and anchors. Anchoring is best-effort — if the
public log is unreachable a batch stays signed-locally-but-not-yet-anchored, and
the hash chain + Ed25519 signature still verify. eIDAS QTSP timestamping is not
provided. Self-verification (tlane verify against your own export of
the chain) is included on every tier. The bulk regulatory export
(/v1/audit/export, the Article-12-style evidence package) is an Enterprise
capability (fail-closed on other tiers)./v1/audit/export regulatory package are included on Enterprise.
Read the Audit page
The Audit page checks a bounded window, not the integrity of the entire workspace. It loads up to 1,000 of the newest rows within your plan’s ledger retention window and runs the open verifier in your browser. The headline names that scope. A passing check never establishes the integrity of rows outside it.- Chained: gateway-admitted calls and recorded guardrail verdicts.
- Captured, not chained: SDK/OTLP-only spans.
- Row hashes: checked from the chain’s first row, or from a verified public anchor when earlier rows are absent. Loaded rows before that starting point are not verified.
- Public proofs: checked separately against the trusted workspace key. Hashes can match while a public proof fails.
When a check needs attention
Save the check report before reloading. This JSON download is available on every plan and records the check scope and findings. Refresh & check latest fetches fresh evidence and runs verification again. Select First finding · sequence N to open the event list at the highlighted record.
A new workspace with an empty ledger gets a gateway setup action, not a green check.
No loaded rows with a nonempty or unavailable inventory is unknown, not an empty
workspace.
Export from the page
Viewing and checking the ledger is included on every plan. The Article-12 bulk export requires Enterprise. Select explicit UTC dates under Export evidence. The download streams the requested date range without the browser preview’s row cap. It does not verify the file. A date boundary may exclude rows that a batch proof needs; use an adequate range and check the resulting evidence. A successful download alone is not proof of completeness or integrity.How it works
tlane verify recomputes the chain and returns exit 0 (no gaps, no mutations)
or exit 1 with a specific fault description. Supplying --tenant-pubkey
additionally validates the Ed25519 signatures and, for anchored batches, the
Rekor inclusion proofs — that flag, not the plan, is what switches those layers
on. Without it the chain is still checked, but a ledger carrying anchor records
exits 1 as INCOMPLETE rather than passing on a partial result.
Export
/v1/audit/export (and /v1/audit/summary) require Enterprise
and fail closed (HTTP 403) without it. The hash chain is appended
for every tenant, and self-verification (tlane verify) is included on every
tier — only the bulk regulatory export is Enterprise-only. For anchored
batches the export bundle includes the Rekor inclusion proof + signed
checkpoint.tlane verify
reads; for anchored batches the bundle carries the Rekor inclusion proof and the
signed checkpoint.
tlane export --pack <name> is a different, unrelated command. It writes a
documentation pack — static markdown templates describing how Tracelane logs,
which models it routes to, and how it handles data. It reads no ledger and makes
no network calls, so it contains none of your data and is not an export of
your audit chain. Use the API call above for the ledger itself.Verify
--tenant-pubkey the anchor layer does not run at all, so a forged anchor would not be caught. The verifier therefore exits non-zero with INCOMPLETE when a ledger carries anchor records and no trusted key was supplied. Obtain the key out-of-band (dashboard Settings → Audit signing key, or GET /v1/audit/pubkey) — never from the export you are auditing.
Output on pass:
rekor_anchors_seen counts the batches that claim an
anchor, and unanchored batches still verify via the chain plus the Ed25519
signature. Pass --json for the same report as a machine-readable object.
Output on failure — the first ten divergences print with their seq:
Verifier availability
The verifier ships in three languages so you can run it in your own pipeline without trusting Tracelane’s toolchain:
All three recompute the SHA-256 hash chain and produce identical output for the
same input. The Rust verifier is the reference implementation; the Python and
TypeScript versions are tested against the same conformance vectors in
evals/audit-ledger/. (Ed25519 signature and — for anchored batches — Rekor
inclusion-proof validation run in all three today.)
Audit event schema
Each NDJSON line is one audit event:payload is the verbatim canonical JSON string that row_hash was computed
over — a verifier must re-hash those exact bytes, not a re-serialised copy.
event_type is an open string; the chat hot path writes
chat.completions.request.
Batch header schema
Each anchored batch carries an anchor record, discriminated from row records by"type":"anchor" (row records carry no type). rekor is absent from the
JSON object entirely unless anchor_state is anchored — the field is
skip_serializing_if = "Option::is_none"
(crates/gateway/src/audit_export.rs:119-120). It is never emitted as an empty
string or null, so a verifier must treat a missing key as “not anchored”.
The row-level rekor_entry_id (:98) is a separate field on the event record
above and follows the same rule.
Public key management
The per-tenant Ed25519 audit keypair is live. Fetch the public key fromGET /v1/audit/pubkey (or Settings → Audit) — it is the trust root the
verifier uses to accept anchors. The private key is generated and held by
Tracelane, envelope-encrypted at rest under a server-held master key
(crates/gateway/src/audit_keys.rs); it is not encrypted under a
customer-managed key, and rotation of the audit signing key is not
offered.
Platform-signed batches
A workspace without its own audit key yet has its batches signed by Tracelane’s platform key — one key shared across workspaces. When the workspace key is created, every later batch is signed with it. The verifier trusts both, and never confuses them:- A batch signed by the platform key verifies as platform-signed: the rows are
unchanged since signing, but Tracelane vouches for them, not your workspace key.
The report counts them (
platform_signed_batches) and names their ranges. - The platform key is accepted only before your workspace key took over. A
platform-signed batch at or after that point fails verification
(
platform_key_after_workspace_key), because that is exactly what someone holding the platform key would have to append.GET /v1/audit/pubkeypublishes the takeover point asworkspace_key_since_seq; pass it as--workspace-key-since-seqso the rule holds even when the range you verify contains no batch signed by your own key. - A platform-signed batch never becomes the starting point a partial (windowed) verification is trusted from — only your workspace key’s anchored batches can.
- The platform key is pinned in each verifier release, and the verifier uses the
pinned key unless you override it. It is also published at
GET /v1/audit/platform-pubkey— use that to cross-check the fingerprint, not as the trust root. Neither key is ever taken from the export.