Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
# `hmac` — canonical bytes + HMAC-SHA-256 signing
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module produces the HMAC-SHA-256 tag attached to every audit
line. Signing operates on a *canonical* byte representation of the
record (keys serialised in a fixed order, `cause` omitted when
absent) so the same logical record always produces the same MAC under
the same key, regardless of how the source object was constructed.
Verification recomputes the MAC and constant-time-compares to the
hex-decoded signature.
The point isn't to encrypt the record — the line is plaintext JSON
written to stdout. The HMAC catches tampering downstream: a log
collector or storage tier that re-writes a field to mask an audited
action invalidates the MAC, and the auditor's verifier flags the
discrepancy. The key never leaves the host; rotation is the caller's
concern.
## Features
- `canonicalBytes(record)` — UTF-8 bytes of JSON with top-level keys
in design-doc §12.5 order and `cause` omitted when absent.
- `signRecord(record, key)` — lowercase hex HMAC-SHA-256, computed
via `@suaveplan/crypto-hash`'s `hmac("SHA-256", …)` primitive.
- `verifyRecord(record, key, signatureHex)` — constant-time match;
rejects bad-length, non-hex, and tampered inputs.
- `HMAC_FIELD` — the `"_hmac"` field name the sink appends to the
JSON line.
## Usage: Basic
```ts
import { signRecord, verifyRecord } from "@suaveplan/api-audit-stdout/hmac";
const key = new Uint8Array(32);
const tag = signRecord(record, key);
const ok = verifyRecord(record, key, tag);
```
## Usage: Advanced
```ts
import {
canonicalBytes,
HMAC_FIELD,
signRecord,
} from "@suaveplan/api-audit-stdout/hmac";
function emit(record: AuditRecord, key: Uint8Array): string {
// Equivalent to what the sink does internally, but caller-managed.
const line = JSON.parse(
new TextDecoder().decode(canonicalBytes(record))
) as Record<string, unknown>;
line[HMAC_FIELD] = signRecord(record, key);
return JSON.stringify(line);
}
```
## API Reference
- `canonicalBytes(record: AuditRecord): Uint8Array`
- `signRecord(record: AuditRecord, key: Uint8Array): string` — hex.
- `verifyRecord(record: AuditRecord, key: Uint8Array, sig: string): boolean`
- `HMAC_FIELD: "_hmac"`
## Implementation Notes
The canonical encoder lists top-level keys explicitly rather than
sorting alphabetically — the design doc fixes the order so callers
reading raw lines see fields in the documented sequence. Nested
objects use natural insertion order; their shape is locked by the
Zod schema so the JSON.stringify output is deterministic. `cause` is
omitted (not serialised as `null`) when absent so a `success` record
without `cause` and a `success` record with `cause: undefined`
produce the same MAC. Hex was chosen over base64 because hex is
case-insensitive trivially and stays ASCII-safe through every common
log pipeline.