# `schema` _(no submodules)_ ## 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`; 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` - `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.