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

Relationships

#2049 Serve Audit Log - Phase 3: Real-time WebSocket streaming

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

Summary

Phase 3 of the serve audit log. Builds on Phase 1 (#2004) and Phase 2 (#2028) to add real-time event streaming over WebSocket, enabling live dashboard feeds and CLI tail mode.

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

Design enabler: design/enablers/serve-audit.md — Phase 3 line item: Real-time WebSocket streaming (audit.subscribe), --follow for swamp audit log

What Phase 2 shipped

  • WAL for durable delivery (audit_wal.ts + wal_sink.ts)
  • Chain hashing with sequence/digest fields and verify API
  • authorizeOrReject returns { allowed, decision } — every event captures the matched grant rule
  • All 106 handlers wrapped with audited()
  • Audit policy with four detail levels (none/metadata/request/requestResponse) and management/data tiers
  • Fail-secure mode (audit.fail-open: false)
  • audit.query and audit.verify server request types
  • swamp audit log and swamp audit verify CLI commands
  • Per-target retention config and WAL cursor management

What Phase 3 adds

1. Multi-sink support in AuditEmitter

The AuditEmitter (src/domain/serve_audit/audit_emitter.ts) currently throws if more than one sink is passed. Adding a WebSocket sink alongside the existing store/WAL sink requires removing this single-sink restriction.

The emitter should fan out to all registered sinks. Chain hashing happens once in the emitter before fan-out — sinks receive already-chained events. Each sink handles its own delivery independently (a slow WebSocket subscriber does not block store writes).

2. WebSocket sink (src/serve/audit_sinks/websocket_sink.ts)

A new sink that broadcasts audit events to subscribed WebSocket connections. Each subscription carries an optional filter (categories, principals, actions, outcomes) so clients only receive events they care about.

Key behaviors:

  • Subscriptions are per-connection — when a WebSocket disconnects, its subscription is automatically cleaned up
  • Filters are applied at delivery time, not at the emitter level — the sink receives all events and filters per subscriber
  • No durability guarantee — this is live streaming, not a replay mechanism. Missed events are queryable via audit.query.
  • Backpressure: if a subscriber's send buffer fills, events are dropped for that subscriber (not queued indefinitely)

3. audit.subscribe server request type

New protocol type in src/serve/protocol.ts:

audit.subscribe — starts a real-time stream of audit events over the existing WebSocket connection. The server sends audit events as they occur, filtered by the subscription parameters. The subscription stays active until the connection closes or the client sends an unsubscribe.

Filter shape matches audit.query: categories, principals, actions, outcomes, resourceKind.

4. swamp audit log --follow

Add a --follow flag to the existing swamp audit log CLI command. When set, the command first queries historical events matching the filters, then subscribes via audit.subscribe to stream new events as they arrive. Output renders incrementally — each event prints as it arrives, same format as the non-follow mode.

Ctrl+C cleanly unsubscribes and exits.

5. System-category events

Add a system category to AuditCategory (currently has: auth, access, execution, secrets, admin, data). System events are emitted automatically for infrastructure lifecycle:

  • Instance start (with version, config digest)
  • Instance stop (graceful shutdown)
  • Health state transitions (healthy/degraded/unhealthy)
  • HA join/leave (instance joining or leaving a cluster)

These events have principal kind system and are always at metadata audit level.

6. Handler for audit.subscribe (src/serve/handlers/audit_handlers.ts)

New handler that:

  • Validates the subscription filter
  • Registers the connection with the WebSocket sink
  • Sends an acknowledgment frame
  • Events stream as individual server messages until disconnect

Requires admin or read access on the audit resource (follows the same authorization pattern as audit.query).

Files to create

  • src/serve/audit_sinks/websocket_sink.ts + test — WebSocket broadcast sink with per-subscriber filtering
  • System event emission points in serve startup/shutdown/health code

Files to modify

  • src/domain/serve_audit/audit_emitter.ts — remove single-sink restriction, support multiple sinks
  • src/domain/serve_audit/audit_event.ts — add system to AuditCategory
  • src/domain/serve_audit/audit_policy.ts — classify system as management tier
  • src/domain/serve_audit/mod.ts — re-export new types
  • src/serve/protocol.ts — add audit.subscribe request and audit.event server message types
  • src/serve/handlers/audit_handlers.ts — add audit.subscribe handler (or new file)
  • src/serve/connection.ts — add audit.subscribe dispatch case, emit system events on connection lifecycle
  • src/serve/serve_config.ts — no config changes needed (WebSocket sink is always-on when audit is enabled)
  • src/cli/commands/audit_log.ts — add --follow flag with audit.subscribe integration
  • src/presentation/output/audit_log_output.ts — support incremental/streaming output mode

What it delivers

  • Real-time audit event stream over WebSocket with per-subscriber filtering
  • swamp audit log --follow for live tailing from the CLI
  • System lifecycle events (start, stop, health transitions, HA changes)
  • Multi-sink fan-out in the emitter (unblocks Phase 4 sinks too)
  • Foundation for the dashboard Activity panel (UI itself is deferred)

Subsequent phases (not in scope)

  • Phase 4: Webhook and syslog sinks, bulk export, HMAC
  • Phase 5: Extension sinks, alerting, compliance templates
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 8 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

9/8/2026, 6:56:38 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack729/8/2026, 3:55:04 AM

Sign in to post a ripple.