2.7 KiB
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<typeof AuditRecordSchema>; structurally assignable toRequestAuditEnvelope.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
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:
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): Uint8ArraybuildSignedLine(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.