diff --git a/CHANGELOG.md b/CHANGELOG.md index b36be35..0744fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,90 @@ All notable changes to this crate are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this crate adheres to [Semantic Versioning](https://semver.org/). +## [0.6.0] - 2026-09-28 + +The fuzzing adoption: a cargo-fuzz workspace over the four wire-facing +parse surfaces, with zero findings in the crate — every crash the +campaigns produced was a harness-model gap, and every fix landed in the +harness, not the crate. The `alkcall` requirement also updates to +0.8.1. No API, wire-format, or behavior change; this release ships no +source change in `src/` at all — the deltas are the fuzz workspace, +the dependency bump, and this changelog. Minor bump per the 0.x +ecosystem-coordination rule (alktty's public signatures reference +alkcall types, so downstream crates must unify on alkcall 0.8.1). + +### Added + +- **cargo-fuzz workspace under `fuzz/`** (per + `docs/plans/fuzzing.md`, adopted from the alkcall fuzzing playbook): + four targets over the wire-facing surfaces, with the invariant logic + in a stable-toolchain shared crate (`fuzz/shared/`) so the corpus + replays on stable — the nightly pin is scoped to the subtree via + `fuzz/rust-toolchain.toml`: + - `chunk_frame` — the 5-byte chunk codec + (`ChunkReader`/`ChunkWriter`), including the + peek/disambiguation seam (`peek_stream_type` vs + `read_chunk_after_peek` in both call orders must yield exactly one + chunk). + - `negotiation_frame` — the 4-byte BE length-prefixed negotiation + framing + `NegotiateRequest` JSON parse + `error_response_bytes`, + and the cross-codec invariant that an error-response frame's + length-prefix high byte is `0x00` so a `ChunkReader` peek + distinguishes it from a chunk (ADR-001 §5). + - `control_json` — `ControlMessage` JSON parse + `signal_from_name`. + - `session_opseq` — the stateful target: a `#[derive(Arbitrary)]` + 12-op sequence drives the real producer session pump + (`drive_session_pre_negotiated`) over a duplex pair against a + fuzz-local mock backend, asserting the wire-contract invariants + (kill-on-`Drop` per ADR-005, exit-chunk-is-last per ADR-004, + lossless per-stream FIFO + single stdout sentinel, stdin + prefix-losslessness, control dispatch bounds, take-once + allocation, teardown termination). +- **Committed corpus: 340 hand-made seeds** across the four targets + (deterministic generator `fuzz/gen_fuzz_seeds.py`; the + `session_opseq` seeds are hand-encoded against the `arbitrary` 1.4.x + derive layout with the encoding pinned by a decode test). Grown + corpus entries are gitignored and excised after campaigns. +- **The standing fuzz gate**: `cargo test --manifest-path + fuzz/shared/Cargo.toml` replays every committed seed through the + same invariant functions the targets run, on stable. Added to the + verification checklist. +- **Detached-runner rule**: campaigns run via `fuzz/run-detached.sh` + (setsid/nohup, fork mode, bounded RSS) — never in the foreground of + an agent session. + +### Changed + +- **BREAKING (ecosystem-coordination) — `alkcall` bumped to 0.8.1.** + alkcall 0.8.1 is a patch release fixing the duplicate + adopt/open channel-state destruction found by alkcall's own fuzz + campaign; it changed no type alktty touches. Downstream crates must + unify on alkcall 0.8.x per the caret requirement. Minor bump per + the 0.x ecosystem-coordination rule. +- **Root `Cargo.toml` gains a `[workspace]` table** + (`members = ["."]`, `exclude = ["fuzz"]`) so the fuzz subtree's + nightly dev-deps never join the root workspace (an MSRV footgun), + and `fuzz/` joins the publish exclude. Verified: the packaged file + list is unchanged except for the exclusion (43 files; the fuzz + workspace, seeds, plans, reviews, and `.opencode/` all stay out of + the crate). + +### Verification + +- 0 findings across all four targets: detached campaigns (10 min each + on the three wire targets; 10 min on `session_opseq` at 42.3k execs, + cov 3934 → 4075 and still growing at budget end) plus a 60s smoke + run on target 4 — no crash/OOM/timeout/leak. +- Corpus replay 9 tests green on stable (340 seeds); crate tests 113 + default / 156 all-features; clippy stable + wasm; fmt; doc; wasm + check — all pass. +- The three crashes the harness-model smoke runs surfaced were + harness gaps, each fixed in the fuzz harness (the alkcall §7.7 + pattern): a failed client write can mean the session completed and + dropped the server duplex half mid-write — the harness drains and + requires the exit chunk on any write/shutdown error (a premature + close is a finding). No adapter code changed. + ## [0.5.0] - 2026-09-18 The alkcall 0.8.0 adoption: a dependency bump only — no source change @@ -321,6 +405,7 @@ alknet mono-repo. the client→backend pump is aborted at session end instead of lingering until the client disconnects. +[0.6.0]: https://git.alk.dev/alkdev/alktty/releases/tag/v0.6.0 [0.5.0]: https://git.alk.dev/alkdev/alktty/releases/tag/v0.5.0 [0.4.1]: https://git.alk.dev/alkdev/alktty/releases/tag/v0.4.1 [0.4.0]: https://git.alk.dev/alkdev/alktty/releases/tag/v0.4.0 diff --git a/Cargo.lock b/Cargo.lock index 98c153a..9f22b1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,7 +48,7 @@ dependencies = [ [[package]] name = "alktty" -version = "0.5.0" +version = "0.6.0" dependencies = [ "alkcall", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index e45a7d2..046c0bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alktty" -version = "0.5.0" +version = "0.6.0" edition = "2021" rust-version = "1.88" license = "MIT OR Apache-2.0"