Audit & the DPR chain
The hash-chained Decision Provenance Record (DPR) audit trail.
Every decision — permit, defer, step_up, modify, deny — leaves a line you can read back:
$ raxit audit tail --n 50 --audit .raxit/var/daemon-audit.jsonl
SEQ CALL_ID TIME AGENT ACTION->RESOURCE EFFECT REASON
------------------------------------------------------------------------------------------------------------------------
3 e5c17818a2c942bbb830dce66cbae312 2026-07-11T22:28:06Z default-agent tool.call->calc permit permit
5 d867c63661454e8497369a08ebaec35d 2026-07-11T22:28:06Z default-agent tool.call->post_to_w.. defer defer.policyand any single record explains itself, including which Cedar policy produced it:
$ raxit explain d867c63661454e8497369a08ebaec35d --audit .raxit/var/daemon-audit.jsonl
call_id: d867c63661454e8497369a08ebaec35d
effect: DEFER
reason_code: defer.policy
hint: A review rule matched; a human must approve via: raxit approvals approve <token>.
chain_position: seq=5 prev_hash=0bc499dda517... record_hash=fc9d708826e4...
contributing cedar policies:
[defer-post_to_webhook]
@id("defer-post_to_webhook")
@effect("defer")
permit (
principal == Agent::"default-agent",
action == Action::"tool.call",
resource == Tool::"post_to_webhook"
);That contributing cedar policies block is the real, compiled Cedar source — not a
reconstruction — showing exactly the @effect("defer") annotation that produced the third
decision state on top of Cedar's native binary Allow/Deny.
The mental model: audit-before-execute
The record is written and fsync'd to the write-ahead log (WAL) before the decision is returned to the
caller — not logged afterward, best-effort. If that write fails, the decision flips to deny
(deny.audit_error) rather than letting an unrecorded action through. Put differently: a call
can never execute without a corresponding audit record existing first. This is the same
fail-closed posture covered in Fail-closed & degrade-closed,
applied specifically to the audit path.
Hash-chained, not just appended
Each record (schema: "dpr/2") carries prev_hash and record_hash, chaining every entry to
the one before it — including the accumulated session facts that produced the decision
(taint_snapshot, prior_actions, original_request), so tampering any one of those fields
breaks the chain exactly like tampering effect or reason_code would.
$ raxit audit verify --audit .raxit/var/daemon-audit.jsonl
audit verify: OKA genuinely tampered record fails loudly and specifically:
$ raxit audit verify --audit .raxit/var/daemon-audit-tampered.jsonl
audit verify: FAIL: record 5 (seq 5): record_hash mismatch (tampered)A different failure mode is not tampering. The chain's genesis hash is seeded from the agent
id at chain-creation time — verifying against the wrong --agent produces a distinct, carefully
worded message rather than a false tamper alarm:
$ raxit audit verify --audit .raxit/var/daemon-audit.jsonl --agent totally-different-agent
audit verify: FAIL: this audit log's genesis was seeded for a different agent than
"totally-different-agent" (not tampering); pass --agent <the id the chain was created with>,
or start a new chainIf you see this, re-run with --agent matching whatever agent: the chain was actually created
under — see Common issues for the same case walked
through as a stall.
Signing is real, but opt-in and dev-keyed
Hash-chaining is unconditional and always on. Cryptographic signing of individual records is a separate, optional layer on top:
raxit: WARNING: audit log .raxit/var/daemon-audit.jsonl is UNSIGNED (no Ed25519 key found at
--sign-key). Records are still hash-chained but carry no signature. Run `raxit keys init` for
tamper-evident R5 evidence.raxit keys init generates an Ed25519 keypair and wires --sign-key so subsequent records are
individually signed. audit: require_signed: true in security.yaml (or --require-signed on
the CLI) can make an unsigned audit log a load-time refusal rather than a warning.
Signing exists and works, but is off by default, and even when enabled the signing key today
is a local dev-seed key, not a production KMS or certificate-chain-backed key. Don't describe
audit records as "cryptographically signed by default" — the honest framing is: hash-chained
always, optionally Ed25519-signed once you run raxit keys init, dev-seed-keyed until a real key
management story lands.

