ops-warden/wiki/AuditTrail.md
tegwick 94f32bd160
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Review layer model v0.6; publish the PEP stance map §6.4 requires
All three v0.4 findings were acted on — §9.1 split into pending/declared-gap and
§5's scope rule adopted as recommended and credited, and §9.6 ruled via the
load-bearing/attributive distinction with ops-warden's `# audit must not block
signing` named as the estate's live example.

Checked the favourable ruling rather than accepting it. §9.6's test is "no
control branches on its presence": the only consumer of audit.jsonl is `warden
activity`, which displays. Nothing gates on a signing record, so the lane is
genuinely attributive. AuditTrail.md now records the ruling instead of the open
question, and states that the trade must be revisited if a control ever gates on
the trail.

CONFORMANCE ACTION. §6.4 obligation 3 requires a stance map "published rather
than held in code", and requires every PEP-shaped consumer to publish one so the
maps can be inventoried — naming ADR-0009 as the reference shape. ops-warden was
not doing it: the map lived in PolicyConfig.failure_modes, a dataclass default.
Not a code comment, but not published either.

pep-stance.yaml publishes it, and the test asserts the published map EQUALS the
shipped default. A published map that may drift from the code is worse than no
map, because it invites reliance it cannot support.

Two findings sent to gate-house, in history/2026-08-29-layer-model-v06-review.md:
§6.4 obligation 1 (no side effect without a decision record) contradicts
obligation 3 and §9.3, with ops-warden's blessed fail-open stance as the
instance; and §6.4 mandates a stance-map inventory in §13 that §13 does not
implement — where ops-warden is currently the only PEP to have published one.

402 tests pass, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4014535@bnt-lap001
Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c
2026-08-29 10:20:49 +02:00

86 lines
No EOL
3.4 KiB
Markdown

# Audit Trail — Unified ops-warden Activity
Date: 2026-07-01
Workplan: WARDEN-WP-0022
ops-warden records **metadata only** for every action it performs. No token, key,
cert body, or other secret value ever lands in the audit log.
---
## What is recorded
| Kind | Source actions | Typical fields |
| --- | --- | --- |
| `sign` | `warden sign`, `warden issue`, `cert_command` | actor, backend, TTL, `policy_decision_id` |
| `access` | `warden access --fetch` / `--exec` | need id, owner repo, subject, decision id, outcome |
| `worker` | `warden worker` tick, approve, full-auto execute | triage counts, draft id, outcome |
| `hub` | State Hub progress notes (`--hub`) | summary, author, event type |
### Storage
- **Primary:** `{state_dir}/audit.jsonl` — append-only JSONL (default
`~/.local/state/warden/audit.jsonl`)
- **Legacy (merged for back-compat):** `signatures.log`, `access-audit.log`
Rotation: when `audit.jsonl` exceeds 5 MiB it is renamed to `audit.jsonl.1` and a
fresh file starts.
### Secret-material guard
`record_event()` rejects fields that look like secret values (known token prefixes,
high-entropy runs). Signing and proxy paths swallow audit failures so gatekeeping
never blocks the primary action — but tests prove values cannot be written.
> **Absence of a record is not evidence of absence.** Because emission never
> blocks the primary action (`src/warden/ca.py`), a failed append loses the event
> while the action still happens. This trail proves that the records it holds were
> not altered or truncated; it does **not** prove that every action produced one.
> Do not reason from a missing entry. This is the estate-wide bound in
> `security-layer-model_v0.4` §9.6 — completeness is the source's obligation, and
> **Ruled 2026-08-29** (`security-layer-model_v0.6` §9.6): this trail is
> **attributive**, not load-bearing — no control branches on the presence of a
> signing record — so the non-atomic trade is legitimate, and the obligations are
> to declare it (this note) and never to claim completeness. Atomicity is required
> only where a control's soundness depends on an event being present or absent.
> Registered in the standard's §13 open-gap table as self-declared. If a future
> control ever gates on this trail, the trade must be revisited before it ships.
---
## Query
```bash
# Human table — last 7 days
warden activity
# Filter and JSON for agents
warden activity --days 3 --kind sign --json
warden activity --days 7 --hub --json
```
| Flag | Purpose |
| --- | --- |
| `--days N` | Look back N days (default 7) |
| `--kind sign\|access\|worker\|hub` | Filter by event kind |
| `--json` | Stable JSON array for automation |
| `--hub` | Include recent State Hub progress notes mentioning ops-warden |
---
## Linger and login independence
The coordination worker can run under a `systemd --user` timer with linger enabled
(WARDEN-WP-0021). Audit events from worker ticks appear with `kind: worker`.
Full **logged-out** operational value still depends on State Hub and tunnels being
reachable without an interactive login (State Hub on railiance01, `cust-wp-0011`).
The audit trail is local-first; `--hub` adds narrative context when the hub is up.
---
## See also
- `wiki/OperatorAccessAssist.md` — metadata-only principle for access proxy
- `wiki/PolicyGatedSigning.md``policy_decision_id` on sign events
- `wiki/playbooks/scheduled-worker.md` — worker timer and review loop