Files
suaveplan/openspec/archive/2026-05-03-bitpacker-hardening/specs/bitpacker/spec.md
2026-05-27 23:29:53 +01:00

6.2 KiB

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/<each-pkg>/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", "<pkg-name>", 1) returns a populated entry

Verified by: packages/bitpacker/<each-pkg>/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.<pkg>.<operation>. 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/<each-pkg>/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/<each-pkg>/tests/property/round-trip.property.test.ts::"round-trip is identity for <kind>"

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"