3.6 KiB
Capability Spec: api-audit-file
Package: @suaveplan/api-audit-file
Status: ACCEPTED
Every requirement below carries a
**Verified by:**line.
Requirements
Requirement: JSON-per-line file writes with HMAC signature
The package SHALL write each audit record as a single JSON line to the configured file path with an HMAC-SHA-256 signature computed over the canonical record bytes.
Scenario: 1000 records write as 1000 JSON lines
- WHEN 1000 valid records are written
- THEN the file contains 1000 lines, each parses as JSON AND each line's HMAC verifies
Verified by: src/sink/sink.test.ts::"writes 1000 records as 1000 verified JSON lines"
Requirement: Size-based rotation
The package SHALL rotate the file when its size reaches rotation.maxSizeBytes (default 100 MB). The rotated file is renamed to <path>.<utc-ISO>.log and a new empty file is opened. Subsequent writes go to the new file.
Scenario: write past maxSizeBytes triggers rotation
- WHEN
maxSizeBytesis 1024 AND 2048 bytes of records are written - THEN a rotated file with an ISO-timestamp suffix exists AND subsequent writes go to a fresh file
Verified by: src/rotation/rotation.test.ts::"size rotation produces timestamp-suffixed file"
Requirement: Time-based rotation
The package SHALL rotate when the current file's age (against the supplied Clock) reaches rotation.maxAgeMs (default 24h). Time rotation uses FakeClock in tests for determinism.
Scenario: clock advance past maxAgeMs triggers rotation
- WHEN
maxAgeMsis 60_000ms AND the clock advances by 61_000ms - THEN the next write rotates first AND lands in the new file
Verified by: src/rotation/rotation.test.ts::"time rotation triggers on clock advance"
Requirement: Retention enforces maxFiles
The package SHALL retain at most rotation.maxFiles (default 30) rotated files. Older files are deleted with api.audit.rotation_evicted_total{sink="file"} increment.
Scenario: N+1 rotated files evicts the oldest
- WHEN
maxFilesis 3 AND a 4th rotation occurs - THEN the oldest rotated file is deleted AND
api.audit.rotation_evicted_totalincrements by 1
Verified by: src/retention/retention.test.ts::"evicts oldest beyond maxFiles and increments counter"
Requirement: Path-not-writeable surfaces at boot
The package SHALL fail fast at sink construction if path's directory is not writeable, raising AUDIT_FILE_PATH_NOT_WRITEABLE.
Scenario: unwriteable path raises at boot
- WHEN
pathresolves to a directory the process cannot write - THEN
createFileAuditSinkthrowsAUDIT_FILE_PATH_NOT_WRITEABLE
Verified by: src/sink/sink.test.ts::"unwriteable path raises AUDIT_FILE_PATH_NOT_WRITEABLE"
Requirement: Drops are observable
The package SHALL drop records that exceed maxQueueMs and increment api.audit.dropped_total{sink="file"}. Drops MUST NEVER be silent.
Scenario: drop increments dropped_total
- WHEN the file writer is stalled long enough for the queue to overflow
- THEN the record is dropped AND
api.audit.dropped_total{sink="file"}increments by 1
Verified by: src/sink/sink.spec-conformance.test.ts::"drop increments dropped_total with sink label file"
Requirement: HMAC verification detects tampering
The package SHALL produce HMACs such that any byte change in the record invalidates verification with the same key.
Scenario: tampering invalidates HMAC
- WHEN one byte in a written line is flipped
- THEN HMAC verification fails
Verified by: src/hmac/hmac.test.ts::"tampering invalidates HMAC"