48 lines
3.1 KiB
Markdown
48 lines
3.1 KiB
Markdown
# `@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).
|