# bitpacker-hardening — Zod surface, OTel, property tests, benchmarks **Packages:** `@suaveplan/bitpacker`, `@suaveplan/bitpacker-strategies`, `@suaveplan/bitpacker-validation`, `@suaveplan/bitpacker-game`, `@suaveplan/bitpacker-streaming`, `@suaveplan/bitpacker-compressed`, `@suaveplan/bitpacker-network`, `@suaveplan/bitpacker-schema`, `@suaveplan/bitpacker-delta` **Status:** PROPOSED **Type:** hardening (no behaviour changes; no public-surface breaks) **Carved from:** `bitpacker-full-port` triage on 2026-05-03 **Scope decisions baked in (2026-05-03):** - Feature-flags integration is **DROPPED** from this change. Every policy choice in the bitpacker family is already JSON-configurable (compression algorithm name + options object, network simulation profile, named adapter registration, registry / storage adapter selection). Feature-flags would only add value for runtime per-user / per-tenant rollout, which is not a legitimate concern for deterministic codecs. If a future risky primitive arrives, gate it at adapter-registration time on the consumer's bootstrap, not via runtime flags. ## Why The bitpacker family is functionally complete and at 100/100/100/100 coverage across every package. Six hardening items from the original `bitpacker-full-port` proposal remain genuinely undone — they would have been done as part of the full port but the modular work prioritised behaviour over instrumentation. Each is small in isolation; bundled together they amount to one focused hardening pass that makes the family enterprise-ready by genesis policy. None of these items change any wire format, public API shape, or behaviour. Every item is additive instrumentation or test infrastructure. ## What changes ### 1. Zod schemas for every public type Every public input/output type across the family gets a Zod schema. Today only `bitpacker-crypto`, `bitpacker-delta`, and `bitpacker-schema` have them; `bitpacker` core, `bitpacker-strategies`, `bitpacker-validation`, `bitpacker-game`, `bitpacker-streaming`, `bitpacker-compressed`, and `bitpacker-network` do not. - ADDED: per-package `*.schema.ts` modules co-located with the implementation file (`schema-name.ts` → `schema-name.schema.ts` + `schema-name.schema.md`). - ADDED: `validate*` helpers around boundaries that today accept `unknown` from external callers (`pack(data: unknown, schema: BitPackerSchema)`, registry `register(adapter)`, `unpack` input bytes). - ENFORCED: input validation runs only at package boundaries, not internal recursion (per CLAUDE.md "validate at boundaries only"). ### 2. Register Zod schemas with `@suaveplan/schema-registry` Once the Zod schemas exist, register the canonical wire-format schemas with `@suaveplan/schema-registry` so cross-version consumers can negotiate. - ADDED: per-package `register-schemas.ts` entry that contributes its public schemas to the registry under namespace `"bitpacker"` with the package name as the schema name and `version: 1`. - ADDED: `tests/registered.test.ts` per package asserting the schemas are present after `import "@suaveplan//register-schemas"`. ### 3. OTel instrumentation across core Only `bitpacker-crypto` has OTel spans today. The bitpacker core, strategy chain, and the four runtime packages (`-streaming`, `-compressed`, `-network`, `-game`) get spans + counters + histograms via `@suaveplan/opentelemetry`. - ADDED: per-package OTel module (`/src/otel/instruments.ts`) declaring `Counter`, `Histogram`, and span names. - ADDED: hot-path emission at pack/unpack boundaries (one span per top-level call; per-chunk events nested inside). - ASSERTED: emission is verified with a real `MeterProvider` from `@suaveplan/testing` — never `vi.mock`. ### 4. `fast-check` property tests The family today verifies behaviour with concrete examples. Property tests close the gap on round-trip soundness for every schema kind. - ADDED: `fast-check` as a `devDependency` on every bitpacker package. - ADDED: per-package `tests/property/*.property.test.ts` files exercising round-trip pack/unpack against generated input. - ADDED: arbitrary generators for each schema kind in `@suaveplan/testing/bitpacker-arbitraries` so tests across packages share the same generator surface. ### 5. `mitata` benchmark suite + CI regression gate Add steady-state benchmarks for every public encoder/decoder, with baselines committed to the repo. - ADDED: `mitata` as a `devDependency` on every bitpacker package. - ADDED: per-package `bench/*.bench.ts` files with `bench(name, fn)` per public operation. - ADDED: `scripts/bench/bitpacker-baselines.json` baseline file. - ADDED: CI gate (Turbo task `bench:check`) that fails if any benchmark regresses by more than 10% from baseline. ### 6. `@suaveplan/feature-flags` integration — DROPPED Resolved 2026-05-03 (see scope decisions at top of proposal). Every policy choice in the bitpacker family is already configurable via plain JSON option bundles or named-adapter registration; runtime per-user / per-tenant rollout is not a legitimate concern for deterministic codecs. If a future risky primitive arrives, gate it at adapter-registration time at the consumer's bootstrap rather than via runtime flag evaluation. No code change in this section. ## Out of scope - 47 source files in the bitpacker family lack a sibling `.md` (bitpacker 30, bitpacker-validation 9, bitpacker-game 7, bitpacker-crypto 1). Documentation pass tracked as a separate `bitpacker-doc-pass` change. - Wire-format-version forward-compat semantics for `_version` strategy. Today the strategy enforces version match; "tolerate newer version with default-ed missing fields" is a feature decision, not hardening. Tracked separately as a future `bitpacker-version-tolerance` change. - Tree-shake assertion test on the rewritten `index.ts`. Vite already verifies tree-shaking via dts-only outputs; an explicit assertion adds little value. ## Source mapping This change derives entirely from the unchecked items in `openspec/changes/bitpacker-full-port/tasks.md` Phase 3 (the "Enterprise Hardening" section). No upstream source lines — every item is an above-baseline genesis-policy addition. | Requirement | Source | Notes | |---|---|---| | Zod schemas for every public type | `bitpacker-full-port/tasks.md` Phase 3 #2 | Above-baseline; genesis policy | | Register with schema-registry | `bitpacker-full-port/tasks.md` Phase 3 #3 | Above-baseline; genesis policy | | OTel across core | `bitpacker-full-port/tasks.md` Phase 3 #5 | Above-baseline; genesis policy | | fast-check property tests | `bitpacker-full-port/tasks.md` Phase 3 #8 | Above-baseline; genesis policy | | mitata benchmark suite + gate | `bitpacker-full-port/tasks.md` Phase 3 #9-10 | Above-baseline; genesis policy | | feature-flags integration | `bitpacker-full-port/tasks.md` Phase 3 #11 | DROPPED — JSON-configurable boundaries cover every legitimate need | ## Verification approach - Per-package `tests/registered.test.ts` for the schema-registry items. - Per-package OTel emission tests using a real `MeterProvider` from `@suaveplan/testing`. - Per-package `tests/property/*.property.test.ts` files using `fast-check`. - Per-package `bench/*.bench.ts` files with `mitata` baselines committed. - `bunx turbo bench:check --filter=...@suaveplan/bitpacker*` as the CI gate. - The 100/100/100/100 coverage gate continues to apply at package level. ## Migration impact None — additive only. No public surface changes; no behaviour changes; no consumer is affected. Every change here is instrumentation or test infrastructure. ## Estimated scope - ~30-40 new files (one Zod schema module + one OTel module + one property-test file + one bench file per package) - ~2-3 new dependencies per package (`fast-check`, `mitata`, possibly `@suaveplan/opentelemetry` if not already wired) - Zero source-file modifications outside of the new modules - One new Turbo task (`bench:check`) - One CI workflow update ## Migration Phase 8 introduces a single behaviour-affecting addition to `@suaveplan/bitpacker-strategies`: ### `_version` strategy now accepts optional `_tolerance` and `_allowedVersions` fields **Old behaviour:** `{ _version: number; _schema: BitPackerSchema }` — `unpack` throws `BitpackerError(INVALID_SCHEMA)` on any wire-version mismatch. **New behaviour:** Same default. New optional fields: - `_tolerance?: "strict" | "forward"` — defaults to `"strict"`. Set to `"forward"` to accept alternate wire-versions on `unpack`. - `_allowedVersions?: readonly number[]` — when paired with `_tolerance: "forward"`, restricts acceptance to the listed versions; otherwise any version `[0, 65535]` is accepted. **Search command for downstream consumers:** ```bash # Find all _version-strategy usages that may benefit from forward tolerance: grep -rn "_version" packages/ apps/ --include="*.ts" | grep -v "/dist/\|/coverage/\|node_modules" | grep -E "_version:\s*[0-9]" ``` **Recommended replacement:** No replacement is needed for existing strict-mode consumers — the change is purely additive and the default behaviour is unchanged. Consumers that need to read newer-version payloads from a server they don't control can opt in by adding `_tolerance: "forward"` and (optionally) `_allowedVersions`. **Failure-mode change:** When `_tolerance: "forward"` is set with `_allowedVersions`, the error message on rejection now includes the `allowedVersions` list in the error context. Consumers asserting on the old error message text (which mentioned only expected/received) must update their assertions.