diff --git a/docs/plans/fuzzing.md b/docs/plans/fuzzing.md index 52c268a..2e1e2f0 100644 --- a/docs/plans/fuzzing.md +++ b/docs/plans/fuzzing.md @@ -71,6 +71,25 @@ Progress log: close never swallows a legal chunk the mux already counted as written. Regression seeds cover the drain-then-fail shape in both chunk harnesses. +- **2026-09-29 — FWD-22, the round-trip guard-CR (harness-assert + fix; wave 4).** Found ~32 min into the first long `sse_parser` + campaign: the re-frame round-trip probe's `frame_event` emitted + `data: ` unguarded, so a JSON payload carrying an *interior* + CR (multi-line data `"\r\n…"` — the payload line is `"\r"`) had + its payload CR consumed by `strip_cr` at re-parse time and the + round-trip lost it (wave-1 had skipped only the *terminal*-CR + case). The parser was never wrong — the shape is representable on + input (`data: x\r\r\n` → value `x\r`) and both drives agreed — the + re-encode was weak: one CR in the wire form is ambiguous between + "payload CR" and "line-terminator CR". Fix: `frame_event` emits + `data: \r` for every data line (the guard CR is the + terminator, `strip_cr` removes exactly it, the payload CR + survives), and the terminal-CR skip is dropped — the round trip is + now total over anything JSON-encodable (JSON never encodes a raw + CR, so the residual asymmetry stays the serde_json non-preserved + key-order class). No crate API change. Regression seeds + unit + cases cover the interior-CR JSON shape (`{"k": "a\\rb"}`, two-line + `{…}\r\r\n{…}`). --- @@ -441,6 +460,31 @@ the counted run): - Toolchain notes live in `fuzz/README.md`; nightly stays confined to `fuzz/`. +Wave-4 release-budget campaigns (2026-09-29, 45 min per target, +launched concurrently, five of five): + +- `ws_chunk_framing` — exited 0: 13.7M execs at ~5k/s, coverage + saturated (297 edges / 1216 features / 139 corpus). +- `spec_import` — exited 0: 6.85M execs at ~3.2k/s, coverage growing + to budget end (14.9k edges / 43.4k features / 3946 corpus). +- `ws_session_opseq` — exited 0: 715k execs at ~253/s, coverage + growing to budget end (2251 edges / 6337 features / 260 corpus). +- `retry_throttle` — exited 0: 3.3M execs at ~1.3k/s, coverage + saturated (1154 edges / 7071 features / 352 corpus). +- `sse_parser` — aborted at 1913 s on FWD-22 (above; the + interior-CR round-trip loss, a harness fix); before the abort it + had 57k execs at ~35/s (1269 edges / 5584 features / 509 corpus), + still growing. 45-min rerun of `sse_parser` post-fix in flight at + the time of writing. +- A stale crash artifact from the wave-2 morning session (quoted-`""` + YAML key, predating that fix) was still present in + `fuzz/artifacts/spec_import/` at campaign start — not produced by + wave 4 (the wave-4 `spec_import` log has zero findings). +- **Seeds regenerate deterministically:** `python3 + fuzz/gen_fuzz_seeds.py`. +- Toolchain notes live in `fuzz/README.md`; nightly stays confined to + `fuzz/`. + 1. ✅ **Done (2026-09-29, commit 5971c0a)** — infra (workspace exclude, toolchain pin, runner, seed generator, README) + targets 1–2 + corpora + corpus replay + AGENTS.md gate + smoke campaigns @@ -450,13 +494,16 @@ the counted run): (`fuzz/shared/src/spec_import.rs`), 42 seeds, `json.dict` extended with the OpenAPI-token set, fuzzing hubs in the three driven modules. Smoke campaign clean (see §5). -3. **Next** — target 4 stateful wave + longer campaigns; revisit - corpus merges if any campaign yields inputs worth curating. +3. ✅ **Done (2026-09-29, wave 4)** — long (45 min) release-budget + campaigns, all five targets (see §5; the one finding, FWD-22, was + a harness-assert fix). Corpus merges remain deliberately deferred + (grown corpora stay gitignored; nothing campaign-dropped has been + curated into seeds). 4. ✅ **Done (2026-09-29, wave 3)** — target 4 stateful (`ws_session_opseq` + `retry_throttle`): two harnesses + 16 seeds + the `byte_adapter::fuzzing` / `client::fuzzing` hubs + FWD-21 (the `WriteChunkWalk` failure-carry drop — a crate contract fix; user-approved semver-relevant API change). Smoke campaigns clean - (see §5). Longer (30+ min) campaigns before the next release. + (see §5). Status updates land in §3's table and §6's list as work completes. \ No newline at end of file