# Capability Spec Delta: bitpacker (bitpacker-hardening) **Package:** `@suaveplan/bitpacker` and the bitpacker family **Status:** PROPOSED > Every requirement carries a `**Verified by:**` line. All requirements are above-baseline genesis-policy additions; there is no upstream source for them. Every requirement is additive instrumentation or test infrastructure with no behaviour change. ## ADDED Requirements ### Requirement: Public types are Zod-validated at package boundaries Every package in the bitpacker family SHALL expose a Zod schema for each public input type and SHALL validate untrusted inputs at the package boundary (the entry method that accepts a value from outside the package). Internal recursion MUST NOT re-validate. #### Scenario: invalid input rejected at boundary - **WHEN** a caller passes a value that does not match the boundary's Zod schema - **THEN** the package throws a `BitpackerError` whose `cause` is the Zod error and whose code is `INVALID_SCHEMA` #### Scenario: internal recursion does not re-validate - **WHEN** the strategy chain recurses into a sub-schema that has already been validated at the entry boundary - **THEN** no Zod schema is parsed during the recursion (verified by spying on the schema parser's call count) **Verified by:** `packages/bitpacker//tests/zod-boundary.test.ts::"throws BitpackerError with INVALID_SCHEMA on bad input"` AND `::"does not re-validate during internal recursion"` ### Requirement: Public schemas registered with schema-registry Every package in the bitpacker family SHALL register its public Zod schemas with `@suaveplan/schema-registry` under namespace `"bitpacker"`, with the package name as the schema name and `version: 1`. #### Scenario: schemas present after import - **WHEN** a consumer imports the package's `register-schemas` subpath - **THEN** `schemaRegistry.get("bitpacker", "", 1)` returns a populated entry **Verified by:** `packages/bitpacker//tests/registered.test.ts::"registers schemas under namespace 'bitpacker'"` ### Requirement: OTel spans emitted at hot-path boundaries Every package in the bitpacker family SHALL emit one OTel span per top-level pack/unpack call, named `suaveplan.bitpacker..`. Per-chunk events MUST be nested span events, not standalone spans. #### Scenario: span emitted on pack - **WHEN** a consumer calls a top-level pack/unpack method - **THEN** exactly one span is emitted with the canonical name and attributes `{schema.kind, byte.count, success}` **Verified by:** `packages/bitpacker//tests/otel.test.ts::"emits one span per top-level pack/unpack call"` ### Requirement: fast-check property tests cover round-trip soundness Every package that exposes a pack/unpack pair SHALL ship a `tests/property/*.property.test.ts` file using `fast-check` to assert that for every value matching the schema's arbitrary, `unpack(pack(v)) ≡ v`. #### Scenario: round-trip property holds for every public schema kind - **WHEN** a fast-check arbitrary generates 100 values for a schema kind - **THEN** every generated value round-trips byte-identically **Verified by:** `packages/bitpacker//tests/property/round-trip.property.test.ts::"round-trip is identity for "` ### Requirement: mitata benchmarks have committed baselines and CI gate Every package that exposes a public encoder/decoder SHALL ship a `bench/*.bench.ts` file using `mitata`, with baseline timings committed to `scripts/bench/bitpacker-baselines.json`. CI SHALL fail the `bench:check` task if any benchmark regresses by more than 10% from the committed baseline. #### Scenario: regression caught by CI gate - **WHEN** a code change makes a benchmarked operation 11%+ slower than its committed baseline - **THEN** `bunx turbo bench:check` exits non-zero with the regressed operation's name and percentage **Verified by:** `scripts/bench/check-baselines.test.ts::"flags regressions over 10%"` ### Requirement: `_version` strategy supports opt-in forward tolerance The `VersionPackingStrategy` in `@suaveplan/bitpacker-strategies` SHALL accept an optional `_tolerance: "strict" | "forward"` field on the version-schema shape. When `_tolerance` is absent or `"strict"`, the strategy SHALL throw `BitpackerError(INVALID_SCHEMA)` on any wire-version mismatch (current behaviour). When `_tolerance: "forward"` is set and the schema also carries an `_allowedVersions: number[]`, the strategy SHALL accept any wire version listed in that array and proceed with the schema's `_schema` field for decoding. When `_tolerance: "forward"` is set without `_allowedVersions`, the strategy SHALL accept any wire version >= 0 and proceed with `_schema`. The on-wire byte format is unchanged: a 16-bit unsigned big-endian version followed by the schema-encoded payload. #### Scenario: strict mode rejects mismatched wire version - **WHEN** a consumer unpacks bytes whose wire version differs from the schema's `_version` and `_tolerance` is absent or `"strict"` - **THEN** the strategy throws `BitpackerError(INVALID_SCHEMA)` with the expected and received versions in the error context #### Scenario: forward-tolerant mode accepts a listed version - **WHEN** a consumer unpacks bytes whose wire version is listed in `_allowedVersions` and `_tolerance: "forward"` is set - **THEN** the strategy decodes the payload via `_schema` without throwing #### Scenario: forward-tolerant mode without allowlist accepts any version - **WHEN** a consumer unpacks bytes with `_tolerance: "forward"` and no `_allowedVersions` field - **THEN** the strategy decodes any wire version >= 0 via `_schema` #### Scenario: forward-tolerant mode rejects a version outside the allowlist - **WHEN** a consumer unpacks bytes whose wire version is NOT in the supplied `_allowedVersions`, with `_tolerance: "forward"` - **THEN** the strategy throws `BitpackerError(INVALID_SCHEMA)` with the expected version, the received version, and the allowed-versions list in the error context **Verified by:** `packages/bitpacker/bitpacker-strategies/src/strategies/version/version.test.ts::"throws on strict mismatch"` AND `::"accepts a listed version when _tolerance: forward"` AND `::"accepts any version when _tolerance: forward without allowlist"` AND `::"rejects an unlisted version under _tolerance: forward"`