Files
2026-05-27 23:29:53 +01:00

3.1 KiB

@suaveplan/api-audit-stdout — stdout audit sink

Package: @suaveplan/api-audit-stdout Status: PROPOSED Type: new-package Stage: 8 — see umbrella openspec/changes/api-plugin-foundation/

Why

Per design doc §12.5, every state-changing admin action AND every non-2xx response on an auth-required route produces an audit record with a fixed JSON shape (ts, type, principal, tenant, request_id, trace_id, subject, outcome, cause). The sink is pluggable via the audit capability. The default sink writes JSON-per-line to stdout because container runtimes redirect stdout anyway — operators wire it into their existing log pipeline without touching the host.

What changes

  • ADDED: createStdoutAuditSink({ writer, hmacKey, clock }) — returns an AuditSink implementing the audit capability. Each write(record) emits a single JSON line to the supplied writer (default process.stdout). The line is HMAC-signed (HMAC-SHA-256 over canonical bytes) using hmacKey so tampering is detectable.
  • ADDED: AuditRecord Zod schema — locks the wire shape from §12.5. Records that fail validation raise AUDIT_STDOUT_INVALID_RECORD and are NOT written.
  • ADDED: Backpressure policy — the writer is async; the sink blocks the caller only up to maxQueueMs (default 100ms), then drops with api.audit.dropped_total{sink="stdout"} increment and a single warning log per minute. Drops are NEVER silent.
  • ADDED: Error codes (AUDIT_STDOUT_*): INVALID_RECORD, WRITER_FAILED, QUEUE_FULL.
  • ADDED: api.audit.records_total{sink="stdout", type, outcome} counter — one increment per successful write.

Out of scope

  • File-rotating sink (that's api-audit-file).
  • Long-term audit storage (separate package).
  • HMAC key management — caller supplies the key. Rotating it without losing replay capability is a host-level concern.

Source mapping

Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.

Requirement Design doc section
Audit record JSON shape (ts, type, principal, …) §12.5
Default sink is api-audit-stdout §12.5
HMAC-signed records §12.5
audit capability slot §12.5

Verification approach

  • src/sink/sink.test.ts — writes valid records, asserts JSON-per-line output with HMAC suffix; invalid records raise AUDIT_STDOUT_INVALID_RECORD.
  • src/sink/sink.spec-conformance.test.ts — drop policy increments api.audit.dropped_total{sink="stdout"} exactly once per dropped record AND emits the warning once per minute (asserted with FakeClock).
  • src/schema/schema.test.ts — every required field present; every optional field accepted; unknown fields rejected (exactOptionalPropertyTypes parity).
  • src/hmac/hmac.test.ts — canonical bytes are deterministic across runs; HMAC verifies with the same key; tampering flips one bit and the verification fails.
  • tests/dep-graph.test.ts — runtime deps (api-types, crypto-hash, error, logger, zod).

Migration impact

None — additive only. api-host registers this when host.audit.sink = "stdout" (the default).