fuzz campaigns + gate: wave 7 close — three 15-min detached campaigns clean (payload_codegen finding harness-model: serde_json default f64 parse drifts 1-2 ULPs; invariant pinned to 2-ULP float-leaf drift + decode determinism, crash input promoted as payload_codec/seed-900), fuzz/.gitignore retightened to seed-* tracking, corpus-replay gate joins AGENTS.md verification list, research doc §9 implementation log, README finalized

This commit is contained in:
glm-5.3-flash committed 2026-10-10 22:38:34 +00:00
1 parent 5043f7bcd8
commit f7de30e57e
7 files changed
+237 -25

No files matched your search

+85 -7
View File
@@ -1,8 +1,10 @@
# Fuzzing alkstore: Research and Plan
**Status:** decided — adopted (2026-10-10), inserted as wave 7 in
`docs/plans/implementation.md` (release readiness renumbered to 8).
Targets not yet implemented; decomposition pending. Campaigns will run
**Status:** landed — adopted (2026-10-10), inserted as wave 7 in
`docs/plans/implementation.md` (release readiness renumbered to 8);
wave 1's three targets implemented, seeds committed, detached campaigns
run (§9 log), the corpus-replay gate on AGENTS.md's verification list.
Wave 2 (`mem_opseq`, §3.4) stays deferred post-release. Campaigns run
via the detached runner (§5) — **never as a foreground child of an
agent session** (the family's §5 rule, inherited verbatim).
**Date:** 2026-10-10
@@ -114,7 +116,7 @@ Upstream layout rules that carry over, in alkstore terms:
binaries — the stable-toolchain corpus replay
(`cargo test --manifest-path fuzz/shared/Cargo.toml`) runs the SAME
invariant functions the fuzzer runs. This is the fuzz gate (§6);
it joins AGENTS.md's verification commands when wave 7 lands.
it is on AGENTS.md's verification commands (landed with wave 7, §9).
- **Workspace/publish exclusion:** this repo's root `Cargo.toml` is a
workspace-only manifest (no root package) with an explicit members
list, so cargo will not auto-discover `fuzz/shared/` — but the
@@ -318,8 +320,8 @@ The standing policy applies identically (per all four upstream docs):
no hosted CI; no Actions/Gitea Actions workflow files. **Corpus replay
is the fuzz gate as plain `cargo test`**:
`cargo test --manifest-path fuzz/shared/Cargo.toml` replays every
committed seed through the invariant functions. It joins
AGENTS.md's verification commands when wave 7 lands. Campaigns run
committed seed through the invariant functions. It is on
AGENTS.md's verification commands (landed with wave 7, §9). Campaigns run
manually via the detached runner — e.g. before the release, or after
touching `payload.rs`, the three parsers, or `validation.rs`.
@@ -355,4 +357,80 @@ Campaign flag starting points (per target, tuned at wave 7):
ADR-020 §4 (payload encoding posture), ADR-023 §1 (encode
fallibility), ADR-024 §5 (mem wasm acceptance); the wave-6 review
gate's overflow findings (`tasks/review-wave-6.md`);
`docs/plans/implementation.md` §Decided points (the adoption record).
`docs/plans/implementation.md` §Decided points (the adoption record).
---
## 9. Implementation log — wave 7 (2026-10-10)
- **2026-10-10 — wave 1's three targets implemented** (the
decomposition's target tasks, all via the §2 layout: stable
`fuzz/shared` invariant library, thin `fuzz_target!` binaries,
committed seed corpora from `gen_fuzz_seeds.py` — 97 + 66 + 44).
§3.2 resolved to the option-*(b)* `#[doc(hidden)]` exposure
(doc-hidden re-exports of the three parsers + `skip_forward`; the
every_parse campaign's two earlier findings — the boundary-arithmetic
division-by-zero/negative-interval wrap and the i64 gap-subtraction
overflow, fixed crate-side and registered as PROVENANCE D-38). The
§3.2 suite-companion option-*(a)* added no suite rows — suite-pinned
rows already carried equivalence; noted here per the decomposition's
resolution scope.
- **2026-10-10 — first campaign finding: the `Value` round-trip
invariant over-tightened (harness-model, not a crate bug).** The
`payload_codec` campaign crashed ~38 s in on raw input
`46741844073709.51615` (`crash-0ce050f1…`, exit 77):
`decode(encode(v)) ≠ v` — `46741844073709.52` vs `46741844073709.516`.
Reproduced and characterized probe-side (serde_json 1.0.152): its
**default fast-path f64 parser is not exactly correctly-rounded** —
parsing serde_json's own ryu serialization can drift 1–2 ULPs
(measured over 600k random-bits finite f64s: ~30% fail one
parse∘serialize∘parse pass, `max drift = 2.00` ULP, overwhelmingly
exactly 1 ULP; the one-pass re-parse is **not a fixpoint** at extreme
significand/exponent shapes), so the byte-level closure holds only
outside float leaves. This is serde_json's *documented shipping
behavior*, and [ADR-020](../architecture/decisions/020-enqueue-opt-semantics-and-bridges.md)
§4 pins exactly that — "not pinned to a serde_json *feature flavor*
… beyond default shipping behavior" — so enabling its
`float_roundtrip` feature would be an ADR deviation, not a fix.
The harness was the defect: it pinned the suite's example-based
round-trip equality (authored floats are ryu-stable) over the whole
raw input space. Fixed in `fuzz/shared`: the closure is now exact
outside `Number` float leaves, each float leaf drift-bounded at
2 ULPs (pinned-not-regressed), plus a decode-determinism pin (two
parses of the same stored bytes are identical). The crash input is
promoted to `corpus/payload_codec/seed-900`; the campaign re-ran
clean.
- **2026-10-10 — wave-1 15-minute detached campaigns run** (§6 runner,
`-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25`;
flags per §7): all three exited 0, `oom/timeout/crash: 0/0/0`
throughout, empty per-target artifact dirs at completion (the one
first-run crash artifact above is the promoted finding's evidence).
- `payload_codec` (post-fix re-run; the pre-fix run died at the
finding) — 927 s, ~586k execs (~650 exec/s), 41 fork jobs,
coverage 2395 edges (ft 7608, in-memory corp 1544 from the 98
committed seeds).
- `every_parse` — 927 s, ~3.7M execs (~4.1k exec/s), 41 fork jobs,
coverage saturated at 285 edges (ft 3898, corp 805 from the 66
committed seeds) — the three parsers' grammar space is fully
enumerated post-fix; the earlier boundary-arithmetic findings were
the campaign-relevant depth.
- `name_validation` — 939 s, ~59k execs (~66 exec/s), 41 fork jobs,
coverage 327 edges (ft 3248, corp 378 from the 44 committed
seeds); the corpus-replay seeds already covered the domain guards,
and the slow exec/s profile is the long-string shapes, not a
harness defect.
- Grown corpora were **not** merged into the committed seeds (the
§2 policy; `fuzz/.gitignore` now tracks only `seed-*` files —
the scaffold's directory-level negation leaked SHA-named grown
units into `git status` during the campaigns, fixed here).
- **NO crate bugs found by the campaigns.** The sole campaign finding
was the harness-model defect above (fixed harness-side); every
contract invariant held across the adversarial input space: the
typed-failure partition (`Codec` only), deep-input loudness at
serde_json's recursion limit, the empty-byte-store loudness
(ADR-023 §1), the typed round-trip batteries, the three-way parser
agreement and overflow posture, and name-validation
determinism/typedness. That is the family's
successful-negative-result posture (alksocks §7's first campaigns:
zero crate bugs after the two earlier wave-7 crate-side
boundary-arithmetic fixes, which the every_parse task itself landed).