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

7.5 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
fuzz-campaigns-and-gate Fuzz campaigns (detached) + corpus-replay gate wiring + AGENTS.md verification-list flip completed
fuzz-target-payload-codec
fuzz-target-every-parse
fuzz-target-name-validation
moderate medium phase implementation
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

  • Every target has had a detached campaign (≥10 min) via run-detached.sh; logs + artifact state recorded in Notes
  • 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)
  • 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
  • cargo test --manifest-path fuzz/shared/Cargo.toml green (the gate, on stable)
  • AGENTS.md Verification Commands carry the gate line; the fuzzing paragraph reflects the landed state
  • fuzz/README.md finalized (targets table, commands, corpus policy)
  • 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.