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 anAuditSinkimplementing theauditcapability. Eachwrite(record)emits a single JSON line to the supplied writer (defaultprocess.stdout). The line is HMAC-signed (HMAC-SHA-256 over canonical bytes) usinghmacKeyso tampering is detectable. - ADDED:
AuditRecordZod schema — locks the wire shape from §12.5. Records that fail validation raiseAUDIT_STDOUT_INVALID_RECORDand are NOT written. - ADDED: Backpressure policy — the writer is async; the sink blocks the caller only up to
maxQueueMs(default 100ms), then drops withapi.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 raiseAUDIT_STDOUT_INVALID_RECORD.src/sink/sink.spec-conformance.test.ts— drop policy incrementsapi.audit.dropped_total{sink="stdout"}exactly once per dropped record AND emits the warning once per minute (asserted withFakeClock).src/schema/schema.test.ts— every required field present; every optional field accepted; unknown fields rejected (exactOptionalPropertyTypesparity).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).