Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneesstack72

Relationships

#2028 Serve Audit Log - Phase 2: WAL, chain hashing, query API, complete coverage

Opened by stack72 · 9/5/2026· Shipped 9/7/2026

Summary

Phase 2 of the serve audit log. Builds on the event pipeline shipped in Phase 1 (#2004 / PR #2379) to add durability guarantees, tamper evidence, complete handler coverage, a query API, and CLI commands.

Design doc: https://claude.ai/code/artifact/40e51500-334b-40ef-89a9-dbd9185a109e

Design enabler: design/enablers/serve-audit.md — Phase 2 line item: Write-ahead log, chain hashing, query API, complete handler coverage

What Phase 1 shipped

  • AuditEvent flat entity with 6 categories, request/response stages, UUID ids
  • RingBuffer, AuditEmitter, StoreSink with batched date-partitioned JSONL writes
  • authorizeOrReject emits denial events (covers all 106 handlers)
  • audited() wrapper on 19 of 106 handler cases (auth, access, execution, secrets, admin, and some config/data)

What Phase 2 adds

1. Write-ahead log (src/infrastructure/persistence/audit_wal.ts)

Phase 1 has no WAL — if the remote store is unreachable, events in the ring buffer are lost on process exit. The WAL spills events to local disk when the ring buffer fills or the remote backend is down, then replays them on reconnection or restart.

  • Append-only JSONL segments on local disk
  • Per-target delivery cursors track what has been flushed
  • On restart, replay unflushed WAL segments to all store targets
  • Delete WAL segments once drained to all targets

2. Chain hashing and tamper evidence

Phase 1 events have no integrity fields. Add per-instance chain hashing so a broken chain signals tampering or data loss.

Extend AuditEvent with:

  • sequence: number — per-instance monotonic counter
  • digest: string — SHA-256 of previous digest concatenated with current event canonical JSON
  • version: 1 — schema version for forward compatibility

The AuditEmitter maintains the chain state per instance. The first event uses a well-known seed. audit.verify replays the chain and reports any breaks.

3. Enrich authorizeOrReject with the access decision

authorizeOrReject (at src/serve/handlers/shared.ts) currently returns boolean and discards the AccessDecision object. Change the return type to { allowed: boolean; decision: AccessDecision | null } so audit events can capture which grant rule matched.

Add a decision field to AuditEvent with action, resource selector, matched rule (grantId or no-match), effect (allow/deny), and principal groups at decision time.

This makes every why was this allowed/denied question answerable from the audit log. All ~117 authorizeOrReject call sites update to destructure .allowed.

4. Complete handler coverage (remaining ~87 cases)

Phase 1 wrapped 19 handlers with audited(). Wrap the remaining ~87 cases in src/serve/connection.ts. Same pattern — no handler internals change. Covers data ops, model reads, workflow management, vault reads, worker/extension/admin/run/report ops.

5. Audit policy with detail levels

Add four detail levels controlling how much context each event captures:

  • None: Event is not recorded
  • Metadata: Who, what, when, outcome — no request or response bodies
  • Request: Metadata + request parameters
  • RequestResponse: Metadata + request + response bodies

New types: AuditLevel, AuditPolicyRule, AuditPolicy. Rules are evaluated in order, first match wins. Also add management vs data event tier classification.

6. Fail-secure mode

When audit.failOpen is false, serve refuses to process any request it cannot durably audit to at least one store target. Default is true (fail-open) — fail-secure is opt-in for regulated environments.

7. Query API

New server request types in src/serve/protocol.ts:

  • audit.query — paginated query with filters (time range, principal, category, action, resource, outcome)
  • audit.verify — verify chain integrity for a time range, reports broken chains or missing events

8. CLI commands

  • swamp audit log — query the audit log with filters, --follow for live tail
  • swamp audit verify — check chain integrity for a time range

Both follow existing patterns: cliffy command, withRemoteOptions(), requestServerResponse().

9. Per-target retention and GC

Phase 1 StoreSink writes JSONL files but never deletes them. Add per-target retention.days config and date-partition garbage collection (delete entire partitions older than retention, never individual events).

What it delivers

  • Durable delivery — events survive process restarts and backend outages via WAL
  • Tamper evidence — chain hashing detects modified or missing events
  • Full coverage — all 106 handlers emit audit events
  • Access decision capture — every allow/deny is traceable to a specific grant rule
  • Operator control — audit policy controls detail granularity per action/category
  • Fail-secure option — regulated environments can refuse unauditable requests
  • Queryable — swamp audit log and swamp audit verify CLI commands
  • Retention management — automatic cleanup of expired partitions

Subsequent phases (not in scope)

  • Phase 3: Real-time WebSocket streaming (audit.subscribe)
  • Phase 4: Webhook and syslog sinks, bulk export, HMAC
  • Phase 5: Extension sinks, alerting, compliance templates
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 42 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

9/7/2026, 7:58:05 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack729/5/2026, 4:02:09 AM

Sign in to post a ripple.