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,79 @@
# `schema`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the Zod schema and canonical-bytes function that bracket every
audit record before it is signed and written. The schema mirrors the
`RequestAuditEnvelope` interface exported by
`@suaveplan/api-host/request-pipeline`; the canonical-bytes function
gives every other module a deterministic input for HMAC signing,
file-line construction, and signature verification.
The package re-validates at the sink boundary even though the host
constructs envelopes from typed data — operators frequently hand-craft
audit records in tests, replay tooling, or migration scripts. The
boundary check makes corrupt records fail loudly with a typed
`AUDIT_FILE.INVALID_RECORD` instead of silently bypassing the HMAC.
## Features
- `AuditRecordSchema` — strict Zod object accepting only the
envelope's documented fields. Unknown keys reject.
- `AuditRecord` — `z.infer<typeof AuditRecordSchema>`; structurally
assignable to `RequestAuditEnvelope`.
- `canonicalise(record)` — UTF-8 bytes of the record JSON-stringified
with sorted top-level keys.
- `buildSignedLine(bytes, hmacHex)` — emits the on-disk JSON line
carrying the record + HMAC, with a trailing `\n`.
## Usage: Basic
```ts
import {
AuditRecordSchema,
buildSignedLine,
canonicalise,
} from "@suaveplan/api-audit-file/schema";
const record = AuditRecordSchema.parse(rawEnvelope);
const bytes = canonicalise(record);
const line = buildSignedLine(bytes, "deadbeef...");
```
## Usage: Advanced
Verify an on-disk line:
```ts
import { hmac } from "@suaveplan/crypto-hash";
import { AuditRecordSchema, canonicalise } from "@suaveplan/api-audit-file/schema";
const { record, hmac: hmacHex } = JSON.parse(lineFromDisk);
const parsed = AuditRecordSchema.parse(record);
const bytes = canonicalise(parsed);
const expected = Buffer.from(hmac("SHA-256", key, bytes)).toString("hex");
if (expected !== hmacHex) {
throw new Error("hmac mismatch — record was tampered");
}
```
## API Reference
- `AuditRecordSchema: z.ZodObject` (strict)
- `type AuditRecord = z.infer<typeof AuditRecordSchema>`
- `canonicalise(record: AuditRecord): Uint8Array`
- `buildSignedLine(canonicalBytes: Uint8Array, hmacHex: string): string`
## Implementation Notes
The canonical bytes drop `undefined` values for the optional `cause`
slot so the absence of an error never affects the signature. Top-level
keys are sorted alphabetically to give every implementation (TypeScript,
Go log-reader, Python forensic tooling) the same input bytes for
verification regardless of construction order. `principal.scopes` is
preserved in array order — the order is semantically meaningful in the
pipeline so we do not collapse it.