# pack-io v0.9.0 — Beta
**The beta release.** v0.9.x is the bug-fixes-only window before RC. v0.9.0 itself ships zero new public API and zero wire-format changes — what it adds is the broader-testing infrastructure that pre-RC software needs (5 new fuzz targets, CI runtime doubled per target) and the **v1.0 performance baseline** committed as the canonical reference that post-1.0 regression detection will diff against.
## Honest caveat
The roadmap defines the alpha → beta promotion as gated on "a stable stretch with no outstanding bugs from the v0.8.x line". No real-consumer bug reports have come in since v0.8.0 — there hasn't been a stretch *of any duration* yet. So v0.9.0 is the ceremonial promotion that opens the bug-fixes-only window; the substantive content is the testing + baseline work below. If a v0.8.x consumer surfaces a bug after this release, it gets fixed in v0.9.1 / v0.9.2 / ... within the bug-fixes-only window — that's exactly what beta is for.
## Status
| Field | Value |
|-----------------|----------------------------------------------------------------------------------------|
| Crate status | **beta — bug-fixes-only** |
| Public API | **frozen since v0.7.0** (no source-breaking change until v2.0) |
| Wire format | **frozen at v0.3.0** (spec version 1.2; no wire change in v0.9.x) |
| Performance | **frozen baseline at v0.9.0** ([`docs/PERFORMANCE_BASELINE.md`](https://github.com/jamesgober/pack-io/blob/main/docs/PERFORMANCE_BASELINE.md)) |
| RC target | v0.9.5+, critical fixes + doc polish only |
| Stable target | v1.0.0 |
## What landed
### 5 new fuzz targets — 13 total
Continuous fuzz coverage extends to the decode paths the v0.7 set didn't touch:
| Target *(v0.9 additions in bold)* | What it hardens |
|---|---|
| `decode_string` | varint length + UTF-8 validation |
| `decode_vec_u8` | byte-run fast path (`u8::deserialize_many`) |
| `decode_tuple` | mixed primitive + length-prefixed shape |
| `decode_collection` | `HashMap<String, Vec<u8>>` count cap + per-entry decode |
| `decode_view_str` | zero-copy `&str` decode + lifetime / UTF-8 validation |
| `decode_struct_derive` | derive-generated struct deserialiser |
| `decode_enum_derive` | derive-generated enum + variant-index varint |
| `decode_versioned` | schema-evolution body-length cap |
| **`decode_btreemap`** | `BTreeMap<u64, String>` ordered-map non-`std` path |
| **`decode_btreeset`** | `BTreeSet<String>` with per-element UTF-8 validation |
| **`decode_hashset`** | `HashSet<u32>` preallocation cap |
| **`decode_view_bytes`** | zero-copy `&[u8]` decode (no UTF-8 path) |
| **`decode_view_collection`** | `Vec<&str>` collection of borrows |
### CI runtime doubled
Per-target runtime in the CI fuzz job bumped from **30 s → 60 s**. Combined with the 5 new targets, every push to `main` now exercises **~13 minutes of continuous fuzzing** across all targets (was ~4 minutes in v0.7 with 8 targets × 30 s).
This is a smoke pass, not exhaustive fuzzing — exhaustive fuzzing happens out-of-band on dedicated infrastructure or via post-1.0 ossfuzz integration. The CI pass is sized to catch the obvious "decoder crashes on input X" regression within minutes of a push.
### v1.0 performance baseline — frozen reference
[`docs/PERFORMANCE_BASELINE.md`](https://github.com/jamesgober/pack-io/blob/main/docs/PERFORMANCE_BASELINE.md) is the canonical v1.0 performance reference. Numbers are Criterion medians from **100 samples × 10 s measurement window × 2 s warmup** — high enough fidelity that the confidence interval on each median is ≤ 1 ns for the primitive paths and ≤ 1 % for the struct paths.
The 10 s window confirms the v0.6 / v0.7 quick-run (3 s) numbers within ~3 % on every row. There's no surprise behaviour at longer measurement times.
**Regression policy** (per [`REPS.md`](https://github.com/jamesgober/pack-io/blob/main/REPS.md) §Performance): any post-1.0 change that exceeds a **5 % regression** on any row of the baseline blocks the merge. A win exceeding 5 % updates the baseline file and the changelog.
CI does **not** run the comparative benchmark — measurement variance across CI runners is too high for it to be a useful regression gate. Performance checks happen on dedicated hardware between releases.
### Baseline summary
| Workload | pack-io | bincode | postcard | rkyv | Position |
|---|---:|---:|---:|---:|---|
| encode/log_record | **37.9 ns** | 39.4 ns | 232.6 ns | 112.6 ns | pack-io fastest |
| decode_owned/log_record | 158.9 ns | 165.1 ns | 285.1 ns | **154.6 ns** | rkyv 1.03× faster |
| decode_view/log_record | 34.7 ns | — | — | **12.0 ns** | rkyv 3× faster (by design) |
| `u64` round-trip | 22.3 ns | **20.6 ns** | — | — | bincode 1.08× faster |
| 64-byte `String` owning | **44.7 ns** | 48.8 ns | — | — | pack-io fastest |
| 64-byte `&str` view | **5.2 ns** | — | — | — | uncontested |
| 4 KiB `Vec<u8>` decode | **59.7 ns** | 63.0 ns | — | — | pack-io fastest |
pack-io is the fastest of the four on encode, owning `String`, view `&str`, and `Vec<u8>` decode. Tied within ~5 % on `u64` round-trip and owned struct decode. Only meaningful loss is rkyv's archived path (~3×), which is a design choice the wire format spec explicitly trades off — see [`docs/PERFORMANCE.md`](https://github.com/jamesgober/pack-io/blob/main/docs/PERFORMANCE.md) for the analysis.
### Status markers
- README banner: "alpha — integration window open" → **"beta as of v0.9.0; v0.9.x is bug-fixes-only"**
- API.md status banner mirrors the beta marker + adds the performance-baseline freeze callout
- Roadmap row for v0.9 marked shipped; v0.9.5+ narrowed to RC; v1.0 next
## Breaking changes
**None.** Every v0.8 source file compiles unchanged. Every v0.8 wire payload decodes identically. v0.9 is purely additive (fuzz coverage + frozen reference doc) and status-marker work.
## What v0.9.x will track
| Allowed in v0.9.x | Deferred |
|-------------------------------------------|-----------------------------------------|
| Bug fixes from real consumers or fuzz hits | New public API (waits for v2.0) |
| Tightening existing tests | Wire format anything |
| Doc improvements | Source-breaking refactors |
| Performance fixes (against the baseline) | Speculative additive features (YAGNI) |
| New fuzz targets / hostile-input cases | New feature flags |
Performance fixes are allowed if they preserve or improve every row of the baseline; any change that regresses any row by > 5 % requires an explicit justification and CHANGELOG callout.
## Verification
All gates green on **both stable and MSRV 1.85**:
```bash
cargo fmt --all -- --check
cargo +1.85 fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo +1.85 clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo +1.85 test --all-features
cargo build --no-default-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo audit
cargo deny check
cd fuzz && cargo +nightly check
```
Test counts at this tag (stable, `--all-features`): **274 total**, all passing — unchanged from v0.8 because v0.9 is bug-fixes-only and no consumer bugs have surfaced yet. All ten example programs run end-to-end. All 13 fuzz targets compile cleanly under nightly.
## What's next
- **v0.9.x point releases** track bugs surfaced by consumers or fuzz hits. No scheduled cadence — driven by reports.
- **v0.9.5+ — RC.** Critical fixes + doc polish only.
- **v1.0.0 — Stable.** API + wire format + performance baseline frozen for the entire 1.x line.
## Installation
```toml
[dependencies]
pack-io = { version = "0.9", features = ["schema"] } # everything
# Or à la carte:
pack-io = { version = "0.9", features = ["derive"] } # derive macros only
pack-io = "0.9" # in-memory + streaming codec only
pack-io = { version = "0.9", default-features = false } # no_std
```
MSRV: Rust 1.85 (2024 edition).
## Documentation
- [README](https://github.com/jamesgober/pack-io/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/pack-io/blob/main/docs/API.md)
- [Wire Format Spec](https://github.com/jamesgober/pack-io/blob/main/docs/WIRE_FORMAT.md) (v1.2 — unchanged from v0.5)
- [Performance overview](https://github.com/jamesgober/pack-io/blob/main/docs/PERFORMANCE.md) — analysis + methodology
- **[v1.0 Performance baseline](https://github.com/jamesgober/pack-io/blob/main/docs/PERFORMANCE_BASELINE.md) (new)** — frozen data table
- [CHANGELOG](https://github.com/jamesgober/pack-io/blob/main/CHANGELOG.md)
---
**Full diff:** [`v0.8.0...v0.9.0`](https://github.com/jamesgober/pack-io/compare/v0.8.0...v0.9.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/pack-io/blob/main/CHANGELOG.md#090---2026-06-04).