9.4 KiB
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.tsmodules co-located with the implementation file (schema-name.ts→schema-name.schema.ts+schema-name.schema.md). - ADDED:
validate*helpers around boundaries that today acceptunknownfrom external callers (pack(data: unknown, schema: BitPackerSchema), registryregister(adapter),unpackinput 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.tsentry that contributes its public schemas to the registry under namespace"bitpacker"with the package name as the schema name andversion: 1. - ADDED:
tests/registered.test.tsper package asserting the schemas are present afterimport "@suaveplan/<pkg>/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 (
<pkg>/src/otel/instruments.ts) declaringCounter,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
MeterProviderfrom@suaveplan/testing— nevervi.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-checkas adevDependencyon every bitpacker package. - ADDED: per-package
tests/property/*.property.test.tsfiles exercising round-trip pack/unpack against generated input. - ADDED: arbitrary generators for each schema kind in
@suaveplan/testing/bitpacker-arbitrariesso 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:
mitataas adevDependencyon every bitpacker package. - ADDED: per-package
bench/*.bench.tsfiles withbench(name, fn)per public operation. - ADDED:
scripts/bench/bitpacker-baselines.jsonbaseline 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 separatebitpacker-doc-passchange. - Wire-format-version forward-compat semantics for
_versionstrategy. Today the strategy enforces version match; "tolerate newer version with default-ed missing fields" is a feature decision, not hardening. Tracked separately as a futurebitpacker-version-tolerancechange. - 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.tsfor the schema-registry items. - Per-package OTel emission tests using a real
MeterProviderfrom@suaveplan/testing. - Per-package
tests/property/*.property.test.tsfiles usingfast-check. - Per-package
bench/*.bench.tsfiles withmitatabaselines 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/opentelemetryif 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 onunpack._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:
# 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.