Skip to content

kew audit

Inspect the audit trail. Every governance action — dispatches, approval decisions, fleet stop/resume, CI fixes — lands as one row in the audit_events table of dispatch.db, carrying the full envelope: who (actor_type/actor_id), which repo, which endpoint, with policy_hash/prev_hash slots reserved for policy binding and hash chaining.

kew audit tail                       # 20 most recent events
kew audit tail --limit 50 --event approval_hold
kew audit tail --item SFD-12         # only events for one work item
kew audit tail --follow              # poll for new events (Ctrl-C to stop)
kew audit tail --json                # one JSON object per line
kew audit export                     # full trail as JSON to stdout
kew audit export -f csv -o audit.csv
kew audit export --since 2026-06-01

Note: -f is --follow on tail but --format on export (matching the top-level kew export).

Event types

Event Emitted by
dispatch_completed every dispatch (success, failure, budget, auth failure, unreachable, exception)
approval_hold / approval_granted / approval_confirmed / approval_declined the approval gate
fleet_stop / fleet_resume kew stop / kew resume
ci_fix_completed kew ci-fix
circuit_breaker the automatic circuit breaker — a run killed for a cost anomaly or wall-clock timeout (scope=run), or a fleet stop on a daily/weekly spend breach (scope=fleet). actor=policy, named by the rule.
claim_rollback the scheduler, when a claimed issue can't be submitted to the pool and is released back to ready (actor=policy)

Who is the actor?

The actor is resolved per event: the KEW_ACTOR environment variable when set, otherwise the OS user. KEW_ACTOR=alice kew stop records actor_id=alice. Run rows record who dispatched; each audit event records who performed that action — the approver can differ from the dispatcher.

Endpoint is configured, not attested

The recorded endpoint is what was configured at dispatch time (ANTHROPIC_BASE_URL when set, else anthropic:default, for claude-code; <provider>:via-opencode from the model's provider prefix for opencode) — honest, but not proof of where traffic actually went. Credentials embedded in URL values (userinfo, query strings) are stripped before recording. Endpoint attestation from runner output is tracked separately (#251).

Where did dispatch-log.jsonl go?

Retired. Its three writers now write audit_events rows instead — SQLite gives the audit trail one serialized, multi-process-safe write path and adds run_id to dispatch events. Existing dispatch-log.jsonl files are left on disk untouched; the per-run .events.jsonl telemetry stream is unchanged.

kew audit verify

Verify the hash-chained audit log by recomputing every link from genesis — not just the head. --anchors PATH additionally checks every in-range anchored id against the recomputed chain, the anchored count against the live chain (count-shrink relative to the latest anchor, bounded by [audit] staleness_window), and the anchor chain — each anchor's prev must bind the previous anchor. --json emits a machine verdict (including anchors.count_shrink and anchor_chain).

The anchor-chain verdict reports how many links it actually verified — Anchor chain: intact (12 link(s) verified). A legacy anchor file whose entries predate the anchor chain verifies zero links, so more than one anchor with 0 link(s) verified prints a WARNING: the re-chain defence found nothing to check rather than checking and passing (#1122). anchor_chain.checked and anchor_chain.total carry the same numbers in --json.

Exit codes: 0 chain intact (and for an empty log) · 1 tamper detected (broken link; an in-range anchor mismatch/absent row; count-shrink or a stale/lagging anchor beyond the staleness window; or re-chained anchor history) · 2 cannot run (no database, or an unreadable --anchors file).

The verdict does not depend on which directory you stand in: the database and the default anchors file both resolve against the project root, so kew audit verify from a subdirectory reads the same ledger as from the root. (Before

1156 it did not, and reading the wrong ledger reported a false COUNT-SHRINK

on a clean chain.)

See Audit log & tamper-evidence for what the guarantee does and does not cover.

kew audit anchor

Append the current chain head {id, entry_hash, count, timestamp, prev} to the anchors file and print it — prev binds the previous anchor (the anchor chain). The default is audit-anchors.jsonl at the project root (the directory holding kew.toml), so running this from a subdirectory extends the one real anchors file instead of dropping a stray copy beside you. A relative path passed explicitly to --anchors still resolves against your current directory. Pure-local — no git, no network. Refuses to extend a broken local chain or a re-chained anchor history. The scheduled audit-anchor workflow runs this and commits/pushes the file; the Actions run log is the witness.

kew audit policy [HASH]

Show the governance policy bound to dispatches, or resolve a retained policy hash.

  • No argument — prints the current effective policy hash and its redacted snapshot (budgets, approval gate, routing, security patterns, limits, and the deployed security-hook digest).
  • HASH — prints the retained snapshot and its first_seen timestamp for that policy. Exit 1 if the hash was never retained, exit 2 if there is no dispatch database.

--json emits machine-readable output. Pure-local; no network. The snapshot is a redacted allowlist — secrets such as [alerting] webhook_url are never included.