diff --git a/AGENTS.md b/AGENTS.md index f7fc64f..814f9b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,9 +92,15 @@ cargo clippy --all-targets -- -D warnings cargo fmt --check ``` -No fuzzing is adopted (no fuzz corpus replay gate — the -alksocks/alktty/alktunnels fuzz posture was not carried into this -crate's plan); revisit only if a task or ADR introduces it. +Fuzzing is adopted (2026-10-10 — wave 7; `docs/research/fuzzing.md`), +modeled on the alkcall/alktunnels/alksocks/alktty pattern: the `fuzz/` +sidecar workspace pins nightly inside `fuzz/` only, the stable +`fuzz/shared` crate replays the committed corpora as a plain +`cargo test` gate, and campaigns run only via the detached runner — +never as a foreground child of an agent session. The +corpus-replay gate (`cargo test --manifest-path fuzz/shared/Cargo.toml`) +joins the verification list above when wave 7 lands; until then there +is no fuzz gate to run. ## Architecture Context diff --git a/docs/plans/implementation.md b/docs/plans/implementation.md index cff92ed..a96e1f3 100644 --- a/docs/plans/implementation.md +++ b/docs/plans/implementation.md @@ -1,10 +1,10 @@ --- status: draft -last_updated: 2026-10-10 (wave 6 implemented and reviewed — the mem -engine landed, the suite's third column green under the current-thread -flavor, the wasm32 gate clean, the wave-6 gate passed with three -inline edge-domain fixes; Findings 2–3 and the flake watch carry to -wave 7.) +last_updated: 2026-10-10 (the fuzzing decision resolved: adopted and +inserted as wave 7 per docs/research/fuzzing.md; release readiness +renumbered to wave 8. Waves 1–6 implemented + reviewed; the wave-6 +gate passed with three inline edge-domain fixes, and Findings 2–3 and +the flake watch carry to the wave-8 pre-release pass.) --- # alkstore — Implementation plan @@ -27,14 +27,19 @@ boundaries. ## The waves Dependency logic in one line: core → (substrate fork ∥ postgres -engine) → sqlite engine → contract suite → mem engine → release -readiness. The substrate fork is contract-blind (ADR-012 §2), so it -needs only the workspace scaffold from wave 1; the Postgres engine +engine) → sqlite engine → contract suite → mem engine → fuzzing → +release readiness. The substrate fork is contract-blind (ADR-012 §2), +so it needs only the workspace scaffold from wave 1; the Postgres engine needs only the core crate. Waves 2 and 4 are therefore independent of each other and could run in either order (or in parallel, if agents are ever available in parallel). The mem engine (wave 6) needs the core crate plus the finished suite (wave 5) — born pinned, per -[ADR-024](../architecture/decisions/024-mem-engine.md). +[ADR-024](../architecture/decisions/024-mem-engine.md). The fuzzing +wave (wave 7) needs the mem engine finished (its campaigns precede the +release freeze; the targets ride in-process surfaces — core payloads, +the three per-engine parsers, name validation — see +[docs/research/fuzzing.md](../research/fuzzing.md)), and release +readiness is wave 8. | Wave | Contents | Depends on | Status | |---|---|---|---| @@ -44,7 +49,8 @@ core crate plus the finished suite (wave 5) — born pinned, per | 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 | [Release readiness](#wave-7--release-readiness): crate docs, deployment matrix final pass, README (written last, honestly), publish prep; fuzzing decision | wave 6 | 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 | decided (2026-10-10, `docs/research/fuzzing.md`), not yet decomposed | +| [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 @@ -106,7 +112,7 @@ shaped this wave's task set: wiring (the constructor task). - **Staying put as ordered**: M-1's retention-failure test and N-5's panic probe → wave 5's contract-suite rows; N-1's growth-posture - review → wave 7. + review → wave 8. ## Wave 4 — Postgres engine @@ -260,7 +266,8 @@ tier (no durability, single-process, ephemeral by design, never fleet-valid), adopted for the wasm/sandbox and downstream-adapter economics the consumer inventory's engine-tier row records (operator-authority, 2026-10-10 — the row-first gate satisfied before -this wave was assumed). Release readiness moved to wave 7. +this wave was assumed). Release readiness moved to wave 7, and again +to wave 8 when the fuzzing wave inserted at 7 (2026-10-10). The wave's shape mirrors the engine waves' rhythm: constructor + tx overlay → mechanisms in parallel → scheduler/outbox → suite @@ -311,7 +318,7 @@ ADR-024), and the mem factory's suite wiring. Riding the window: review 003's Finding 1 (the pg scheduler transient-error retry loop, MEDIUM — engine-side, small, mem-independent); Findings 2–3 follow their park recommendation into -wave 7's pre-release pass. +the pre-release pass (now wave 8). **Wave 6's task set** (the decomposition, 2026-10-10 — the pg wave's rhythm: foundations → seam → mechanisms in parallel → scheduler/outbox @@ -356,21 +363,63 @@ the mechanisms; the mechanisms are mutually parallel. The suite pre-task and the Findings-1 rider are independent of the mem chain entirely and can run any time in the window (the rider must close before the gate). -## Wave 7 — Release readiness +## Wave 7 — Fuzzing -The original wave 6, renumbered when the mem engine inserted at wave -6 ([ADR-024](../architecture/decisions/024-mem-engine.md)): crate +**Decided 2026-10-10, before release readiness decomposed** — the +plan's one decided-at-implementation deferral is discharged here rather +than folded into the release pass: adoption (the family pattern per +alkcall/alktunnels/alksocks/alktty), inserted as this wave with release +readiness renumbered to wave 8, so campaigns run before the release +freeze. The rationale, target inventory, invariants, campaign posture, +and inherited tooling are written up in +[docs/research/fuzzing.md](../research/fuzzing.md) — this section +carries only the plan-level shape. + +Scope (wave 1 of the doc's two-wave framing; `mem_opseq` is the +deferred wave 2, post-release): the `fuzz/` sidecar workspace laid out +per the upstream pattern — nightly pinned inside `fuzz/` only, the +stable-toolchain `fuzz/shared` crate whose corpus replay is the fuzz +gate (`cargo test --manifest-path fuzz/shared/Cargo.toml`), the +detached runner copied from alksocks — and three targets: +`payload_codec` (core payload encode/decode invariants), +`every_parse` (the three per-engine `@every` parsers, driven +differentially — the wave-6 overflow-finding class), and +`name_validation`. Campaigns run detached per target. + +Named design points the decomposition resolves (each already sketched +in the doc): §3.2's parser-exposure decision — the three parsers are +`pub(crate)`, the family posture prefers already-`pub` surfaces, and +the doc leans (b) `#[doc(hidden)]`-style narrow exposure (the +`Job::from_row` precedent) with a public-`schedule()` companion; +the AGENTS.md posture flip (already done at adoption — the no-fuzzing +line now records the decision; the corpus-replay gate joins the +verification list only when the wave lands); and the +workspace/publish-exclusion verification. Committed crash-input seeds +promote into the corpus +(the alksocks seed-900/901 posture); grown corpora stay gitignored. + +## Wave 8 — Release readiness + +The original wave 6, renumbered twice: wave 7 when the mem engine +inserted at wave 6 ([ADR-024](../architecture/decisions/024-mem-engine.md)), +wave 8 when the fuzzing wave inserted at 7 (2026-10-10, the section +above): crate docs, the deployment matrix final pass (across all three engines now), the crate README (written last, honestly), publish prep, N-1's -growth-posture review — and the **fuzzing decision** (the -alksocks/alktty/alktunnels pattern), the one remaining -decided-at-implementation deferral. Decomposes after wave 6's review +growth-posture review. Decomposes after wave 7's review gate, with review 003's Findings 2–3 riding the pre-release pass, and the wave-5 gate's flake-watch flag carried (`tasks/review-wave-5.md` §Notes 6 — the two unreproducible pg row failures; if either reproduces, a dedicated investigative session before any postulate-and-fix). +Historical note for records written before this second renumber +(review 001/002/003, the wave-3..6 task files): their "wave 7" +references to the release pass mean what is now wave 8; per the +family's renumber-annotation precedent (`tasks/review-wave-5.md`'s +ADR-024 annotations), affected records get swept (or annotated) when +wave 8 decomposes — history is not rewritten in place. + ## Decided points - **Contract-suite layout — option (a)**: a small internal @@ -393,17 +442,21 @@ before any postulate-and-fix). ([ADR-024](../architecture/decisions/024-mem-engine.md))** — a separate `alkstore-mem` crate, full contract v1, honest-ephemeral posture, wasm32 compile-clean acceptance; wave 6, with release - readiness renumbered to wave 7. The deferral discharged: its + readiness renumbered to wave 7 (later 8 — see the fuzzing decision + below). The deferral discharged: its collapse condition ("implementation time") was reached early via the consumer pause + the wasm/sandbox motivation, with the row-first gate satisfied by the consumer inventory's engine-tier row (operator-authority, 2026-10-10). -- **Fuzzing adoption** (the alksocks/alktty/alktunnels pattern): - remains the one decided-at-implementation deferral, recorded here - so it surfaces as an explicit decision point in wave 7 (per - AGENTS.md's no-fuzzing posture, revisit only if a task or ADR - introduces it). Renumbered from wave 6 together with the mem - decision's move. +- **Fuzzing adoption** (the alkcall/alktunnels/alksocks/alktty + pattern): **decided 2026-10-10, before wave 8 decomposed — adopted, + inserted as wave 7** (release readiness renumbered to wave 8). The + deferral is discharged: the rationale, target inventory, invariants, + and campaign posture live in `docs/research/fuzzing.md`; the + decomposition resolves the named design points (parser exposure, + gate wiring, publish exclusion). AGENTS.md's no-fuzzing posture line + flipped at adoption; the corpus-replay gate joins the verification + list when the wave lands. ## Review gates @@ -428,6 +481,13 @@ decomposes. Specific gates: (harness pre-task disposed), the engine's own mechanism tests, and the `engine-mem.md` spec read against the pinned ADR-024 text — the gate that flips the mem spec toward `stable`. +- **Wave 7 review** — the fuzz wave's gate: the targets' campaigns run + clean via the detached runner (or their findings fixed and promoted + to seeds), the corpus-replay gate green on stable, the seeds + committed per the corpus policy, the harness-model findings + (if any) documented in the research doc's implementation log, and + the named design points (parser exposure, publish exclusion) + audited as resolved. ## Review rounds so far @@ -439,7 +499,7 @@ decomposes. Specific gates: `docs/reviews/001-waves-1-2-general-review.md`) — M-1 fixed inline (sweep savepoint scope); M-2/N-2/N-4/N-6 resolved as ADR-023 pre-decomposition; lint removal + suite adds folded into waves 3/5; - N-1 recorded for release readiness (now wave 7). + N-1 recorded for release readiness (now wave 8). - **Wave 3 review gate** (`review-wave-3`) — engine-vs-contract conformance code-read clean; two findings fixed inline (the `open_writer_connection` boundary move out of `substrate/mod.rs`; @@ -507,15 +567,17 @@ decomposes. Specific gates: three-way arithmetic equivalence green (backoff curve, opts resolution on all three columns); the wasm32 acceptance items verified (compile-clean, tokio sync+time subset, no blocking calls, - flavor enforced); the rider's arms verified (Finding 1 discharged; - Findings 2–3 confirmed still parked for wave 7). Three edge-domain + flavor enforced); the rider's arms verified (Finding 1 discharged; + Findings 2–3 confirmed still parked for the release pass — wave 8 + since this renumber). Three edge-domain fixes landed inline at the gate: the stream cursor read's `i64::MAX` overflow (`Bound::Excluded`), `trim_to`'s `i64::MAX` horizon endpoint (`Bound::Included`), and the scheduler register path aligning with SQLite on rejecting a parseable interval whose first boundary overflows (`InvalidSpec`). Unpinned contract-adjacent - observations recorded in the task's Notes for the wave-7 pre-release - pass: the multi-thread publish-to-wake ordering tolerance (same + observations recorded in the task's Notes for the pre-release pass + (wave 8 since this renumber): the multi-thread publish-to-wake + ordering tolerance (same shape implied for the SQL engines; nothing pins wake-fire order vs offset order), the `heartbeat(extend)`/`save_offset`/ `EventReceiver::try_recv`-closed classifications missing from the @@ -531,11 +593,13 @@ decomposes. Specific gates: - **Wave-5 residue decomposition** (2026-10-10) — the wave-5 review's outstanding items audited post-gate: the one recorded "for a later task" gap (scheduler fire-wake latency parity) decomposed as - `pg-fix-scheduler-fire-wake` (pre-wave-6, standalone); the flake-watch - flag carried into the wave-7 section above; review 003's Finding 1 - already decomposed (`pg-fix-scheduler-tick-seam`, the wave-6 rider) - and Findings 2–3 already parked to wave 7. See the Wave 7 section for - the watch carriage. + `pg-fix-scheduler-fire-wake` (pre-wave-6, standalone); the flake-watch + flag carried into the release-readiness section above (now wave 8); + review 003's Finding 1 + already decomposed (`pg-fix-scheduler-tick-seam`, the wave-6 rider) + and Findings 2–3 already parked to the release pass. See the + Wave 8 section for + the watch carriage. - **Wave-5 residue closure** (2026-10-10) — the remaining "recorded for a later task" item landed: `pg-fix-scheduler-fire-wake` closed the scheduler fire-wake latency-parity gap (the tick's diff --git a/docs/research/fuzzing.md b/docs/research/fuzzing.md new file mode 100644 index 0000000..dc8d2fc --- /dev/null +++ b/docs/research/fuzzing.md @@ -0,0 +1,358 @@ +# 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 +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 +**Upstream precedent (inherited wholesale, not re-litigated):** +- `/workspace/@alkdev/alkcall/docs/research/fuzzing.md` — the origin doc; + one real finding (duplicate adopt/open destroying the live channel, + fixed 0.8.1). +- `/workspace/@alkdev/alktunnels/docs/research/fuzzing.md` — six + harness-model findings, no crate bugs. +- `/workspace/@alkdev/alksocks/docs/research/fuzzing.md` — the most + recent full edition; this doc borrows its structure and §7's + successful-negative-result disposition. +- `/workspace/@alkdev/alktty/docs/plans/fuzzing.md` — the + op-sequence-target pattern (`session_opseq`) for the deferred wave 2. + +Tooling, corpus policy, runner, and the no-hosted-CI posture come from +those docs unchanged; this doc does not re-decide them, it scopes them +to this crate. + +--- + +## 1. Should this crate be fuzzed? + +**Yes — adopted 2026-10-10** (recorded in `implementation.md`'s decided +points; AGENTS.md's prior no-fuzzing posture flips with this decision). +Four lines: + +1. **The family adopted it everywhere it had parse or arithmetic + surfaces, and the pattern is mature.** Four sibling crates (alkcall, + alktunnels, alksocks, alktty) run the identical layout: a `fuzz/` + sidecar workspace (nightly pinned *inside* `fuzz/` only), thin + `fuzz_target!` binaries, invariant logic in a stable-toolchain + `fuzz/shared` crate so **corpus replay is plain `cargo test` — the + fuzz gate** (which fits this repo's no-CI, coordinator-run-merge- + gates posture exactly). The tooling and runner are copied wholesale; + no new decisions are needed. + +2. **This crate has already produced exactly the bug class fuzzing + enumerates.** The wave-6 review gate caught three edge-domain + defects by hand: the stream cursor read's `i64::MAX` overflow + (`Bound::Excluded`), `trim_to`'s `i64::MAX` horizon endpoint, and + the scheduler register path's first-boundary overflow rejection. + All three live in hand-rolled arithmetic and parsing — and the + crate deliberately owns *three* copies of that arithmetic (the + `@every` parsers and boundary math per engine, ADR-012 §2's + one-owner rule), plus the equal-jitter backoff curve and + opts-stamping resolution per engine. Hand-rolled overflow-prone + numeric code × three owners is precisely the shape the upstream + docs adopted fuzzing for. Review found these spotwise; fuzzing + demonstrates the domain guards across the whole input space. + +3. **The contract suite's blind spot is fuzzing's strength.** The + suite (`alkstore-contract-suite`) is example-based — curated stamps + and inputs a human authored (no proptest). What it cannot provide: + random *malformed* input exploration (payload bytes that are not + what any engine would write, spec strings outside the grammar), + stateful interleavings, and resource-bound assertions under + adversarial sequences. The alksocks §1.3 argument applies as-is. + +4. **Sequencing: cheapest now.** First release is the wave-8 deliverable; + after that the public surface is frozen and targets become regression + protection. All four upstream docs adopt at/just-before first publish + for exactly this reason — and the plan's wave-7 deferral carrier is + being discharged on schedule, before wave 8 decomposes. + +**The honest caveat** (mirroring all upstream docs): the crate has no +`unsafe`, typed errors end-to-end (thiserror taxonomy, ADR-008 §5), a +no-`unwrap()`/no-panic policy enforced by review, and payload decode +delegates to serde_json — itself battle-fuzzed and recursion-limited +(oversized nesting is a typed `Error::Codec`, not a stack overflow). +Expected yield is therefore not memory-safety bugs. It is (a) the +demonstration that the typed-error / no-panic contracts hold on +adversarial input, (b) overflow-arm discovery in the hand-rolled +arithmetic (the wave-6 finding class — the crate's own track record +says these were *found by humans, spotwise*, so more arms plausibly +exist), (c) regression protection for the published surface. If the +campaigns find nothing crate-side, that is the family's +successful-negative-result posture (alksocks' first campaigns: zero +crate bugs, three harness-model findings, all valuable — the +fuzzer-as-harness-checker pattern). + +## 2. Toolchain (inherited, not re-decided) + +Identical to all four upstream docs: **cargo-fuzz + `arbitrary`**, +nightly confined to the `fuzz/` subtree via `fuzz/rust-toolchain.toml` +(the siblings pin `channel = "nightly"` + `llvm-tools-preview` there); +the crates themselves stay on stable. AFL/bolero/libafl/honggfuzz/ +OSS-Fuzz not adopted; sanitizer: `--sanitizer=address` with debug +assertions (cargo-fuzz defaults) — no MSAN needed (no `unsafe`). + +**Layout (the upstream pattern, load-bearing):** + +``` +fuzz/ +├── Cargo.toml alkstore-fuzz (nightly-only bins; own [workspace]) +├── rust-toolchain.toml pins nightly for this subtree only +├── fuzz_targets/ thin fuzz_target! wrappers (3-line each) +├── shared/ alkstore-fuzz-shared — STABLE-toolchain library +│ └── src/… invariant logic + corpus replay (§3) +├── corpus// committed hand-made seeds (§4) +├── gen_fuzz_seeds.py deterministic seed generator (quiche pattern) +├── run-detached.sh §5 detached runner (copy from alksocks; CWD-independent) +└── README.md +``` + +Upstream layout rules that carry over, in alkstore terms: + +- **Invariant logic lives in `fuzz/shared/`**, not inline in the target + 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. +- **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 + defensive `exclude = ["fuzz"]` on the workspace table is added anyway + (the alkcall §2 auto-discovery failure's lesson, cheap insurance). + Wave 8's publish-prep task verifies the fuzz sidecar cannot leak + into any published package (`exclude` lists on the crates if needed). +- **Corpus policy:** commit hand-made seeds, gitignore grown corpora + (quinn/h2 model). Crash inputs found by campaigns are promoted to + committed seeds (the alksocks seed-900/901 posture). +- **wasm note:** the wave-6 constraint on `alkstore-mem` (compiles + clean for `wasm32-unknown-unknown`, tokio sync+time only) is + unaffected — the fuzz sidecar builds host-side only, and `shared` + must not introduce a dependency that breaks the mem crate's wasm + posture (it consumes the same public API). + +## 3. Target inventory + +Two feasibility constraints shape the list: every target should drive +already-`pub` API (the family's posture — no `#[cfg(fuzzing)]` +exposure; see §3.2 for the one snag), and campaign targets must be +in-process (the pg engine needs a live server — its paths are not +campaign targets; §3.5). + +### 3.1 `payload_codec` (wave 1 — core, `alkstore/src/payload.rs`) + +Input: raw `&[u8]`, plus `arbitrary`-derived `serde_json::Value`s. +Drives `encode_payload` and `payload_as::` (over `Value` and a +representative typed-payload set). Invariants: + +- **no-panic on any byte sequence** (the baseline). +- **typed-failure partition**: every decode failure is `Error::Codec` + — no other error 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 (the deep-nesting probes). +- **Value round-trip**: `encode_payload(v)` → `payload_as::` + yields `v` for every arbitrary `Value` (canonicalization is + serde_json's, pinned-not-regressed, not re-derived). +- **typed round-trip**: for the representative payload structs, + `encode(Value::from(t))` decodes back to `t` — the ADR-020 §4 + "engines store exactly these bytes" contract's decode side. + +Seeds: 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). + +Dictionary: `json.dict` (copy from a sibling). + +### 3.2 `every_parse` (wave 1 — the three parsers, differential) + +Input: arbitrary `&[u8]` → UTF-8-lossy `String`s (lossy is deliberate: +the engines receive `&str`, which is always valid UTF-8 — but the +generators should still stress the grammar's edge shapes). Drives all +three `parse_every_interval` implementations: + +- SQLite: `alkstore-sqlite/src/substrate/queue_ops.rs` + (`parse_every_interval`, substrate-owned, `Result`), +- Postgres: `alkstore-postgres/src/scheduler.rs` (`Result` → + `InvalidSpec`), +- Mem: `alkstore-mem/src/scheduler.rs` (`Result` → `InvalidSpec`), + +plus each engine's next-boundary arithmetic (the checked-add / +skip-forward paths — the wave-6 finding class). Invariants: + +- **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 — never a panic or an `Ok`). +- **three-way agreement**: the accept-set and the interval value are + identical across the three parsers for every input. The contract + suite (suite-pinned rows) pins behavioral equivalence on *valid* + specs; fuzzing extends that to *arbitrary* strings — cheap, and it + pins the ADR-009 §2 grammar three ways continuously. +- **overflow posture**: the boundary paths reject + (checked-add → `InvalidSpec`/equivalent), never wrap or panic — the + wave-6 scheduler-register fix's invariant, over the whole interval + domain (`u64::MAX`-shaped digits, the 9223372036854775807 neighborhood, + `d`-unit multipliers). +- **the `n <= 0` and giant-`n` arms**: leading-zero forms, digit + overflow at exactly `i64::MAX/86400`, positive-zero rejection. + +**The exposure design point (wave-7 decomposition resolves):** the +three parsers are `pub(crate)` — the family's "drive already-`pub` APIs" +posture has no precedent for this. Options named for decomposition: + +- **(a)** drive through the public `schedule()` path against the mem + (and SQLite — in-process, temp files) factories with arbitrary spec + strings: pure public surface, but slower per-exec and no byte-level + differential (behavioral only). +- **(b)** a `#[doc(hidden)]` narrow exposure mirroring the existing + `Job::from_row` / `Schedule::new` constructor precedent + (ADR-017 §3 consumer-read policy does not govern engine-internal + test surfaces; the parsers are engine-owned, not contract surface) — + keeps the fast byte-level differential target the upstream docs favor. +- **(c)** a dev-only feature flag (`fuzzing`), rejected by the family's + no-`#[cfg(fuzzing)]` experience unless the harness shape provably + needs it. + +Lean: **(b)** for the fast differential, **(a)** as its in-suite +companion (the suite already pins behavior; (a) adds no new rows, only +optional corpus seeds). Decomposition decides; this doc does not +pre-commit. + +### 3.3 `name_validation` (wave 1 — core, `alkstore/src/validation.rs`) + +Input: arbitrary `&[u8]` → strings, all `NameKind`s, lengths spanning +the caps. Drives `validate_name` / `validate_shared_name` / +`validate_local_name`. Invariants: total (no panic); deterministic +accept/reject; rejection only via typed `Error::InvalidName`; the +reserved-prefix rule (`__alkstore_`, ADR-014 §1's shared namespace) +rejects in every casing/boundary form the fuzzers find; length +boundary exactness at the cap (the ±1 neighborhood). + +Cheapest target; low expected yield but keeps the entry-point domain +guard demonstrated (the gate reviews audit guard-order discipline; this +demonstrates it over the whole string space). + +### 3.4 `mem_opseq` (wave 2 — deferred, post-release) + +The stateful op-sequence target, the alktty `session_opseq` / +alkcall `manager_routing` analog: `MemStore::new()` + an +`arbitrary`-derived op sequence (enqueue/claim/ack/reclaim/dead-letter/ +sweep/publish/trim/save_offset/try_lock/lock-extend/notify/notify_tx/ +schedule-tick/outbox enqueue, interleaved with tx begin/commit/drop and +close), driven over a current-thread tokio runtime (the suite's +mem-column flavor). Invariant sketch (decomposition firms it): + +- no-panic across any op sequence; +- the ADR-010 queue state machine's legality (no transition the state + machine forbids; reclaim-eats-attempt; dead-letter only from + exhausted attempts); +- per-consumer stream offsets monotone; receiver arms correct after + trim and close; +- tx overlay: commit-merge atomicity, drop-=-discard (no ghost rows), + wake-at-commit — the suite's tx rows as invariants over random + interleavings instead of authored ones; +- lock TTL expiry/re-acquisition; the loser's clean `None`. + +Deferred for the same reasons alksocks deferred its wave 2: wave 1 +covers the byte surfaces; this is a large harness; and the crate is +pre-release — regression protection matters most once the surface +freezes. Note the mem engine is the *only* engine fully fast enough +for this (SQLite is feasible but file-bound per-op; pg needs a server). + +### 3.5 What is deliberately NOT a target + +- **The pg engine's server-reachable paths** (forwarder, listener, + pool open, DSN handling) — not in-process; campaigns cannot drive + them without a live server. Their failure arms are owned by the + wave-4/5/6 test suite (the forwarder reconnect tests etc.). +- **The SQLite substrate internals beyond the public surface** — + substrate edits are governed by the lineage-fidelity diff discipline + (ADR-012 §3 / ADR-018), not by fuzzing; its inherited test floor + remains the owner of that surface. The `every_parse` target touches + it only through the public/hidden exposure decision (§3.2). +- **Arithmetic beyond §3.2** (backoff curve, opts resolution) for wave + 1 — the suite pins their outputs three-way; fuzzing them is a + wave-2 candidate if the opseq target's invariants want them. The + equal-jitter curve's determinism makes fuzz yield unlikely. +- **wasm targets** — libFuzzer is host-side; the mem wasm acceptance + stays compile-clean (§2's wasm note). + +## 4. Scope: one implemented wave + a deferred second + +**Wave 1 (wave 7 in the plan, this session's scope):** the three +targets of §3.1–3.3 — `payload_codec`, `every_parse`, `name_validation`. +All in-process, all fast (byte/grammar-level), all invariants sketched +above. Sequencing mirrors upstream: scaffold (layout of §2, runner, +dict, seed generator) → targets in parallel → seeds committed → +10–30 min detached campaigns per target → fix anything found (crash +inputs promoted to seeds) → corpus replay green. + +**Wave 2 (deferred — post-release):** `mem_opseq` (§3.4). Recorded in +§3.4 so the deferral is a named scope decision, not a pending-client +hedge. + +The wave's decomposition resolves the named open points: §3.2's +exposure decision, the AGENTS.md verification-list flip (corpus replay +joins the merge gates when the wave lands), and the publish-exclusion +verification (§2). + +## 5. Operational isolation: campaigns never share fate with the agent session + +**Inherited verbatim from the upstream docs (alksocks §5)** — an +environment constraint, not a nice-to-have. Three layers, all +mandatory: (1) libFuzzer soft limits (`-rss_limit_mb=2048` +`-malloc_limit_mb=2048`) — never `ulimit -v` under ASAN; (2) fork mode +(`-fork=1`) as blast-radius containment; (3) process detachment — +`run-detached.sh` wraps `cargo fuzz run` in `setsid` + `nohup` + full +redirection; poll, never `wait`. The alksocks runner is copied +as-is (it is CWD-independent and repo-layout-agnostic). Also +inherited: `cargo fuzz run` returns before its last fork-mode children +fully exit — `pgrep -af` to LIST, kill by explicit pid from the +runner's echoed `pid=`, never pattern-kill-and-recheck in one call. + +## 6. No hosted CI + +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 +manually via the detached runner — e.g. before the release, or after +touching `payload.rs`, the three parsers, or `validation.rs`. + +## 7. Verification commands + +```bash +cargo test # workspace gates (stable) +cargo test --manifest-path fuzz/shared/Cargo.toml # corpus replay (fuzz gate, from wave 7) +cargo fuzz build # in fuzz/ (nightly, ASAN) +fuzz/run-detached.sh # campaigns (never foreground) +cargo clippy --all-targets -- -D warnings +cargo fmt --check +cargo clippy --manifest-path fuzz/shared/Cargo.toml --all-targets -- -D warnings +cargo fmt --manifest-path fuzz/shared/Cargo.toml --check +cargo check -p alkstore-mem --target wasm32-unknown-unknown # mem's wasm acceptance +``` + +Campaign flag starting points (per target, tuned at wave 7): +`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`. + +## 8. References + +- Upstream docs (authoritative for tooling, runner, corpus policy, + no-CI posture): alkcall / alktunnels / alksocks / alktty, paths in + the header. +- RUSTSEC-2026-0037 (quinn-proto — the maintainers' fuzzing-coverage + statement; the upstream §1 datapoint). +- This crate: ADR-008 §5 (error taxonomy), ADR-009 §2 (the `@every` + grammar), ADR-010 (queue state machine), ADR-012 §2 (one-owner + parsers/arithmetic), ADR-017 §3–§4 (exposure/versioning policy), + 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). \ No newline at end of file