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

47 lines
3.0 KiB
Markdown

# `@suaveplan/api-audit-file` — file-rotating audit sink
**Package**: `@suaveplan/api-audit-file`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 8 — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
Per design doc §12.5, some operators (single-host deployments, on-prem environments without a log shipper) need an audit sink that writes to a local file with rotation. This package is that sink. It mirrors the `api-audit-stdout` contract (`audit` capability, JSON-per-line, HMAC-signed records) and adds size + time rotation.
## What changes
- ADDED: `createFileAuditSink({ path, hmacKey, clock, rotation })` — returns an `AuditSink`. Writes JSON-per-line with HMAC signature to `path`. Rotation policy: size-based (`maxSizeBytes`, default 100 MB) AND time-based (`maxAgeMs`, default 1 day). Rotated files are renamed to `<path>.<utc-ISO>.log` and optionally gzipped if `compress: true`.
- ADDED: Retention policy — `maxFiles` (default 30) keeps the most recent N rotated files; older files are deleted with a `api.audit.rotation_evicted_total{sink="file"}` increment.
- ADDED: Atomic-write strategy — writes go through a temp file path then `fsync` + rename for crash safety, OR an append-on-open `fd` model gated by `host.audit.file.appendMode` (`"strict-sync" | "append"`).
- ADDED: Same backpressure model as `api-audit-stdout`: bounded queue, observable drops via `api.audit.dropped_total{sink="file"}`.
- ADDED: Error codes (`AUDIT_FILE_*`): `INVALID_RECORD`, `WRITER_FAILED`, `QUEUE_FULL`, `ROTATION_FAILED`, `RETENTION_FAILED`, `PATH_NOT_WRITEABLE`.
## Out of scope
- Cross-host audit aggregation — separate package.
- Encryption at rest — caller handles (the sink writes plaintext + HMAC; disk-encryption is outside scope).
- Centralised log shipping (`fluentd`, `vector`, …) — operators wire the rotated files into those tools.
## Source mapping
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| File-rotating audit sink (`api-audit-file`) | §12.5 |
| HMAC-signed records | §12.5 |
| Same `audit` capability contract | §12.5 |
## Verification approach
- `src/sink/sink.test.ts` — write 1000 records, assert all are present, JSON-parseable, HMAC-valid.
- `src/rotation/rotation.test.ts` — exercise size rotation (write enough bytes to exceed `maxSizeBytes`) and time rotation (advance `FakeClock` past `maxAgeMs`); assert rotated file exists with the expected timestamp suffix.
- `src/retention/retention.test.ts` — create N+1 rotated files (with `FakeClock`), assert oldest is evicted and `api.audit.rotation_evicted_total` increments.
- `src/sink/sink.spec-conformance.test.ts` — drop policy mirrors `api-audit-stdout`; observability assertions.
- `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 = "file"`.