fuzzing decision: adopted as wave 7, release readiness renumbered to wave 8

docs/research/fuzzing.md (new) — the adoption record and campaign plan,
inheriting the alkcall/alktunnels/alksocks/alktty pattern wholesale:
fuzz/ sidecar workspace with nightly pinned inside fuzz/ only, stable
fuzz/shared corpus replay as the fuzz gate, detached campaigns. Wave-1
targets: payload_codec, every_parse (three-way differential over the
per-engine parsers), name_validation; mem_opseq deferred post-release.
Named design points for decomposition: pub(crate) parser exposure
(lean: doc(hidden), the Job::from_row precedent), AGENTS.md gate
flip, workspace/publish exclusion.

docs/plans/implementation.md — wave 7 inserted (fuzzing; decided
2026-10-10, not yet decomposed), release readiness moved to wave 8,
dependency line + wave table + decided points + review-gate list
updated, forward 'wave 7' references re-pointed with a historical note
for pre-renumber records.

AGENTS.md — no-fuzzing posture flipped to adopted; corpus-replay gate
joins the verification list when wave 7 lands.
This commit is contained in:
glm-5.3-flash committed 2026-10-10 14:02:50 +00:00
1 parent 7e8bfde12d
commit 62cddade00
3 files changed
+467 -39

No files matched your search

+9 -3
View File
@@ -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
+100 -36
View File
@@ -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
+358
View File
@@ -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/<target>/ 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::<T>` (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::<Value>`
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<i64, String>`),
- Postgres: `alkstore-postgres/src/scheduler.rs` (`Result<i64>` →
`InvalidSpec`),
- Mem: `alkstore-mem/src/scheduler.rs` (`Result<i64>` → `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 <target> # 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).