# v0.9.0 - REPS Audit and Feature Freeze
`wire-codec` 0.9.0 is the pre-1.0 hardening release. The crate has been audited
end to end against [REPS.md](../../REPS.md), every finding has been resolved or
documented, and the public API is now considered frozen for 1.0.
This release contains one small breaking change to a constructor signature
(`Delimited::new` now returns `Result`); everything else is a tightening of
invariants, lints, and documentation. The wire format and behaviour of every
function are unchanged.
## Audit summary
The full REPS checklist (`.dev/ROADMAP.md` Phase 0.9.0) was worked through.
Findings and resolutions:
### Compiler and lint configuration
- **Finding**: `lib.rs` was missing three lints from the REPS-mandated set
(`warnings`, `clippy::unreachable`, `clippy::undocumented_unsafe_functions`).
- **Resolution**: Lint header rewritten to match REPS verbatim. The lint
`clippy::missing_safety_doc` is kept in place of REPS's
`clippy::undocumented_unsafe_functions` because the latter does not exist
in clippy; the former is the actual lint name with identical behaviour.
### Error handling
- **Finding**: `Delimited::new` panicked on an empty delimiter via
`assert!`. REPS section 3 prohibits library panics for recoverable
conditions.
- **Resolution**: `Delimited::new` now returns
`Result<Self, Error>`. A new variant `Error::EmptyDelimiter` was added.
All call sites in tests, benches, examples, and doctests have been
updated. Empty-delimiter rejection is now exercised by a dedicated unit
test (`empty_delimiter_rejected`).
- **Status**: This is the only API-breaking change in 0.9.0. Migration is a
one-line `.unwrap()` (or `?`) at the construction site.
- **Finding**: Several `Result`-returning functions on `ReadBuf` and
`WriteBuf` (`read_u16_be`, `write_u32_le`, etc.) lacked explicit
`# Errors` doc sections.
- **Resolution**: Every `Result`-returning public function now documents
its error contract.
### API stability
- **Finding**: `LengthWidth` enum lacked `#[non_exhaustive]`. The roadmap
envisages adding wider prefix widths in the future.
- **Resolution**: `LengthWidth` is now `#[non_exhaustive]`. Adding new
variants in a later minor release is non-breaking.
- **Finding**: `LengthPrefixed`, `Delimited`, `LengthWidth`, and `Endian`
did not derive `PartialEq` / `Eq` / `Hash`. Comparison and hashing are
expected ergonomics for configuration types.
- **Resolution**: All four now derive the standard equality and hashing
traits.
- **Finding**: `Encode::encoded_size` returned `usize` without
`#[must_use]`. Calling it and dropping the value is almost always a bug.
- **Resolution**: Added `#[must_use]` to the trait method.
- **Decision (no change)**: The codec and framing traits (`Encode`,
`Decode`, `Framer`) are intentionally left unsealed. Downstream users
must be able to implement custom codecs and framing strategies. This is
the core extension point of the crate; sealing it would defeat the
purpose.
### Documentation
- Every public type, trait, function, and constant has a rustdoc block.
- Every `Result`-returning function documents its `# Errors`.
- No `# Panics` sections are required because no public function panics
(after the `Delimited::new` fix).
- `# Examples` sections are present on every non-trivial public item.
- `README.md`, `docs/API.md`, and `CHANGELOG.md` all reflect 0.9.0.
- `cargo doc --no-deps --all-features` builds with zero warnings under
`RUSTDOCFLAGS=-D warnings`.
### Banned terms
- **Finding**: The REPS Documentation policy bans the words
*comprehensive*, *robust*, *seamless*, and *leverage* in shipping docs.
- **Resolution**: None of these terms appear in any shipping file. (REPS.md
itself uses the words when defining the policy; that file is treated as
vendored, not authored content.)
### Reproducibility
- **Finding**: No `rust-toolchain.toml`. REPS section "Reproducibility &
Build Determinism" requires the toolchain to be pinned by tooling, not
by README convention.
- **Resolution**: Added `rust-toolchain.toml` pinning `channel = "stable"`
with `components = ["rustfmt", "clippy"]`. MSRV remains pinned
separately via `rust-version = "1.75"` in `Cargo.toml`.
### Supply chain
- **Finding**: No `cargo audit` or `cargo deny` enforcement in CI.
- **Resolution**: New CI job `supply-chain` runs both on every push and
pull request via `rustsec/audit-check@v2` and
`EmbarkStudios/cargo-deny-action@v2`. A `deny.toml` policy file ships
with the crate: license allow-list, banned-crate registry, duplicate
version warning, source restriction to crates.io.
### Test discipline
- **Finding**: Tests follow Rust idiom naming, not REPS's recommended
`test_<subject>_<condition>_<expected>` form.
- **Decision (no change)**: The current naming is descriptive and
human-readable. REPS phrases this as a convention rather than a hard
rule; adopting the long form would obscure intent more than it would
improve searchability. Documented here for transparency.
### Benchmarking
- **Finding**: REPS section "Testing" requires `criterion` for
benchmarks. The 0.5.0 release ships a manual `std::time::Instant`
harness instead.
- **Decision (no change)**: `criterion 0.5` transitively depends on a
crate that requires `edition2024`, which is incompatible with MSRV
1.75. The manual harness reports per-operation nanoseconds, integrates
with `cargo bench`, and produces stable comparable numbers. When
the MSRV moves past 1.85, replacing the harness with `criterion` is a
drop-in change. Logged here as a tracked deviation.
### Anti-patterns scan
The REPS "Anti-Patterns: What NOT To Do" section was walked top to bottom.
Findings:
- **Performance**: No `clone()` on hot paths. No heap allocation on the
encode/decode/frame fast path. No dynamic dispatch in tight loops.
- **Security**: No unchecked arithmetic on untrusted input
(varint/zigzag/bitfield all use explicit width validation).
- **Correctness**: No `unwrap` / `expect` in non-test code. No silently
ignored errors. No race conditions (no shared mutable state).
- **Architectural**: No god structs or god modules. No global state. No
circular dependencies. No `RefCell` use.
- **Testing**: No happy-path-only tests. Property tests cover adversarial
input. No flaky tests (seeded `proptest`).
- **Documentation**: No "Phase X" or "Step Y" headers in shipping docs.
No emoji. No marketing-style adjectives. No stale doc references.
- **Dependencies**: Zero runtime dependencies. One dev-only dependency
(`proptest`), pinned with justification (`>=1, <1.6`).
## Public API surface
The frozen 1.0 surface (subject only to bug fixes and non-breaking
additions in subsequent 0.9.x or 1.x releases):
| crate root (re-exports) | `Error`, `Result`, `ReadBuf`, `WriteBuf`, `Encode`, `Decode`, `BitReader`, `BitWriter`, `VERSION` |
| `error` | `Error` (`UnexpectedEof`, `BufferFull`, `VarintOverflow`, `FrameTooLarge`, `InvalidLengthPrefix`, `DelimiterNotFound`, `InvalidEncoding`, `BitOverflow`, `EmptyDelimiter`), `Result` |
| `buf` | `ReadBuf`, `WriteBuf` with `read_*_be/le`, `write_*_be/le`, `peek`, `advance`, `remaining`, `position` |
| `traits` | `Encode`, `Decode` |
| `varint` | `encode_u32`, `encode_u64`, `decode_u32`, `decode_u64`, `encoded_len_u32`, `encoded_len_u64`, `MAX_LEN_U16`, `MAX_LEN_U32`, `MAX_LEN_U64` |
| `zigzag` | `encode_i32`, `decode_i32`, `encode_i64`, `decode_i64` |
| `bitfield` | `BitReader`, `BitWriter`, `MAX_BIT_WIDTH` |
| `framing` | `Frame`, `Framer`, `LengthPrefixed`, `LengthWidth` (`#[non_exhaustive]`), `Endian`, `Delimited` |
## Breaking changes from 0.5.0
- `framing::Delimited::new(delimiter)` now returns
`Result<Delimited<'_>, Error>` instead of `Self`. Migration:
```rust
let framer = Delimited::new(b"\n");
let framer = Delimited::new(b"\n")?; let framer = Delimited::new(b"\n").unwrap(); ```
- `LengthWidth` is now `#[non_exhaustive]`. Exhaustive matches on it from
outside the crate must add a `_ => ...` arm. Pattern matches against
individual variants continue to work unchanged.
## Verification
Built and tested green:
- `cargo build` (default features)
- `cargo build --no-default-features`
- `cargo build --all-features`
- `cargo +1.75 build --all-features --tests`
- `cargo +1.75 test --all-features`
- `cargo fmt --all -- --check`
- `cargo clippy --all-targets --all-features -- -D warnings`
- `cargo +1.75 clippy --all-targets --all-features -- -D warnings`
- `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features`
- `cargo bench --bench codec`
- `cargo bench --bench framing`
Test totals: 26 unit + 5 integration + 6 + 5 + 6 + 3 property + 3 smoke +
18 doctests = **72 tests** across stable and MSRV.
Supply-chain checks (`cargo audit`, `cargo deny`) wired into CI as a
separate `supply-chain` job.
## Compatibility
- Edition: `2021`
- MSRV: Rust `1.75`
- License: Apache-2.0 OR MIT
- Platforms: Linux, macOS, Windows (CI on all three, stable + 1.75)
- Runtime dependencies: none
- Dev dependencies: `proptest` (test-only)
## What is next
The remaining roadmap items for 1.0.0 are tracked in `.dev/ROADMAP.md`
under "Phase 0.9.x" and "Phase 1.0.0":
- Any findings raised by `cargo audit` or `cargo deny` once the new
CI job is observed across a few PR cycles.
- Final benchmark capture committed alongside the 1.0.0 tag.
- `docs/release/v1.0.0.md` written.
- `Cargo.lock` commit (decided per REPS reproducibility guidance).
- Tag `v1.0.0` and publish to crates.io.
After 1.0.0, semver applies: breaking changes require a 2.x major release.