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

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 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"