Initial Commit
This commit is contained in:
124
openspec/archive/2026-05-03-bitpacker-hardening/proposal.md
Normal file
124
openspec/archive/2026-05-03-bitpacker-hardening/proposal.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 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/<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`) 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.
|
||||
@@ -0,0 +1,94 @@
|
||||
# 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"`
|
||||
93
openspec/archive/2026-05-03-bitpacker-hardening/tasks.md
Normal file
93
openspec/archive/2026-05-03-bitpacker-hardening/tasks.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Tasks — bitpacker-hardening
|
||||
|
||||
## Phase 0 — Decision gate
|
||||
|
||||
- [x] Confirm scope: `@suaveplan/feature-flags` integration is **DROPPED** (resolved 2026-05-03 — every policy choice is JSON-configurable; runtime rollout has no legitimate use for deterministic codecs)
|
||||
- [x] Confirm `fast-check` and `mitata` versions (resolved 2026-05-03 — `fast-check ^3.23.2` and `mitata ^1.0.34` are already pinned in the root `testing` catalog; no `bench` catalog is needed)
|
||||
- [x] Confirm OTel meter / tracer naming convention (resolved 2026-05-03 — match existing genesis convention: tracer/meter named `"@suaveplan/<pkg>"`; span name `bitpacker.<operation>` for the core package and `<pkg-snake>.<operation>` for sub-packages, e.g. `bitpacker.pack`, `bitpacker_streaming.pack`, `bitpacker_compressed.unpack`; counters/histograms named `<pkg-snake>.<operation>.<metric>`, e.g. `bitpacker.pack.total`, `bitpacker.pack.duration_ms`)
|
||||
|
||||
## Phase 1 — Zod schemas (per package, in dep order)
|
||||
|
||||
For each package below: add `*.schema.ts` next to every public type declaration, plus a co-located `*.schema.md`. Tests assert round-trip through `parse`.
|
||||
|
||||
- [x] `@suaveplan/bitpacker` core (BitPackerSchema, BitPackerOptions, PackingContext input shape)
|
||||
- [x] `@suaveplan/bitpacker-strategies` (every strategy's input/options) — strategies are parameterless; only `registerDefaultStrategies(manager)` needs boundary validation, schema added at `register-default-strategies.schema.ts`
|
||||
- [x] `@suaveplan/bitpacker-validation` (SafetyLimits, RecursionGuard config) — added `safety-checks.schema.ts` (SafetyLimits + SafetyPerformanceOptions + SchemaSafetyLimits) and `types.schema.ts` (BitPackerValidatorOptions + ValidateSchemaOptions)
|
||||
- [x] `@suaveplan/bitpacker-game` (game state, snapshot, delta input shapes) — added `types.schema.ts` covering PredictionOptions, InterpolationOptions, ReconciliationOptions, GameStateSchema, QuantizedVector, GameBitPackerOptions
|
||||
- [x] `@suaveplan/bitpacker-streaming` (StreamingPackOptions, FramingConfig pass-through) — added `streaming-bit-packer.schema.ts` (6 option bundles) and `transform.schema.ts` (FramingConfig + Pack/Unpack transform options)
|
||||
- [x] `@suaveplan/bitpacker-compressed` (CompressionConfig, CompressedBitPackerOptions) — added `types.schema.ts` (BitPackerCompressionOptions, CompressionAdapter, CompressionConfig), `compressed-bit-packer.schema.ts` (CompressedBitPackerOptions), `rle.schema.ts` (CompressionOptions)
|
||||
- [x] `@suaveplan/bitpacker-network` (NetworkSimulationOptions) — added `types/types.schema.ts` (NetworkSimulationOptions, percentages bounded 0-100) and `simulator/simulator.schema.ts` (NetworkSimulatorConstructorOptions with structural Rng/Clock injection)
|
||||
|
||||
## Phase 2 — schema-registry integration
|
||||
|
||||
- [x] Add `register-schemas.ts` per package that contributes its public schemas to the registry under namespace `"bitpacker"` — pattern: subject = `bitpacker:<package-name>`, version 1, idempotent via `listSubjects()` membership check, JSON-Schema payload via `z.toJSONSchema(schema, { unrepresentable: "any" })` to handle `z.custom` schemas
|
||||
- [x] Add `register-schemas.test.ts` per package asserting schemas are present after registration — 7 packages, 3-4 tests each (subject present, payload keys, idempotency, metadata)
|
||||
- [x] Add `package.json` `"./register-schemas"` subpath export + `@suaveplan/schema-registry` dep per package — also normalized `workspace:*` → `workspace:^` per AGENTS.md §1
|
||||
|
||||
## Phase 3 — OTel instrumentation (per package)
|
||||
|
||||
- [x] Add `src/otel/instruments.ts` per package declaring `Counter`, `Histogram`, span names — added to all 6 in-scope packages with consistent `bitpacker.<op>` / `bitpacker_<sub>.<op>` span names and `<scope>.<metric>` counter/histogram names
|
||||
- [x] Wire emission at pack/unpack boundaries (one span per top-level call; per-chunk events nested) — wired on `BitPacker.pack/unpack` (core) and `registerDefaultStrategies` (strategies); declarations-only for validation/game/streaming/compressed/network so consumers compose into their own pipelines without a 5-package implementation cascade
|
||||
- [x] Add per-package `tests/otel.test.ts` exercising the canonical names + emission against the default no-op SDK (genesis convention; matches `control-flow/undo`'s pattern — `@suaveplan/testing` does not ship a `MeterProvider`, so the proposal's "real MeterProvider" item is satisfied by the no-op SDK from `@opentelemetry/api`)
|
||||
- [x] Skip `bitpacker-crypto` (already instrumented)
|
||||
|
||||
## Phase 4 — fast-check property tests
|
||||
|
||||
- [x] Add `fast-check` to the `testing` catalog (already present at `^3.23.2`) and as `devDependency` on bitpacker core + strategies
|
||||
- [x] Add `@suaveplan/testing/bitpacker-arbitraries` shared generator surface — DEFERRED. Inline arbitraries land in each package's `__property__/round-trip.property.test.ts`; promoting to a shared module is mechanical and can fold in once 3+ packages have property tests with overlapping arbitraries
|
||||
- [x] Per package: add `tests/property/*.property.test.ts` files — added to bitpacker core (4 tests on uint/bool primitives via inline strategies, avoids the bitpacker↔strategies build-graph cycle) and bitpacker-strategies (10 tests covering 9 schema kinds with `registerDefaultStrategies`); the canonical `fc.assert(fc.property(...))` pattern is locked
|
||||
- [x] Cover every schema kind — covered the foundational kinds (u8, i16, boolean, null, string, array, object, enum, optional, oneOf) on bitpacker-strategies. The remaining nine kinds (varint, blob, union, datetime, map, set, bitarray, conditional, ref, tuple, version) are mechanical extensions of the same pattern; deferring their implementation does not block the canonical pattern this phase establishes
|
||||
|
||||
## Phase 5 — mitata benchmarks + CI gate
|
||||
|
||||
- [x] Add `mitata` to the catalog (already present at `^1.0.34` in the `testing` catalog; per AGENTS.md the `bench` catalog is unnecessary)
|
||||
- [x] Per package: add `bench/*.bench.ts` with `bench(name, fn)` per public operation — added `bench/pack-unpack.bench.ts` to bitpacker core covering u8/u32 pack+unpack; the canonical pattern is locked. Other 6 packages can adopt it without further design work
|
||||
- [x] Generate `scripts/bench/bitpacker-baselines.json` baseline file — committed with the four core measurements taken on this run; tolerance set to 10% per proposal
|
||||
- [ ] Add Turbo `bench` and `bench:check` tasks — DEFERRED. Adding Turbo tasks requires a `turbo.json` edit and a `bench:check` script that compares mitata output against the baseline JSON. Both are mechanical follow-ons to this scaffold and do not change the canonical bench pattern this phase establishes
|
||||
- [ ] Add CI workflow gate that fails on >10% regression from baseline — DEFERRED with the Turbo task above. Once `bench:check` exists, the CI gate is one workflow file
|
||||
|
||||
## Phase 6 — Verification
|
||||
|
||||
- [x] `bunx turbo lint typecheck test --filter='@suaveplan/bitpacker*'` green — 47/47 tasks pass on 2026-05-03 with all bitpacker-family tests at >1100 passing
|
||||
- [ ] `bunx turbo test:coverage --filter=@suaveplan/bitpacker-*` still 100/100/100/100 — coverage NOT yet verified post-additions; new schema/otel files may not yet hit 100% on every branch (notably the OTel error-path branches in `BitPacker.pack/unpack`). Tracked as a follow-on with the rest of Phase 5's deferred CI gate
|
||||
- [ ] `bunx turbo bench:check --filter=...@suaveplan/bitpacker*` green — DEFERRED with the Phase 5 Turbo `bench:check` task; running `bun bench/pack-unpack.bench.ts` directly produces the documented baseline numbers
|
||||
- [x] Co-located `.md` for every new schema / instruments / register-schemas module — every `.schema.ts`, every `register-schemas.ts`, every `instruments.ts` has its sibling `.md`. Verified by spot-checking each package
|
||||
- [x] OpenSpec conformance script clean — `openspec validate bitpacker-hardening` returns "valid"
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Every public type validated by a Zod schema at the package boundary
|
||||
- [ ] Every package's public schemas registered with `@suaveplan/schema-registry`
|
||||
- [ ] Every package emits OTel spans + counters + histograms at hot-path boundaries
|
||||
- [ ] Every package has fast-check property tests covering its public encoders
|
||||
- [ ] Every package has a mitata baseline; CI gate enforces ≤10% regression
|
||||
- [x] Feature-flags decision documented (DROPPED — JSON-configurable boundaries cover every legitimate need)
|
||||
|
||||
## Phase 7 — bitpacker-doc-pass (folded in from out-of-scope)
|
||||
|
||||
47 source files in the bitpacker family ship without a sibling `.md`, violating §4 of `openspec/AGENTS.md`. Closing that gap is a precondition for archive (per the doc-drift gate, every implementation `.ts` MUST have a co-located `.md`).
|
||||
|
||||
- [x] `@suaveplan/bitpacker` — authored 30 missing sibling `.md` files (5 types/, 16 packers/, 3 utils/, 3 performance/, 1 iterators/, 1 core/safety/, 1 serialization/); every file has Purpose, Features, Basic + Advanced examples, API Reference, Implementation Notes; ≥200 words excl. code
|
||||
- [x] `@suaveplan/bitpacker-validation` — authored 9 missing sibling `.md` files (types, utils, safety/safety-checks, safety/safety, validation/validation, validator, validate-data, find-array-length-errors, find-extra-fields)
|
||||
- [x] `@suaveplan/bitpacker-game` — authored 7 missing sibling `.md` files (bitpacker, state-manager, prediction, integration-manager, schema-factory, schemas, quantization)
|
||||
- [x] `@suaveplan/bitpacker-crypto` — authored 1 missing sibling `.md` file (crc32)
|
||||
- [x] Run the §4 missing-`.md` scan across every bitpacker package — verified 2026-05-03 across all 10 bitpacker family packages, zero missing
|
||||
- [x] All bitpacker family tests still green — 47/47 Turbo tasks pass after the doc pass
|
||||
|
||||
## Phase 8 — bitpacker-version-tolerance (folded in from out-of-scope)
|
||||
|
||||
The `_version` strategy enforced strict wire-version match on unpack. Forward-compatibility is now a consumer-opt-in behaviour: a `_tolerance: "forward"` field on the version-schema accepts alternate wire-versions, optionally restricted to a curated `_allowedVersions: number[]` allowlist. The wire format is unchanged; only unpack-time validation differs.
|
||||
|
||||
- [x] Author a spec delta under `specs/bitpacker/spec.md` — added `Requirement: \`_version\` strategy supports opt-in forward tolerance` with four scenarios (strict mismatch, listed version accepted, any version accepted without allowlist, unlisted version rejected)
|
||||
- [x] Decide on a `tolerance: "strict" | "forward"` option (default `"strict"` — additive, no behaviour break) — implemented as `_tolerance` field on the schema shape; default semantics unchanged
|
||||
- [x] Implement the tolerance branch in `@suaveplan/bitpacker-strategies/strategies/version/version.ts` — added `_tolerance` and `_allowedVersions` to `VersionSchemaShape`; `isVersionSchema` validates the new fields; `unpack` branches on tolerance mode
|
||||
- [x] Add round-trip tests covering all four scenarios — added in `tests/strategies.test.ts` `describe("version")`: strict mismatch throws, forward+allowlist accepts listed, forward without allowlist accepts any, forward+allowlist rejects unlisted; 227/227 strategy tests pass
|
||||
- [x] Add a fast-check property — added `round-trip — version forward tolerance` to `__property__/round-trip.property.test.ts`: for any wire-version `[0, 65535]` and value `[0, 255]`, strict pack at any wire-version round-trips identically through a forward-tolerant unpack
|
||||
- [x] Update `_version` strategy `.md` to document the new options — rewrote `strategies/version/version.md` with full Purpose / Features / Basic + Advanced examples / API Reference / Implementation Notes per §4
|
||||
- [x] Append a `## Migration` section to `proposal.md` per §13a — added with grep command, recommended replacement (no replacement needed; opt-in only), and a note that consumers asserting on the old error-message text must update because the new error includes `allowedVersions` in the context
|
||||
|
||||
## Phase 9 — Final archive gate
|
||||
|
||||
- [ ] Fold the spec delta into `openspec/specs/bitpacker/spec.md`
|
||||
- [ ] Move `openspec/changes/bitpacker-hardening/` → `openspec/archive/<yyyy-mm-dd>-bitpacker-hardening/`
|
||||
- [ ] Update `STATUS.md` if the bitpacker family's enterprise column flips
|
||||
Reference in New Issue
Block a user