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,83 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-audit-stdout (api-audit-stdout)
**Package**: `@suaveplan/api-audit-stdout`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time.
## ADDED Requirements
### Requirement: JSON-per-line writes with HMAC signature
The package SHALL write each audit record as a single JSON line to the supplied writer (default `process.stdout`). Each line MUST include an HMAC-SHA-256 signature computed over the canonical bytes of the record using the configured `hmacKey`.
#### Scenario: written line is JSON with signature
- WHEN `sink.write({ ts: "...", type: "request.error", ... })` is called
- THEN exactly one line is appended to the writer AND that line parses as JSON AND it contains a `_hmac` field whose value verifies against the canonical bytes
**Verified by:** `src/sink/sink.test.ts::"writes JSON-per-line with HMAC signature"`
---
### Requirement: Record shape validated by Zod
The package SHALL validate each record against `AuditRecord` Zod schema BEFORE writing. Invalid records raise `AUDIT_STDOUT_INVALID_RECORD` and MUST NOT be written.
#### Scenario: missing required field rejected
- WHEN a record is missing `request_id`
- THEN `write()` throws `AUDIT_STDOUT_INVALID_RECORD` AND no line is written
#### Scenario: unknown field rejected
- WHEN a record carries an unknown top-level field
- THEN `write()` throws `AUDIT_STDOUT_INVALID_RECORD`
**Verified by:** `src/schema/schema.test.ts::"missing request_id is rejected"` AND `src/schema/schema.test.ts::"unknown field is rejected"`
---
### Requirement: Backpressure drops are observable
The package SHALL block the caller up to `maxQueueMs` (default 100ms) and then drop the record with `api.audit.dropped_total{sink="stdout"}` increment AND a single warning log per minute. Drops MUST NEVER be silent.
#### Scenario: drop increments dropped_total
- WHEN the writer is paused such that the queue exceeds `maxQueueMs`
- THEN the record is dropped AND `api.audit.dropped_total{sink="stdout"}` increments by 1
#### Scenario: warning log throttled to once per minute
- WHEN 100 records are dropped within 30 seconds (with `FakeClock`)
- THEN at most one warning is logged in that minute window
**Verified by:** `src/sink/sink.spec-conformance.test.ts::"drop increments dropped_total"` AND `src/sink/sink.spec-conformance.test.ts::"warning log throttled to once per minute"`
---
### Requirement: Successful writes counted with labels
The package SHALL increment `api.audit.records_total{sink="stdout", type, outcome}` once per successful write.
#### Scenario: successful write increments counter
- WHEN a valid record is written
- THEN `api.audit.records_total{sink="stdout", type, outcome}` increments by exactly 1 with the record's `type` and `outcome` as labels
**Verified by:** `src/sink/sink.spec-conformance.test.ts::"successful write increments records_total with type and outcome labels"`
---
### Requirement: HMAC verification detects tampering
The package SHALL produce HMAC signatures such that any modification to the record's canonical bytes invalidates the signature on verification with the same key.
#### Scenario: tampering invalidates HMAC
- WHEN a written line's `outcome` is flipped from `success` to `failure` after signing
- THEN HMAC verification with the original key returns `false`
**Verified by:** `src/hmac/hmac.test.ts::"tampering invalidates HMAC"`