Files
alkstore/tasks/fuzz-campaigns-and-gate.md
T

144 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: fuzz-campaigns-and-gate
name: Fuzz campaigns (detached) + corpus-replay gate wiring + AGENTS.md verification-list flip
status: completed
depends_on: [fuzz-target-payload-codec, fuzz-target-every-parse, fuzz-target-name-validation]
scope: moderate
risk: medium
impact: phase
level: implementation
tags: [wave-7, fuzzing]
---
## Description
The wave's operational close (§4's sequencing tail: seeds committed →
campaigns → fix anything found → corpus replay green) plus the two
wiring deliverables the plan names:
- **Campaigns, detached only** (§5 inherited verbatim): per target, via
`fuzz/run-detached.sh` — **never a foreground child of an agent
session**. Budget 10–30 min per target (`FUZZ_RUNTIME_SECS`), flags
per §7's starting points: `payload_codec -max_len=65536
-dict=json.dict`; `every_parse -max_len=4096 -use_value_profile=1`;
`name_validation -max_len=4096 -use_value_profile=1`. Poll per §5
(`tail` the log, `ls` the artifacts dir, `pgrep -af` to LIST, kill by
explicit pid from the runner's echoed `pid=` — never
pattern-kill-and-recheck in one call).
- **Findings disposition**: crate-side bugs are fixed (the fix rides
this task when small; crash inputs are promoted to committed seeds —
the alksocks seed-900/901 posture — and the campaign re-run clean);
large or design-shaped findings escalate to the user before any
postulate-and-fix. Harness-model findings (no crate bug) are
documented in the research doc's implementation log (a new §
appended to `docs/research/fuzzing.md` — the alksocks §7
successful-negative-result disposition applies if campaigns find
nothing crate-side).
- **Corpus policy final state** (§2): committed hand-made seeds +
promoted crash inputs; grown corpora and artifacts gitignored (the
scaffold's `fuzz/.gitignore` pattern); verify `git status` shows no
grown-corpus noise after campaigns.
- **The AGENTS.md verification-list flip**: the corpus-replay gate
joins the Verification Commands —
`cargo test --manifest-path fuzz/shared/Cargo.toml` — with the
one-line note that campaigns run only via the detached runner (the
AGENTS.md fuzzing paragraph's "until then there is no fuzz gate"
clause updated to the landed state). This is the plan's named design
point ("joins the verification list when the wave lands").
- **`fuzz/README.md` finalized**: targets table complete, campaign
commands, corpus policy.
- **Research doc §7's verification list** already carries the gate —
no edit needed there beyond the implementation log.
## Acceptance Criteria
- [x] Every target has had a detached campaign (≥10 min) via
`run-detached.sh`; logs + artifact state recorded in Notes
- [x] Campaigns clean, or findings fixed + crash inputs promoted to
committed seeds + re-run clean; dispositions recorded (crate-side
vs harness-model; the latter in the research doc's log)
- [x] Corpus policy verified: committed seeds only in git; grown
corpora/artifacts ignored; `gen_fuzz_seeds.py` re-run is a no-op
diff on the committed set
- [x] `cargo test --manifest-path fuzz/shared/Cargo.toml` green
(the gate, on stable)
- [x] AGENTS.md Verification Commands carry the gate line; the fuzzing
paragraph reflects the landed state
- [x] `fuzz/README.md` finalized (targets table, commands, corpus
policy)
- [x] Root workspace gates + `shared` clippy/fmt gates green; mem wasm
check still clean
## References
- docs/research/fuzzing.md §4 (sequencing), §5 (detached-runner rule),
§6 (no-CI / the gate), §7 (flags + verification commands)
- docs/plans/implementation.md §Wave 7 (the named design points this
task closes: the AGENTS.md flip)
- /workspace/@alkdev/alksocks/docs/research/fuzzing.md §5, §7 (the
inherited operational posture + the successful-negative-result
disposition)
- tasks/fuzz-scaffold.md (the runner + gitignore this task exercises)
## Notes
- **One campaign finding, harness-model — no crate bugs (§9 log in the
research doc carries the full disposition).** The `payload_codec`
campaign crashed ~38 s in on `"46741844073709.51615"`:
`decode(encode(v)) ≠ v` for f64 leaves. serde_json's default
fast-path f64 parse of its own ryu output drifts 1–2 ULPs
(probe-measured: ~30% of random-bits finite f64s fail one
parse∘serialize∘parse pass; max drift exactly 2.00 ULP; not a
fixpoint at extreme significand shapes). ADR-020 §4 pins default
serde_json ("not pinned to a feature flavor"), so the crate-side
`float_roundtrip` route is an ADR deviation, not a fix — harness
defect. Fixed in `fuzz/shared/src/payload_codec.rs`: exact closure
outside `Number` float leaves, float leaves drift-bounded at
2 ULPs (`FLOAT_DRIFT_ULPS`, pinned-not-regressed), plus a
decode-determinism pin. Crash input promoted to
`corpus/payload_codec/seed-900`; re-run clean.
- **`fuzz/.gitignore` retightened (its final state was this task's to
set).** The scaffold's directory-level negations (`!corpus/<target>`)
leaked SHA-named grown-corpus units into `git status` while
campaigns ran (libFuzzer fork mode merges new units into the first
corpus dir). Now: dirs re-included, contents ignored,
`!corpus/<target>/seed-*` back — committed seeds + promoted crash
inputs in git, grown corpora invisible.
- **Campaigns ran in parallel** (15-min budget each, `FUZZ_RUNTIME_SECS=
900`): logs `fuzz/artifacts/{payload_codec-20261010-221350,221951,
every_parse-20261010-221351,name_validation-20261010-221352}.log`
(the 221350 log is the pre-fix crash evidence; the 215619 log is the
every_parse task's earlier 180 s smoke run, superseded). Polling
note: the runner's children match `pgrep -af cargo-fuzz` (hyphen),
not `cargo fuzz`.
- **`gen_fuzz_seeds.py` no-op verified twice**: with the wave's edits
stashed and with them applied, a re-run produced a zero diff on the
committed corpus set (seed-900 is promotion, not generator output).
- **serde_json version pin observed**: the drift characterization was
against 1.0.152 (`fuzz/Cargo.lock`); the 2-ULP bound is
pinned-not-regressed — a serde_json upgrade may only tighten it.
## Summary
Wave 7's operational close landed. Three 15-minute detached campaigns
(`run-detached.sh`, `-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048
-timeout=25`; `payload_codec -max_len=65536 -dict=json.dict`,
`every_parse`/`name_validation -max_len=4096 -use_value_profile=1`)
all exited 0, `oom/timeout/crash: 0/0/0`, empty artifact dirs at
completion: `payload_codec` ~586k execs (~650 exec/s, 2395 edges, corp
1544); `every_parse` ~3.7M execs (~4.1k exec/s, 285 edges saturated,
corp 805); `name_validation` ~59k execs (327 edges, corp 378). The one
finding (payload_codec, ~38 s in) was harness-model — the `Value`
round-trip invariant over-tightened against serde_json's default f64
parse-precision posture — fixed harness-side crash-input-promoted, and
the re-run is clean; no crate bugs (the research doc's
successful-negative-result §9). Corpus policy final state verified:
only `seed-*` tracked, grown corpora ignored, generator re-run a
no-op. The gate joined AGENTS.md's Verification Commands and the
fuzzing paragraph reflects the landed state; `fuzz/README.md` finalized
(targets table incl. the float-drift pin, commands, corpus policy);
research doc §9 appended and its §2/§6 "when wave 7 lands" clauses
updated. Verified: workspace build/test, root + `shared`
clippy/fmt, the `shared` corpus-replay gate (6 tests incl. the 208
committed seeds), and `cargo check -p alkstore-mem --target
wasm32-unknown-unknown` all green.