--- id: WARDEN-WP-0022 type: workplan title: "Audit trail + `warden activity` — one place to see what ops-warden did" domain: infotech repo: ops-warden status: finished owner: claude topic_slug: custodian planning_priority: high planning_order: 22 created: "2026-07-01" updated: "2026-07-01" state_hub_workstream_id: "fc8afa28-68a7-4250-a19e-9754829f0cd5" --- # WARDEN-WP-0022 — Audit trail + `warden activity` **Problem:** ops-warden's actions are recorded in scattered places — `signatures.log` `access-audit.log`, the systemd journal (worker ticks), and State Hub progress notes (the narrative). There is **no single, structured audit trail** and no one command to answer *"what did ops-warden do in the last N days?"*. For a security steward, a coherent, metadata-only audit record is table stakes. **Goal:** a unified, append-only audit log that captures **every** ops-warden action with a common shape (never a secret value), and a single `warden activity` command to read it. **Non-negotiable (this is a security tool's audit):** the audit record holds **metadata only** — actor/subject, action, target/path id, decision id, TTL, outcome, timestamp — and **never** a token, key, cert body, or other secret. A secret-material guard rejects any event field that looks like a value (mirrors the catalog `_assert_no_secret_material`). **Posture:** read/append-only, in-boundary (local logs + optional hub read). Tamper-evident hash-chaining is noted as an optional hardening for when the ecosystem reaches testing. **Relates to:** WP-0014 (`access-audit.log`), the SSH lane (`signatures.log`), WP-0020/0021 (the worker). Linger is now enabled (worker survives logout); full logged-out value also needs the State Hub + tunnels to be login-independent (State Hub → railiance01, `cust-wp-0011`). --- ## Tasks ### T1 — Unified audit event log ```task id: WARDEN-WP-0022-T01 status: done priority: high state_hub_task_id: "7f8f768a-4c62-4096-bad8-912cea0f35a7" ``` - [x] `src/warden/audit.py`: append-only JSONL at `state_dir/audit.jsonl`. Common event schema — `ts`, `kind` (`sign`|`access`|`worker`), `action`, `subject`, `target`, `decision_id`, `outcome`, `source`. `record_event(**meta)` with a secret-material guard (reject token prefixes / high-entropy runs) so no value can ever land here. `read_events(*, since, kinds)` for the reader. - [x] Log rotation / bound (size or age) so it stays manageable. ### T2 — Instrument the actions ```task id: WARDEN-WP-0022-T02 status: done priority: high state_hub_task_id: "e7ae4037-ca79-4557-81f0-bfb8478ff647" ``` - [x] Emit an audit event from each ops-warden action: `warden sign` (cert issued — actor, type, ttl, backend, policy_decision_id), `warden access --fetch/--exec` (proxy — need id, owner, decision id), and the worker (`approve` → reply sent to X; tick → triage summary N/drafted/escalated). Fold the existing `signatures.log` / `access-audit.log` in as sources (keep back-compat; don't drop a record). - [x] Assert no secret value reaches the audit in any path (tests). ### T3 — `warden activity` command ```task id: WARDEN-WP-0022-T03 status: done priority: high state_hub_task_id: "4439bdd8-1461-47df-8b0b-048df7384a68" ``` - [x] `warden activity [--days N] [--kind sign|access|worker] [--json] [--hub]` — a single chronological view merging the audit log (and, for back-compat, `signatures.log` / `access-audit.log`); `--hub` also pulls recent ops-warden State Hub progress notes for the narrative. Human table by default; stable `--json` for agents. ### T4 — Tests, runbook, SCOPE ```task id: WARDEN-WP-0022-T04 status: done priority: medium state_hub_task_id: "bdfb8703-7a79-43e7-913b-19d61722f164" ``` - [x] Tests: audit append/read/rotation, the secret-material guard rejects values, the instrumented actions emit events, `warden activity` filtering + `--json` shape. - [x] `wiki/AuditTrail.md` (what's recorded, the no-secret guarantee, how to query, the linger + login-independence note). SCOPE entry. --- ## Acceptance - Every ops-warden action (sign, access proxy, worker send/tick) appends a metadata-only audit event; the secret-material guard is proven to reject values. - `warden activity --days 3` answers "what did ops-warden do" in one command; `--json` gives agents a stable shape. - No secret value appears in the audit log, ever. ## See also - `WARDEN-WP-0014` (`access-audit.log`), `WARDEN-WP-0020`/`0021` (the worker) - `wiki/OperatorAccessAssist.md` (the metadata-only audit principle) - `wiki/AuditTrail.md`