Skip to main content
Gateway-admitted calls and their recorded guardrail verdicts enter a per-tenant, append-only SHA-256 hash chain. The chain is tamper-evident: any change to a past event — deletion, insertion, reorder, or modification — breaks the recomputed hash, and anyone holding an export can detect it without trusting Tracelane. What the guarantee rests on, stated plainly. Every tier gets its own Ed25519 signing keypair, minted on the tenant’s first anchored batch. That key is generated and held by Tracelane (envelope-encrypted at rest), so the signature proves a batch has not been altered by anyone other than Tracelane; it cannot, by itself, stop Tracelane from re-signing a rewritten chain. What stops that is the public Sigstore Rekor anchor: an anchored batch’s Merkle root sits in an append-only public log with a timestamp nobody can backdate, so rewriting history would leave the old root on the record. Against a third party, the chain plus the signature is the guarantee on every batch; against Tracelane itself, the guarantee is the Rekor anchor, and it covers only the batches that are anchored. Signing keys are held by Tracelane, not by you.
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).
The cryptographic-anchoring layer (Ed25519 + Rekor) and self-verification are included on every tier. Seven-year ledger retention and the bulk /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.
The workspace count is a separate inventory snapshot, not a verification result. If that read fails, the page shows Unavailable; it does not substitute the number of loaded rows. The page names how many rows lie outside these rows when the count is known. Both reads can change as events arrive. A successful subset has a neutral headline naming the number checked and the total. Last event recorded and Last anchored show the latest recorded event time and the latest batch time marked anchored. Missing times are Unknown. These timestamps are activity evidence, not a recorder health guarantee. Batch evidence lists the loaded batch ranges, eight at a time. A proof attached to a record is not itself a passing check. Individual events are behind a disclosure, with 50 loaded rows per page. Pagination does not fetch or check the rest of the ledger. At a billion rows the browser still receives the bounded window. The page does not offer a whole-ledger verification button, a current-chain-head claim, or a full-ledger coverage percentage. The current API does not provide a paged batch inventory or an independently verified workspace-wide status. Database counting and export processing still depend on ledger size; this is not a billion-row performance guarantee.

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

Audit ledger chain: events → SHA-256 hash chain (live) → Merkle root → Ed25519 → Sigstore Rekor (live, best-effort)
Export, self-verify and ledger-range read the canonical store; they never fall back to the ClickHouse copy, so a verifier is never handed a partial ledger dressed as a whole one. A self-hosted gateway run without Postgres keeps its chain in ClickHouse alone. The result: the hash chain is self-verifying. Given any NDJSON export, 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.
The ledger export is an API call. It returns the NDJSON that 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

Without --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:
Anchoring is best-effort: 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:
Exit code 1.

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 from GET /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/pubkey publishes the takeover point as workspace_key_since_seq; pass it as --workspace-key-since-seq so 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.