diff --git a/docs/plans/implementation.md b/docs/plans/implementation.md index a96e1f3..1fd5edf 100644 --- a/docs/plans/implementation.md +++ b/docs/plans/implementation.md @@ -49,7 +49,7 @@ readiness is wave 8. | 4 | [Postgres engine](#wave-4--postgres-engine): schema bootstrap, pool/open, listener/forwarder, all mechanisms, tx seam, pg backlog column | wave 1 | implemented + reviewed (2026-10-08) | | 5 | [Contract suite](#wave-5--contract-suite): the cross-engine equivalence properties (core-contract.md §Verification backlog), version-stamped per ADR-017 | waves 3 + 4 | implemented + reviewed (2026-10-10) | | 6 | [Mem engine](#wave-6--mem-engine): separate `alkstore-mem` crate, full contract v1 over in-memory mechanisms (tx overlay, queue/scheduler/streams/locks/wakes), the suite's third factory column (born pinned), wasm32 compile-clean acceptance | waves 1 + 5 | implemented + reviewed (2026-10-10) | -| [7](#wave-7--fuzzing) | [Fuzzing](#wave-7--fuzzing): the `fuzz/` sidecar workspace (cargo-fuzz targets over the payload codec, the three `@every` parsers, name validation), committed seed corpora + the corpus-replay gate, detached campaigns | wave 6 | decided (2026-10-10, `docs/research/fuzzing.md`), not yet decomposed | +| [7](#wave-7--fuzzing) | [Fuzzing](#wave-7--fuzzing): the `fuzz/` sidecar workspace (cargo-fuzz targets over the payload codec, the three `@every` parsers, name validation), committed seed corpora + the corpus-replay gate, detached campaigns | wave 6 | decomposed (2026-10-10) | | [8](#wave-8--release-readiness) | [Release readiness](#wave-8--release-readiness): crate docs, deployment matrix final pass, README (written last, honestly), publish prep | wave 7 | not yet decomposed | ## Wave 1 — Foundations @@ -398,6 +398,47 @@ workspace/publish-exclusion verification. Committed crash-input seeds promote into the corpus (the alksocks seed-900/901 posture); grown corpora stay gitignored. +**Wave 7's task set** (the decomposition, 2026-10-10 — the doc's §4 +sequencing: scaffold → exposure → targets in parallel → campaigns + +gate wiring → review gate): + +- `fuzz-scaffold` — the `fuzz/` sidecar workspace per §2's layout, + copied from alksocks (nightly pin inside `fuzz/` only, the stable + `fuzz/shared` skeleton, `run-detached.sh` verbatim, `json.dict`, + `gen_fuzz_seeds.py` skeleton, the `fuzz/.gitignore` corpus pattern), + plus the root workspace's defensive `exclude = ["fuzz"]`. +- `fuzz-parser-exposure` — §3.2's design point, resolved: **option + (b)**, `#[doc(hidden)]` narrow re-exports of the three + `parse_every_interval` implementations + the three `skip_forward` + boundary functions at each engine crate root (substrate untouched — + lineage fidelity), with (a) as the recorded in-suite companion and + (c) rejected per the family's no-`#[cfg(fuzzing)]` experience. +- `fuzz-target-payload-codec` — the §3.1 target (core payload + encode/decode invariants: no-panic, the typed-failure partition, + deep-input loudness, Value + typed round-trips) + its seeds. +- `fuzz-target-every-parse` — the §3.2 differential target over the + three parsers + boundary arithmetic (three-way agreement, overflow + posture, the giant-`n` arms) + its seeds; the wave's load-bearing + target (the wave-6 finding class lives here). +- `fuzz-target-name-validation` — the §3.3 target (totality, + determinism, typed rejections, reserved-prefix rule, cap-boundary + exactness) + its seeds. +- `fuzz-campaigns-and-gate` — detached campaigns per target (§5's + rule, §7's flags), findings dispositioned (crate-side fixed + + promoted to seeds; harness-model into the research doc's + implementation log), the corpus policy's final state, the + AGENTS.md verification-list flip (the corpus-replay gate joins the + merge gates), `fuzz/README.md` finalized. +- `review-wave-7` — the gate (campaigns audit, replay-gate re-run, + corpus policy, the named design points audited resolved, the wasm + posture, records). + +Parallelism note: the scaffold and the exposure task are independent +of each other; the three targets are mutually parallel once their +dependencies land (payload-codec and name-validation need only the +scaffold; every-parse needs scaffold + exposure); campaigns gate on +all three targets. + ## Wave 8 — Release readiness The original wave 6, renumbered twice: wave 7 when the mem engine @@ -609,6 +650,12 @@ decomposes. Specific gates: disposition note + Notes bullet, `review-wave-5` §Notes 4's audit row) retired with pointers, keeping the mem engine's fire posture written against a closed gap before wave 6. +- **Wave-7 decomposition** (2026-10-10) — the fuzzing decision's + task set: scaffold → exposure (§3.2 resolved as option (b) with the + (a) companion) → three targets in parallel → campaigns + gate + wiring (the AGENTS.md flip) → review gate. Seven tasks; the + every-parse differential is the load-bearing one (the wave-6 + finding class). See the Wave 7 section. - **Wave-4 fix-batch decomposition** (2026-10-09) — the general review's findings decomposed into seven `pg-fix-*` tasks + a `review-wave-4-fixes` gate: Finding 1 → `pg-fix-forwarder-reconnect` diff --git a/tasks/fuzz-campaigns-and-gate.md b/tasks/fuzz-campaigns-and-gate.md new file mode 100644 index 0000000..c4e5d18 --- /dev/null +++ b/tasks/fuzz-campaigns-and-gate.md @@ -0,0 +1,89 @@ +--- +id: fuzz-campaigns-and-gate +name: Fuzz campaigns (detached) + corpus-replay gate wiring + AGENTS.md verification-list flip +status: pending +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 + +- [ ] 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 + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/fuzz-parser-exposure.md b/tasks/fuzz-parser-exposure.md new file mode 100644 index 0000000..ddc45fd --- /dev/null +++ b/tasks/fuzz-parser-exposure.md @@ -0,0 +1,89 @@ +--- +id: fuzz-parser-exposure +name: Parser exposure — `#[doc(hidden)]` narrow surface for the three `@every` parsers + boundary arithmetic +status: pending +depends_on: [] +scope: narrow +risk: low +impact: component +level: implementation +tags: [wave-7, fuzzing] +--- + +## Description + +The §3.2 exposure design point, resolved by this decomposition: **option +(b)** — a `#[doc(hidden)]` narrow exposure mirroring the existing +`Job::from_row` / `Schedule::new` constructor precedent — with **(a)** +(the public-`schedule()` path) as the in-suite companion, no new suite +rows. Option (c) (a `fuzzing` feature flag) is rejected per the family's +no-`#[cfg(fuzzing)]` experience. Rationale: the fast byte-level +differential target the upstream docs favor needs direct parser access; +the parsers are engine-internal test surfaces, not contract surface +(ADR-017 §3's consumer-read policy governs core types, not these); and +the `#[doc(hidden)]` precedent already exists in-repo for exactly this +shape of narrow, undocumented-but-public access. + +What gets exposed, per engine — **at the engine crate root only; +substrate files untouched** (the SQLite parser and its `skip_forward` +live in the lineage-governed `substrate/queue_ops.rs`, ADR-012 §3 — the +exposure is a re-export in the engine's `lib.rs`, never a substrate +edit): + +- **alkstore-sqlite**: `#[doc(hidden)] pub use` of + `parse_every_interval` (`Result`) and `skip_forward` + from `substrate::queue_ops`. +- **alkstore-postgres**: `parse_every_interval` (`Result` → + `InvalidSpec`, currently `pub(crate)`) and `skip_forward` (currently + private) widened to `pub(crate)` as needed and `#[doc(hidden)] pub + use`-re-exported from `lib.rs`. +- **alkstore-mem**: same shape as pg (`scheduler.rs`'s parser + + `skip_forward`). + +Doc posture: each re-export carries a one-line doc note marking it a +fuzz/test-only surface (not contract surface, not consumer API, no +stability guarantee) — the `Job::from_row` precedent's doc style. No +engine behavior changes; no parser logic moves; the diff is visibility +plus re-exports. + +The **(a) companion** is recorded, not built: the public `schedule()` +path against the mem and SQLite factories remains the suite's behavioral +pin (already green, three-way); the `every_parse` target may optionally +drive `schedule()` for register-path posture seeds, but the byte-level +differential runs on the exposed functions. + +## Acceptance Criteria + +- [ ] All three engines re-export their `parse_every_interval` and + `skip_forward` from the crate root, `#[doc(hidden)]`, with the + fuzz/test-only doc note +- [ ] `alkstore-sqlite/src/substrate/**` diff is empty (lineage + fidelity preserved — the exposure is engine-side only) +- [ ] The re-exports are reachable from an external crate (pinned by a + compile-level test or the `fuzz-target-every-parse` build itself; + record which) +- [ ] No behavior change: workspace gates green (`cargo build`, + `cargo test`, clippy `-D warnings`, fmt); the engines' own + scheduler/parser test suites untouched and green +- [ ] `cargo check --target wasm32-unknown-unknown -p alkstore-mem` + still clean (visibility widening is wasm-neutral, pinned anyway) + +## References + +- docs/research/fuzzing.md §3.2 (the design point this resolves — + options (a)/(b)/(c) and the lean) +- docs/architecture/decisions/012-forked-substrate-design.md §2–§3 + (one-owner parsers; substrate lineage fidelity) +- docs/architecture/decisions/017-contract-versioning.md §3 (the + `#[doc(hidden)]` precedent's policy boundary — engine-internal + surfaces sit outside it) +- alkstore/src/job.rs (`Job::from_row` — the in-repo doc-hidden + precedent) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/fuzz-scaffold.md b/tasks/fuzz-scaffold.md new file mode 100644 index 0000000..7a49443 --- /dev/null +++ b/tasks/fuzz-scaffold.md @@ -0,0 +1,106 @@ +--- +id: fuzz-scaffold +name: Fuzz sidecar scaffold — `fuzz/` workspace, runner, dict, seed generator, shared-crate skeleton +status: pending +depends_on: [] +scope: moderate +risk: low +impact: phase +level: implementation +tags: [wave-7, fuzzing] +--- + +## Description + +The wave-7 scaffold ([docs/research/fuzzing.md](../research/fuzzing.md) +§2 — the layout is inherited wholesale from the four upstream siblings, +not re-decided): the `fuzz/` sidecar workspace and its stable-toolchain +gate crate, copied from alksocks' working edition with only naming +substitutions. + +- **`fuzz/` sidecar workspace**: `fuzz/Cargo.toml` (`alkstore-fuzz`, + `publish = false`, own `[workspace]` table, `libfuzzer-sys` + + `alkstore-fuzz-shared` path dep; `[[bin]]` entries land with the + target tasks, not here), `fuzz/rust-toolchain.toml` pinning + `channel = "nightly"` + `llvm-tools-preview` **inside `fuzz/` only** + — the crates themselves stay on stable, and the root workspace's + toolchain behavior must be unaffected. +- **`fuzz/shared/`** — `alkstore-fuzz-shared`, the STABLE-toolchain + library whose `#[cfg(test)]` corpus-replay modules are the fuzz gate + (§6): skeleton `lib.rs` with the crate doc carrying the quinn-CI + pattern rationale, plus the `arbitrary_value.rs` `JsonValue` + bounded-depth helper copied from alksocks (the structured-generation + path §3.1's targets share). Dependencies: `alkstore` (path), plus + `serde_json` + `arbitrary` (derive feature). Engine-crate deps land + with the targets that need them. `shared` consumes public API only — + it must never become a dependency of any workspace crate (the wasm + note, §2). +- **`fuzz/run-detached.sh`** — copied verbatim from alksocks + (CWD-independent, `setsid` + `nohup` + full redirection, `pid=` echo, + `FUZZ_RUNTIME_SECS` override). The §5 isolation rule is inherited + unchanged: campaigns never run as a foreground child of an agent + session. +- **`fuzz/json.dict`** — copied from a sibling (alksocks has it at + `fuzz/json.dict`). +- **`fuzz/gen_fuzz_seeds.py`** — the deterministic seed generator + (quiche pattern): alksocks' script as the structural template, + alkstore's own seed functions land per-target. Skeleton lands here + with the shared `seed_name`/`write_seed` plumbing and an empty + target registry. +- **`fuzz/.gitignore`** — the alksocks pattern: `target`, `artifacts`, + `coverage`, and `corpus/*` with `!`-negations for the committed seed + dirs (each target task adds its negation line when its corpus lands). +- **`fuzz/README.md`** — skeleton modeled on alksocks' (layout, targets + table filled by the target tasks, commands incl. the + run-from-`fuzz/`-so-rustup-selects-nightly rule). +- **Root `Cargo.toml`**: add `exclude = ["fuzz"]` to the `[workspace]` + table — defensive insurance against auto-discovery (the alkcall §2 + lesson; this repo's explicit-members list already prevents it, the + exclude makes it structural). The publish-exclusion *verification* + stays wave 8's (§2); this task lands the mechanism. +- **Corpus dirs**: `fuzz/corpus//` scaffolding lands per-target + with the seeds; nothing to commit here. + +Toolchain prerequisites verified present in the dev environment +(2026-10-10): nightly + `cargo-fuzz` 0.13.2 installed; alksocks' +`fuzz/Cargo.lock` exists as the copy reference. + +## Acceptance Criteria + +- [ ] `fuzz/` exists with the §2 layout: sidecar `Cargo.toml` (own + `[workspace]`, `publish = false`), `rust-toolchain.toml` + (nightly + llvm-tools-preview), `shared/` crate, `run-detached.sh` + (executable), `json.dict`, `gen_fuzz_seeds.py`, `README.md`, + `fuzz/.gitignore` +- [ ] `fuzz/shared` compiles and tests green on **stable** via + `cargo test --manifest-path fuzz/shared/Cargo.toml` (the gate + command works from the wave's first task onward) +- [ ] `cargo fuzz build` succeeds from `fuzz/` (nightly selected by the + pin; zero targets yet is acceptable if cargo-fuzz tolerates it — + otherwise a placeholder target documents the check; record which) +- [ ] Root workspace unaffected: `cargo build` / `cargo test` / + `clippy -D warnings` / `fmt --check` green from the root, and the + root toolchain is still stable (the pin did not leak upward) +- [ ] `exclude = ["fuzz"]` present on the root `[workspace]` table +- [ ] `run-detached.sh` is byte-identical to alksocks' modulo the + usage-line naming (no behavior drift) +- [ ] `shared` is not referenced by any workspace crate manifest + (wasm posture intact: `cargo check --target + wasm32-unknown-unknown -p alkstore-mem` still clean) + +## References + +- docs/research/fuzzing.md §2 (layout + toolchain), §5 (runner), §6 + (no-CI / replay gate) +- /workspace/@alkdev/alksocks/fuzz/ (the copy source: Cargo.toml, + rust-toolchain.toml, run-detached.sh, json.dict, gen_fuzz_seeds.py, + .gitignore, README.md) +- docs/plans/implementation.md §Wave 7 + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/fuzz-target-every-parse.md b/tasks/fuzz-target-every-parse.md new file mode 100644 index 0000000..fdfa5d7 --- /dev/null +++ b/tasks/fuzz-target-every-parse.md @@ -0,0 +1,108 @@ +--- +id: fuzz-target-every-parse +name: Fuzz target — `every_parse` (the three `@every` parsers, differential) +status: pending +depends_on: [fuzz-scaffold, fuzz-parser-exposure] +scope: moderate +risk: medium +impact: component +level: implementation +tags: [wave-7, fuzzing] +--- + +## Description + +The §3.2 target: `every_parse` — the wave's load-bearing target, since +the crate's own track record (the wave-6 gate's three edge-domain +findings) lives exactly here. Drives the three per-engine +`parse_every_interval` implementations **differentially** through the +`fuzz-parser-exposure` surfaces, plus each engine's next-boundary +arithmetic (`skip_forward`): + +- SQLite: `alkstore-sqlite`'s re-export (substrate-owned, + `Result`), +- Postgres: `alkstore-postgres`'s re-export (`Result` → + `InvalidSpec`), +- Mem: `alkstore-mem`'s re-export (`Result` → `InvalidSpec`). + +Input: arbitrary `&[u8]` → UTF-8-lossy `String`s (lossy is deliberate — +the engines receive `&str`, always valid UTF-8; the generator still +stresses the grammar's edge shapes). Invariants (§3.2, verbatim): + +- **no-panic on any string** (each parser, each boundary path); +- **typed rejection**: every rejection is the parser's own typed error + (`InvalidSpec` on pg/mem; the substrate's typed error under the + engine's mapping on SQLite) — never a panic, never a silent `Ok` on + a grammar-violating shape the authored tests already reject; +- **three-way agreement**: the accept-set and the interval value are + identical across the three parsers for every input. Error *identity* + is NOT pinned equal (the substrate's `String` vs `InvalidSpec` is a + known, typed asymmetry) — the differential pins the Ok/Err partition + and the `Ok` value; +- **overflow posture**: the boundary paths (`skip_forward` driven with + adversarial `(next_fire_at, now, interval)` triples — the + `i64::MAX` neighborhood, negative shapes, `d`-unit multiplier + blowups) reject-or-saturate per each engine's pinned semantics, + never wrap or panic — the wave-6 scheduler-register fix's invariant + over the whole domain; +- **the `n <= 0` and giant-`n` arms**: leading-zero forms, digit + overflow at exactly `i64::MAX/86400`, positive-zero rejection. + +The register-path posture (parse → first-boundary `checked_add` → +reject with `InvalidSpec`, the wave-6 alignment) is suite-pinned +already; the target's optional `(a)`-companion leg may drive the public +`schedule()` on the mem/SQLite factories for register-path seeds — +optional, recorded if used. + +- **`fuzz_targets/every_parse.rs`** + **`shared/src/every_parse.rs`** + (invariants + `#[cfg(test)]` replay), `[[bin]]` entry, `shared` + manifest gains the three engine crates (path deps — stable-buildable, + they already are). +- **Seeds** via `gen_fuzz_seeds.py`'s `every_parse` function: every + grammar-valid unit form (`s|m|h|d`), multi-digit and leading-zero + forms, `@every 0s` / `@every -5s` shapes, the `i64::MAX`-neighborhood + and `i64::MAX/86400` digit-overflow forms, `d`-multiplier blowups, + whitespace/garbage/truncation shapes, non-grammar prefixes (`@once`, + `@hourly`-adjacent strings). Corpus committed; gitignore negation. +- **`fuzz/README.md`** targets-table row. + +Risk note: medium because the differential may surface a real +three-way divergence (that is the target's purpose) — a finding is +*success*, dispositioned per the campaign task's posture (fix inline or +escalate), not a task failure. + +## Acceptance Criteria + +- [ ] `fuzz_targets/every_parse.rs` + `shared/src/every_parse.rs` + exist; `cargo fuzz build` builds the target +- [ ] All five §3.2 invariant classes implemented and exercised by both + the fuzz path and the replay module; the three-way agreement + asserted on every input (fuzz) and every seed (replay) +- [ ] Seed corpus committed, deterministic-generated, gitignore + negation in place +- [ ] `cargo test --manifest-path fuzz/shared/Cargo.toml` green; + `shared` clippy/fmt gates green +- [ ] Root workspace gates still green; substrate diff still empty +- [ ] Any three-way divergence found during target development is + recorded in Notes with its disposition (fixed / escalated), not + silently absorbed + +## References + +- docs/research/fuzzing.md §3.2 (target spec + the exposure decision + this target consumes), §4, §7 +- docs/architecture/decisions/009-queue-and-scheduler.md §2 (the + `@every` grammar the three parsers implement) +- docs/architecture/decisions/012-forked-substrate-design.md §2 + (one-owner rule — why there are three parsers to differ) +- tasks/review-wave-6.md (the wave-6 finding class this target + generalizes; the register-overflow alignment) +- tasks/fuzz-parser-exposure.md (the surfaces this target drives) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/fuzz-target-name-validation.md b/tasks/fuzz-target-name-validation.md new file mode 100644 index 0000000..039a414 --- /dev/null +++ b/tasks/fuzz-target-name-validation.md @@ -0,0 +1,71 @@ +--- +id: fuzz-target-name-validation +name: Fuzz target — `name_validation` (core name validation) +status: pending +depends_on: [fuzz-scaffold] +scope: narrow +risk: trivial +impact: component +level: implementation +tags: [wave-7, fuzzing] +--- + +## Description + +The §3.3 target: `name_validation` — the cheapest target, driving core's +already-`pub` validation surface (`alkstore/src/validation.rs`: +`validate_name` over all `NameKind`s, `validate_shared_name`, +`validate_local_name`). No exposure needed. + +- **`fuzz_targets/name_validation.rs`** + **`shared/src/ + name_validation.rs`** (invariants + `#[cfg(test)]` replay), + `[[bin]]` entry. Input: arbitrary `&[u8]` → strings (lossy + a + structured path), lengths spanning the caps, all `NameKind`s. +- **Invariants** (§3.3, verbatim): total (no panic on any input); + deterministic accept/reject (same input → same verdict, pinned by + double-invocation in the invariant fn); rejection only via typed + `Error::InvalidName` (or `ReservedName` where the reserved-prefix + rule fires — both are the taxonomy's typed rejections; nothing else, + no panic); the reserved-prefix rule (`__alkstore_`, ADR-014 §1's + shared namespace) rejects in every casing/boundary form the fuzzer + finds; length-boundary exactness at the cap (the ±1 neighborhood — + a name at the cap is accepted, one past it rejected, per the pinned + rule). +- **Seeds** via `gen_fuzz_seeds.py`'s `name_validation` function: + cap-boundary lengths (±1, ±2), the reserved prefix in plain / + suffixed / embedded forms, empty and whitespace-only strings, unicode + and control-char shapes, the reserved listener-reconnected string. + Corpus committed; gitignore negation. +- **`fuzz/README.md`** targets-table row. + +Low expected yield (the doc says so) — the value is the entry-point +domain guard demonstrated over the whole string space, the gate's +guard-order discipline made continuous. + +## Acceptance Criteria + +- [ ] `fuzz_targets/name_validation.rs` + `shared/src/ + name_validation.rs` exist; `cargo fuzz build` builds the target +- [ ] All §3.3 invariants implemented and exercised by both the fuzz + path and the replay module +- [ ] Seed corpus committed, deterministic-generated, gitignore + negation in place +- [ ] `cargo test --manifest-path fuzz/shared/Cargo.toml` green; + `shared` clippy/fmt gates green +- [ ] Root workspace gates still green + +## References + +- docs/research/fuzzing.md §3.3 (target spec), §4, §7 +- docs/architecture/core-contract.md §Naming / reserved namespace +- docs/architecture/decisions/014-naming-and-reserved-namespace.md §1 + (the reserved-prefix rule the target demonstrates) +- alkstore/src/validation.rs (the surface) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/fuzz-target-payload-codec.md b/tasks/fuzz-target-payload-codec.md new file mode 100644 index 0000000..897ab74 --- /dev/null +++ b/tasks/fuzz-target-payload-codec.md @@ -0,0 +1,88 @@ +--- +id: fuzz-target-payload-codec +name: Fuzz target — `payload_codec` (core payload encode/decode invariants) +status: pending +depends_on: [fuzz-scaffold] +scope: moderate +risk: low +impact: component +level: implementation +tags: [wave-7, fuzzing] +--- + +## Description + +The §3.1 target: `payload_codec`, driving core's payload surface +(`alkstore/src/payload.rs` — `encode_payload`, `payload_as::`) — +already-`pub` API, no exposure needed. + +- **`fuzz_targets/payload_codec.rs`** — the thin `fuzz_target!` + wrapper (3-line pattern) delegating to + `alkstore_fuzz_shared::payload_codec::fuzz_payload_codec(data)`; + the `[[bin]]` entry lands in `fuzz/Cargo.toml`. +- **`shared/src/payload_codec.rs`** — the invariant logic, with the + `#[cfg(test)]` corpus-replay module (the gate). Input: raw `&[u8]` + plus `arbitrary`-derived `serde_json::Value`s (the + `JsonValue` bounded-depth helper from the scaffold). Invariants + (§3.1, verbatim): + - **no-panic on any byte sequence** (the baseline); + - **typed-failure partition**: every decode failure is + `Error::Codec` — no other variant, no panic (the ADR-023 §1 + fallibility pin, demonstrated across the input space); + - **deep-input loudness**: serde_json's recursion limit surfaces as + typed `Codec`, never a stack overflow; + - **Value round-trip**: `encode_payload(v)` → `payload_as::` + yields `v` for every arbitrary `Value` (canonicalization is + serde_json's, pinned-not-regressed); + - **typed round-trip**: for a representative typed-payload set, + `encode(Value::from(t))` decodes back to `t` (the ADR-020 §4 + decode side). +- **Seeds** (§3.1's list, via `gen_fuzz_seeds.py` gaining the + `payload_codec` function): valid JSON of every Value variant, + truncations, deep nesting at and past the recursion limit, non-UTF-8 + bytes, trailing garbage, oversized numbers, duplicate keys, the + `Null`-decode shape (ADR-023 §1's empty-byte-store corruption note). + Corpus committed under `fuzz/corpus/payload_codec/`; the + `fuzz/.gitignore` gains the `!corpus/payload_codec` negation. +- **`fuzz/README.md`** targets-table row. +- `shared`'s manifest gains `alkstore` (path) if the scaffold didn't + already carry it, plus `serde_json` + `arbitrary`. + +Campaign flags are the campaign task's concern (`-max_len=65536 +-dict=json.dict` starting point, §7); this task lands target + seeds + +replay. + +## Acceptance Criteria + +- [ ] `fuzz_targets/payload_codec.rs` + `shared/src/payload_codec.rs` + exist; `cargo fuzz build` builds the target (nightly, ASAN) +- [ ] All five §3.1 invariants implemented in `shared` and exercised by + both the fuzz path and the `#[cfg(test)]` replay module +- [ ] Seed corpus committed (`fuzz/corpus/payload_codec/`), generated + by `python3 fuzz/gen_fuzz_seeds.py` (deterministic — re-running + is a no-op diff); gitignore negation in place +- [ ] `cargo test --manifest-path fuzz/shared/Cargo.toml` green + (replays the committed corpus through the invariants) +- [ ] `shared` clippy/fmt gates green + (`cargo clippy --manifest-path fuzz/shared/Cargo.toml + --all-targets -- -D warnings`, `fmt --check`) +- [ ] Root workspace gates still green + +## References + +- docs/research/fuzzing.md §3.1 (target spec), §4 (wave-1 scope), §7 + (flags) +- docs/architecture/decisions/020-payload-encoding.md §4 (the encoding + posture the round-trips pin) +- docs/architecture/decisions/023-fourth-review-round.md §1 (encode + fallibility) +- /workspace/@alkdev/alksocks/fuzz/shared/src/datagram_codec.rs (the + invariant-module + replay-test pattern) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/review-wave-7.md b/tasks/review-wave-7.md new file mode 100644 index 0000000..2718172 --- /dev/null +++ b/tasks/review-wave-7.md @@ -0,0 +1,86 @@ +--- +id: review-wave-7 +name: Review gate — wave 7 (fuzzing) +status: pending +depends_on: [fuzz-campaigns-and-gate] +scope: moderate +risk: low +impact: phase +level: review +tags: [wave-7, review] +--- + +## Description + +The wave-7 review gate (the plan's §Review gates, wave-7 entry). Audit +the fuzz wave as landed: + +1. **Campaigns audit**: each target's campaign ran via the detached + runner (logs in `fuzz/artifacts/`), clean — or its findings fixed + and crash inputs promoted to committed seeds with the re-run clean. + Verify no campaign was run foreground (the §5 rule — process + hygiene, checked by reading the logs' provenance and the task + Notes). +2. **Corpus-replay gate green on stable**: + `cargo test --manifest-path fuzz/shared/Cargo.toml` re-run this + session; the replay modules exercise the same invariant functions + the fuzz targets run (the §2 load-bearing layout rule — read one + target's wiring end to end to confirm no drift between fuzz path + and replay path). +3. **Corpus policy audit**: committed seeds per policy (hand-made + + promoted crash inputs; deterministic generator re-run is a no-op + diff); grown corpora + artifacts gitignored; nothing + campaign-generated leaked into git. +4. **Named design points audited as resolved** (the plan's wave-7 + list): §3.2's parser exposure (option (b) landed, substrate diff + empty, the (a)-companion disposition recorded); the AGENTS.md + verification-list flip (gate line present, fuzzing paragraph + truthful); the workspace/publish-exclusion mechanism (`exclude = + ["fuzz"]` on the root workspace table — the *verification* that no + published package leaks the sidecar stays wave 8's, per §2). +5. **Harness-model findings**: documented in the research doc's + implementation log (or the successful-negative-result disposition + recorded there); any crate-side finding's fix verified against the + invariant that caught it. +6. **wasm posture**: `cargo check --target wasm32-unknown-unknown -p + alkstore-mem` still clean; `shared` is referenced by no workspace + crate. +7. **Workspace gates re-run green this session** (`cargo build`, + `cargo test`, clippy `-D warnings`, fmt) + the `shared` clippy/fmt + gates. +8. **Records**: `implementation.md`'s wave-7 row updated + (implemented + reviewed); the research doc's Status header updated + (targets implemented, campaigns run); task files audited (all + wave-7 dependency tasks completed, Notes/Summary filled). + +## Acceptance Criteria + +- [ ] Campaign audit recorded per target (runtime, flags, outcome, + findings + dispositions) +- [ ] Corpus-replay gate re-run green this session; fuzz-path/replay + drift check recorded for at least one target end to end +- [ ] Corpus policy verified (committed set, gitignore posture, + generator determinism) +- [ ] The three named design points audited resolved, each with its + evidence +- [ ] Research doc implementation log present (findings or the + negative-result disposition) +- [ ] wasm check + all gates re-run green this session +- [ ] `implementation.md` wave-7 row updated; task files audited + +## References + +- docs/plans/implementation.md §Wave 7, §Review gates (wave-7 entry) +- docs/research/fuzzing.md (§2 layout rules, §4 scope, §5 runner rule, + §6 gate, §7 verification) +- tasks/review-wave-6.md (the gate pattern this mirrors) +- /workspace/@alkdev/alksocks/docs/research/fuzzing.md §7 (the + negative-result disposition precedent) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file