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:
1 parent
7e8bfde12d
commit
62cddade00
3 files changed
+463
-35
No files matched your search
@@ -92,9 +92,15 @@ cargo clippy --all-targets -- -D warnings
|
|||||||
cargo fmt --check
|
cargo fmt --check
|
||||||
```
|
```
|
||||||
|
|
||||||
No fuzzing is adopted (no fuzz corpus replay gate — the
|
Fuzzing is adopted (2026-10-10 — wave 7; `docs/research/fuzzing.md`),
|
||||||
alksocks/alktty/alktunnels fuzz posture was not carried into this
|
modeled on the alkcall/alktunnels/alksocks/alktty pattern: the `fuzz/`
|
||||||
crate's plan); revisit only if a task or ADR introduces it.
|
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
|
## Architecture Context
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-10-10 (wave 6 implemented and reviewed — the mem
|
last_updated: 2026-10-10 (the fuzzing decision resolved: adopted and
|
||||||
engine landed, the suite's third column green under the current-thread
|
inserted as wave 7 per docs/research/fuzzing.md; release readiness
|
||||||
flavor, the wasm32 gate clean, the wave-6 gate passed with three
|
renumbered to wave 8. Waves 1–6 implemented + reviewed; the wave-6
|
||||||
inline edge-domain fixes; Findings 2–3 and the flake watch carry to
|
gate passed with three inline edge-domain fixes, and Findings 2–3 and
|
||||||
wave 7.)
|
the flake watch carry to the wave-8 pre-release pass.)
|
||||||
---
|
---
|
||||||
|
|
||||||
# alkstore — Implementation plan
|
# alkstore — Implementation plan
|
||||||
@@ -27,14 +27,19 @@ boundaries.
|
|||||||
## The waves
|
## The waves
|
||||||
|
|
||||||
Dependency logic in one line: core → (substrate fork ∥ postgres
|
Dependency logic in one line: core → (substrate fork ∥ postgres
|
||||||
engine) → sqlite engine → contract suite → mem engine → release
|
engine) → sqlite engine → contract suite → mem engine → fuzzing →
|
||||||
readiness. The substrate fork is contract-blind (ADR-012 §2), so it
|
release readiness. The substrate fork is contract-blind (ADR-012 §2),
|
||||||
needs only the workspace scaffold from wave 1; the Postgres engine
|
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
|
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
|
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
|
are ever available in parallel). The mem engine (wave 6) needs the
|
||||||
core crate plus the finished suite (wave 5) — born pinned, per
|
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 |
|
| 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) |
|
| 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) |
|
| 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) |
|
| 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
|
## Wave 1 — Foundations
|
||||||
|
|
||||||
@@ -106,7 +112,7 @@ shaped this wave's task set:
|
|||||||
wiring (the constructor task).
|
wiring (the constructor task).
|
||||||
- **Staying put as ordered**: M-1's retention-failure test and N-5's
|
- **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
|
panic probe → wave 5's contract-suite rows; N-1's growth-posture
|
||||||
review → wave 7.
|
review → wave 8.
|
||||||
|
|
||||||
## Wave 4 — Postgres engine
|
## 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
|
fleet-valid), adopted for the wasm/sandbox and downstream-adapter
|
||||||
economics the consumer inventory's engine-tier row records
|
economics the consumer inventory's engine-tier row records
|
||||||
(operator-authority, 2026-10-10 — the row-first gate satisfied before
|
(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
|
The wave's shape mirrors the engine waves' rhythm: constructor + tx
|
||||||
overlay → mechanisms in parallel → scheduler/outbox → suite
|
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
|
Riding the window: review 003's Finding 1 (the pg scheduler
|
||||||
transient-error retry loop, MEDIUM — engine-side, small,
|
transient-error retry loop, MEDIUM — engine-side, small,
|
||||||
mem-independent); Findings 2–3 follow their park recommendation into
|
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
|
**Wave 6's task set** (the decomposition, 2026-10-10 — the pg wave's
|
||||||
rhythm: foundations → seam → mechanisms in parallel → scheduler/outbox
|
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
|
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).
|
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
|
**Decided 2026-10-10, before release readiness decomposed** — the
|
||||||
6 ([ADR-024](../architecture/decisions/024-mem-engine.md)): crate
|
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),
|
docs, the deployment matrix final pass (across all three engines now),
|
||||||
the crate README (written last, honestly), publish prep, N-1's
|
the crate README (written last, honestly), publish prep, N-1's
|
||||||
growth-posture review — and the **fuzzing decision** (the
|
growth-posture review. Decomposes after wave 7's review
|
||||||
alksocks/alktty/alktunnels pattern), the one remaining
|
|
||||||
decided-at-implementation deferral. Decomposes after wave 6's review
|
|
||||||
gate, with review 003's Findings 2–3 riding the pre-release pass, and
|
gate, with review 003's Findings 2–3 riding the pre-release pass, and
|
||||||
the wave-5 gate's flake-watch flag carried
|
the wave-5 gate's flake-watch flag carried
|
||||||
(`tasks/review-wave-5.md` §Notes 6 — the two unreproducible pg row
|
(`tasks/review-wave-5.md` §Notes 6 — the two unreproducible pg row
|
||||||
failures; if either reproduces, a dedicated investigative session
|
failures; if either reproduces, a dedicated investigative session
|
||||||
before any postulate-and-fix).
|
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
|
## Decided points
|
||||||
|
|
||||||
- **Contract-suite layout — option (a)**: a small internal
|
- **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
|
([ADR-024](../architecture/decisions/024-mem-engine.md))** — a
|
||||||
separate `alkstore-mem` crate, full contract v1, honest-ephemeral
|
separate `alkstore-mem` crate, full contract v1, honest-ephemeral
|
||||||
posture, wasm32 compile-clean acceptance; wave 6, with release
|
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
|
collapse condition ("implementation time") was reached early via
|
||||||
the consumer pause + the wasm/sandbox motivation, with the row-first
|
the consumer pause + the wasm/sandbox motivation, with the row-first
|
||||||
gate satisfied by the consumer inventory's engine-tier row
|
gate satisfied by the consumer inventory's engine-tier row
|
||||||
(operator-authority, 2026-10-10).
|
(operator-authority, 2026-10-10).
|
||||||
- **Fuzzing adoption** (the alksocks/alktty/alktunnels pattern):
|
- **Fuzzing adoption** (the alkcall/alktunnels/alksocks/alktty
|
||||||
remains the one decided-at-implementation deferral, recorded here
|
pattern): **decided 2026-10-10, before wave 8 decomposed — adopted,
|
||||||
so it surfaces as an explicit decision point in wave 7 (per
|
inserted as wave 7** (release readiness renumbered to wave 8). The
|
||||||
AGENTS.md's no-fuzzing posture, revisit only if a task or ADR
|
deferral is discharged: the rationale, target inventory, invariants,
|
||||||
introduces it). Renumbered from wave 6 together with the mem
|
and campaign posture live in `docs/research/fuzzing.md`; the
|
||||||
decision's move.
|
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
|
## Review gates
|
||||||
|
|
||||||
@@ -428,6 +481,13 @@ decomposes. Specific gates:
|
|||||||
(harness pre-task disposed), the engine's own mechanism tests, and
|
(harness pre-task disposed), the engine's own mechanism tests, and
|
||||||
the `engine-mem.md` spec read against the pinned ADR-024 text —
|
the `engine-mem.md` spec read against the pinned ADR-024 text —
|
||||||
the gate that flips the mem spec toward `stable`.
|
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
|
## Review rounds so far
|
||||||
|
|
||||||
@@ -439,7 +499,7 @@ decomposes. Specific gates:
|
|||||||
`docs/reviews/001-waves-1-2-general-review.md`) — M-1 fixed
|
`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
|
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;
|
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
|
- **Wave 3 review gate** (`review-wave-3`) — engine-vs-contract
|
||||||
conformance code-read clean; two findings fixed inline (the
|
conformance code-read clean; two findings fixed inline (the
|
||||||
`open_writer_connection` boundary move out of `substrate/mod.rs`;
|
`open_writer_connection` boundary move out of `substrate/mod.rs`;
|
||||||
@@ -508,14 +568,16 @@ decomposes. Specific gates:
|
|||||||
resolution on all three columns); the wasm32 acceptance items
|
resolution on all three columns); the wasm32 acceptance items
|
||||||
verified (compile-clean, tokio sync+time subset, no blocking calls,
|
verified (compile-clean, tokio sync+time subset, no blocking calls,
|
||||||
flavor enforced); the rider's arms verified (Finding 1 discharged;
|
flavor enforced); the rider's arms verified (Finding 1 discharged;
|
||||||
Findings 2–3 confirmed still parked for wave 7). Three edge-domain
|
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`
|
fixes landed inline at the gate: the stream cursor read's `i64::MAX`
|
||||||
overflow (`Bound::Excluded`), `trim_to`'s `i64::MAX` horizon
|
overflow (`Bound::Excluded`), `trim_to`'s `i64::MAX` horizon
|
||||||
endpoint (`Bound::Included`), and the scheduler register path
|
endpoint (`Bound::Included`), and the scheduler register path
|
||||||
aligning with SQLite on rejecting a parseable interval whose first
|
aligning with SQLite on rejecting a parseable interval whose first
|
||||||
boundary overflows (`InvalidSpec`). Unpinned contract-adjacent
|
boundary overflows (`InvalidSpec`). Unpinned contract-adjacent
|
||||||
observations recorded in the task's Notes for the wave-7 pre-release
|
observations recorded in the task's Notes for the pre-release pass
|
||||||
pass: the multi-thread publish-to-wake ordering tolerance (same
|
(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
|
shape implied for the SQL engines; nothing pins wake-fire order vs
|
||||||
offset order), the `heartbeat(extend)`/`save_offset`/
|
offset order), the `heartbeat(extend)`/`save_offset`/
|
||||||
`EventReceiver::try_recv`-closed classifications missing from the
|
`EventReceiver::try_recv`-closed classifications missing from the
|
||||||
@@ -532,9 +594,11 @@ decomposes. Specific gates:
|
|||||||
outstanding items audited post-gate: the one recorded "for a later
|
outstanding items audited post-gate: the one recorded "for a later
|
||||||
task" gap (scheduler fire-wake latency parity) decomposed as
|
task" gap (scheduler fire-wake latency parity) decomposed as
|
||||||
`pg-fix-scheduler-fire-wake` (pre-wave-6, standalone); the flake-watch
|
`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
|
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)
|
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
|
and Findings 2–3 already parked to the release pass. See the
|
||||||
|
Wave 8 section for
|
||||||
the watch carriage.
|
the watch carriage.
|
||||||
- **Wave-5 residue closure** (2026-10-10) — the remaining
|
- **Wave-5 residue closure** (2026-10-10) — the remaining
|
||||||
"recorded for a later task" item landed: `pg-fix-scheduler-fire-wake`
|
"recorded for a later task" item landed: `pg-fix-scheduler-fire-wake`
|
||||||
|
|||||||
@@ -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).
|
||||||
Reference in new issue
Block a user