Files
2026-05-27 23:29:53 +01:00

136 lines
7.0 KiB
Markdown

# Enterprise hardening — `@suaveplan/packer` integrity cluster
**Package**: `@suaveplan/packer`
**Status**: PROPOSED
**Type**: feature
**Design**: (inline below — small scope)
**Spec delta**: ./specs/packer/spec.md
## Why
`@suaveplan/packer` currently ships ed25519 signing and a SHA-256 per-chunk wire
hash, but neither the whole-file nor the chunked paths emit a content-addressable
digest scheme or a bundle-level Merkle root. There is also no OpenTelemetry
instrumentation on the public entrypoints (no counters, no histograms, no spans)
and every user-visible error carries a hard-coded English string. The
`enterprise/packer.md` audit calls all three out as gaps blocking the
ENTERPRISE label.
This is cluster #1 of a 3-cluster enterprise hardening of `@suaveplan/packer`
(integrity, resilience, extensibility). Clusters #2 and #3 are deliberately out
of scope here — see "Out of scope" below.
## What changes
- ADDED: `src/integrity/integrity.ts` — `entryDigestBlake3(data)` and
`merkleRootBlake3(digests)` primitives over `@noble/hashes/blake3`.
- ADDED: `integrity?: PackedDataIntegrity` field on `PackedDataMetadata`
carrying `{ entryDigest, merkleRoot }`. Always populated by `Packer.pack`.
- ADDED: `PACKER_INTEGRITY_FAILED` error code; thrown by `Unpacker.unpack` when
integrity is missing or does not match the recomputed digests.
- ADDED: `entryDigest` field on `ManifestChunkEntry`; `merkleRoot` field on
`Manifest`. `buildManifest` populates both; `verifyManifest` continues to
cover them via the existing canonical-encode signature.
- ADDED: `unpackChunksWithManifest(chunks, manifest, opts)` — chunked unpacker
variant that verifies per-chunk BLAKE3 against `manifest.chunks[i].entryDigest`
and the manifest's Merkle root before yielding plaintext.
- ADDED: `src/observability/observability.ts` — OTel instruments
(`packer_bundle_size_bytes`, `packer_entry_count`, `packer_pack_total`,
`packer_unpack_total`, `packer_integrity_failure_total`,
`packer_pack_duration_ms`, `packer_unpack_duration_ms`,
`packer_entry_decode_ms`) and `withPackerSpan` helper.
- ADDED: `PackerObservability` injection on `PackerOptions` and on
`PackChunkedOptions` / `UnpackChunkedOptions` — defaults to a no-op recorder
pulled from `@opentelemetry/api`.
- ADDED: `src/i18n/messages.ts` — English-defaults message catalog keyed by
`packer.error.<code>.<scenario>`. Constructor option
`PackerOptions.messageResolver?: MessageResolver` (and equivalent on chunked
option bundles); default resolver looks up the local catalog.
- MODIFIED: every `throw new PackerError(CODE, "english string")` site now
resolves the message through the injected `MessageResolver`.
## Out of scope
- `@suaveplan/retry` / `@suaveplan/idempotency` integration → cluster #2 (resilience).
- Clock / Rng injection audit (no `Date.now()` / `Math.random()` at module
scope) → cluster #2.
- Whole-file `AbortSignal` propagation → cluster #2.
- `@suaveplan/schema-registry` integration → cluster #3 (extensibility).
- `@suaveplan/feature-flags` integration → cluster #3.
- Plugin / extension interface → cluster #3.
- `fast-check` property tests for round-trip invariants → cluster #3.
- mitata benchmarks → cluster #3.
## Source mapping
This change derives from `../suaveplan/enterprise/packer.md`.
| Requirement | Source lines | Notes |
|---|---|---|
| PACKER-18 (BLAKE3 per-entry + Merkle root, mandatory verify) | L40 | Verbatim — "per-entry BLAKE3 plus bundle-level Merkle root; verification is mandatory on read". |
| PACKER-19 (`PACKER_INTEGRITY_FAILED`) | L45 | Reworded — audit line: "Structured errors … with stable error codes for integrity failures". |
| PACKER-20 (OpenTelemetry counters / histograms / spans) | L44 | Verbatim — "OpenTelemetry instrumentation via `@suaveplan/opentelemetry` for all public entrypoints (bundle size, entry count, pack/unpack latency)". |
| PACKER-21 (i18n-keyed messages, `MessageResolver` injection) | L49 | Verbatim — "i18n-ready — no hard-coded user-visible strings, all messages keyed". |
### Requirements with no source line
None — every requirement in this cluster maps directly to an audit bullet.
## Verification approach
- `src/integrity/integrity.test.ts` — pins BLAKE3 KAT + Merkle root vectors
(PACKER-18 primitive correctness).
- `src/integrity/integrity.spec-conformance.test.ts` — pins the wire-level
integrity-field shape on `PackedDataMetadata` and `ManifestChunkEntry`
(PACKER-18 surface).
- `src/packer/packer.spec-conformance.test.ts` (extended) — `pack` always
emits `integrity`; `unpack` rejects with `PACKER.INTEGRITY_FAILED` on
mismatch and absence (PACKER-18, PACKER-19).
- `src/chunked/unpacker/unpacker-with-manifest.spec-conformance.test.ts` —
`unpackChunksWithManifest` verifies per-chunk BLAKE3 + manifest Merkle root
and rejects mutations (PACKER-18, PACKER-19).
- `src/observability/observability.spec-conformance.test.ts` — uses a real
in-memory `MeterProvider` + `InMemoryMetricExporter` and a real
`BasicTracerProvider` + `InMemorySpanExporter`, asserts every named counter
/ histogram / span fires from production paths (PACKER-20).
- `src/i18n/messages.spec-conformance.test.ts` — pins the catalog shape and
proves a custom `MessageResolver` is consulted at every throw site
(PACKER-21).
## Migration impact
Public-surface additions only. Two consumer packages (`@suaveplan/drive-plugin-chunks`,
`@suaveplan/drive-webrtc`) are audited and the sweep is part of the gates; no
existing call site changes shape. Whole-file `unpack` now rejects packed data
that lacks the new `integrity` field — this is the only consumer-visible
behavior change and is intentional per the cluster scope ("verification is
mandatory on read"). A `## Migration` section below names the rollover.
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/packer` to enumerate.
- `@suaveplan/drive-plugin-chunks` — [x] tests run / [x] docs reviewed / [ ] no action needed
- `@suaveplan/drive-webrtc` — [x] tests run / [x] docs reviewed / [ ] no action needed
If `tests run` is unchecked for any dependent, the change MUST NOT be folded
into the canonical spec.
## Migration
- **Old behavior**: `Unpacker.unpack(packed)` succeeded on any `PackedData`
whose envelope verified through the existing signature / AEAD pipeline,
regardless of whether `metadata.integrity` was present.
- **New behavior**: `Unpacker.unpack(packed)` throws
`PackerError(PACKER.INTEGRITY_FAILED)` when `metadata.integrity` is missing
OR when the recomputed `(entryDigest, merkleRoot)` does not match the
envelope's recorded values.
- **Search command**: `grep -rln "Unpacker\|@suaveplan/packer" packages/ | grep -v "/dist/\|/coverage/"`.
- **Replacement**: regenerate stale packed bundles with `Packer.pack` (which
always emits `integrity`), or — for callers that intentionally ship
pre-integrity envelopes — pass `verifyIntegrity: false` on
`UnpackOptions` (additive, additive, additive: this opt-out exists to make
the rollover one-line; the default is verify).