Initial Commit
This commit is contained in:
47
openspec/archive/2026-05-26-api-audit-stdout/proposal.md
Normal file
47
openspec/archive/2026-05-26-api-audit-stdout/proposal.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# `@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).
|
||||
Reference in New Issue
Block a user