136 lines
7.0 KiB
Markdown
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).
|