Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
<!-- markdownlint-disable MD024 -->
# 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 `maxSizeBytes` is 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 `maxAgeMs` is 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 `maxFiles` is 3 AND a 4th rotation occurs
- THEN the oldest rotated file is deleted AND `api.audit.rotation_evicted_total` increments 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 `path` resolves to a directory the process cannot write
- THEN `createFileAuditSink` throws `AUDIT_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"`