Audit chain
Every decision is one row. Rows hash-chain. A signed root lands daily. Cryptographically verifiable forever.
Every authorize, every step-up, every revocation lands in audit_events. Each row
hashes the previous row's hash plus its own canonicalized payload — a Merkle list
that's tamper-evident from any signed root onward.
The row shape
{
"eventId": "evt_01J7K…",
"ts": "2026-05-23T14:12:31.412Z",
"decision": "allow",
"command": "/github/issue/list",
"resource": { "provider": "github", "owner": "acme", "repo": "app" },
"context": { "now": {...}, "cosigner": false, "envelope_active": true },
"principal": "did:key:z6Mk…",
"agentName": "triage-bot",
"swarmId": null,
"chainDepth": 0,
"parentReceiptId": null,
"prevHash": "sha256:abc…",
"hash": "sha256:def…"
}
prevHash is the row above. The chain is canonicalized in JSON Canonicalization
Scheme (JCS) before hashing, so two equivalent JSONs hash identically.
Signed roots
Once a day, the broker signs the latest hash with an Ed25519 key (AUDIT_SIGN_KEY)
and stores it in audit_roots. The same payloads are mirrored as Parquet to R2 with
a 7-year lifecycle.
To prove an event existed at a point in time:
- Note the event's
eventId+hash. - Verify the row by re-canonicalizing + re-hashing.
- Walk forward to the next signed root; confirm the hash chain.
- Verify the signed root with
AUDIT_VERIFY_KEY(the public side, posted in the dashboard's Security page).
Reading the dashboard
- Sortable table — Time, App, Decision, Command, Resource, ChainDepth, SwarmId.
- Filter by App, by decision (allow / deny / step_up), by provider.
- CSV / JSON export from the top — includes every field shown above, plus
keyId,coherenceScore,intentRisk.

Drill-in
Click a row → drawer:
- Full canonicalized payload (collapsible JSON).
- Hash + prevHash (clickable, jumps to neighbor).
- For step-ups: linked cosigner UCAN cid + approver passkey credential id.
- Download proof — JSON bundle for offline verification via audit-verify CLI.
Operational dashboards
/app/audit is for spot checks. For trending, the dashboard pre-bins:
/app/monitoring— allow rate, p95 PDP latency, step-up rate, top deny reasons.- CSV exports → Splunk / Datadog / your warehouse.