- sqlite scheduler tick: a failed COMMIT (or ROLLBACK) left the connection released with a stuck-open transaction, wedging the writer slot forever — the one commit site missing the drop-+-reopen-replenish arm; tick now takes the conn by value with a poisoned disposition (drop + replenish), the commit-fault seam reaches the tick, and failed_tick_commit_replenishes_the_writer_slot pins the arm - pg save_offset_tx stored GREATEST($3,0) where the wave-8 gate's negative-seed fix (raw seed, ADR-023 §2 2026-10-11 annotation) covered only the auto-commit form; mem's merged in-tx read clamped a staged negative seed against a phantom zero; both fixed and the tx-form totality pinned by a new stamped suite leg (in_tx_reads_see_own_writes) green on all three columns vs the live harness server - pg: a failed forwarder register leaked the channel refcount forever (no receiver to drop it) — listen/spawn_bridge now unregister on the register error, mirroring the subscribe-failure arm; register doc corrected to put the failure disposition at the call site - pg forwarder: nine poisoned-mutex expect() panics converted to the mem crate's into_inner recovery idiom (no-expect-outside-tests rule) - core encode_payload: a deeply-nested Value aborted the process in serde_json's unguarded serializer recursion and the fuzz pin blessed storing bytes no decoder could read back; encode now rejects nesting past 127 containers (the empirically-verified decode limit) with typed Codec — decode-aligned boundary pins added to the fuzz invariant and value_type_tests, applied pre-publish within the open class-1 window - scheduler sleep deadlines: Instant + Duration overflow caps (MAX_SLEEP_S, tick-and-resleep) on all three engines for far-future boundaries; sqlite tick claim stamp now saturating_add - docs read true again: Error::Closed scope (stream try_recv arm), validation whitespace wording, sqlite lib.rs phantom durability knobs, pg lock.rs honest autocommit wording, mem tx/stream merge + wake- granularity wording; wakes.rs stale allow(dead_code) removed - tests: value_type_tests exercised its own map_err instead of the function under test (fixed); unwrap_or_default reference encodes panic with cause - docs/reviews/004-pre-release-general-review.md records nine fixed + sixteen parked findings (sqlite seam panic-safety armor, watcher post-death-subscribe hang + initial-stat self-kill, pg forwarder TOCTOU/hang/diagnosability, pg BIGINT server-side stamp overflow, mem growth/outbox notes, pg identifier-truncation doc note, smaller) and two false positives caught on verification; implementation.md review-rounds entry + core-contract backlog rows updated - gates: build, workspace tests (sqlite 194, core 27, suite legs new), full live pg column 127+25+9, clippy -D warnings, fmt, fuzz replay 6/6, wasm32 check, doc gate -D warnings — all green
alkstore fuzzing
libFuzzer targets for alkstore's parse/codec surfaces (see
docs/research/fuzzing.md for the full rationale and campaign plan).
Layout
fuzz_targets/— one binary per target; thinfuzz_target!wrappers.shared/— the invariant logic, as a plain library so normalcargo test(stable toolchain) can replay the committed corpora through the same invariants (fuzz/shared/src/*.rs#[cfg(test)]modules; quinn's CI pattern). The fuzz binaries are nightly-only; the shared crate is stable-clean. It consumes alkstore's public API only and must never become a dependency of any workspace crate.corpus/<target>/— committed hand-made seeds (regenerate withpython3 fuzz/gen_fuzz_seeds.pyfrom the repo root — must be a no-op diff on the committed set) plus promoted campaign crash inputs (seed-900+ numbering). Onlyseed-*files are tracked; grown corpora (SHA-named units), artifacts, and coverage output are gitignored and are not merged back into the committed seeds.run-detached.sh— mandatory runner for agent sessions: wrapscargo fuzz runinsetsid+nohup+ log redirection so an OOM in a target can never take down the agent host (research doc §5).json.dict— JSON token dictionary for the structured targets.gen_fuzz_seeds.py— deterministic seed generator (quiche pattern).
Targets
| Target | Drives | Invariants |
|---|---|---|
payload_codec |
encode_payload / payload_as::<T> (alkstore/src/payload.rs) |
no-panic on any byte sequence; every decode failure typed Error::Codec; deep nesting loud at serde_json's recursion limit; Value round-trip closes exactly outside Number float leaves, float leaves pinned to serde_json's default-parse drift bound (1–2 ULPs, see the research doc's implementation log — ADR-020 §4 pins default serde_json, not the float_roundtrip flavor); decode deterministic over stored bytes; typed round-trip for a representative payload set |
every_parse |
the three parse_every_interval implementations (differential, through the engines' #[doc(hidden)] re-exports) + each engine's skip_forward boundary arithmetic |
no-panic on any UTF-8-lossy string (each parser, each boundary path); typed rejection (Error::InvalidSpec on pg/mem, the substrate's error under its mapping on SQLite); three-way agreement on the Ok/Err partition and the Ok interval value (error identity not pinned — the SQLite Result<i64, String> vs InvalidSpec asymmetry is known); overflow posture: skip_forward over adversarial (next_fire_at, now, interval) triples rejects-or-saturates, never wraps or panics; the n <= 0 and giant-n arms (leading zeros, digit overflow at exactly i64::MAX/86400, positive-zero rejection) |
name_validation |
validate_name / validate_shared_name / validate_local_name (alkstore/src/validation.rs) |
no-panic on any byte sequence (lossy-decoded and arbitrary-structured strings); deterministic accept/reject (double-invocation-pinned); rejection only via the typed taxonomy — Error::InvalidName for empty/whitespace-only on both kinds, Error::ReservedName for the leading byte-exact __alkstore_ prefix on the shared kind only (the local kind takes the non-empty rule only, so a leading reserved prefix is legal there); the reserved-prefix rule exact in every casing/boundary form (casing deviations, missing trailing underscore, embedded and whitespace-led forms accept); the rejection carries the supplied name verbatim (names are not trimmed); verdict-class length-independence across the probed neighborhoods (0↔1, 11↔12, 63↔64, 4095↔4096, 65536) — no length cap is pinned (ADR-008 §4), so no length is ever a rejection cause |
Commands
cargo fuzz build and cargo fuzz run must be executed with fuzz/
(or deeper) as the working directory so rustup selects the pinned
nightly toolchain — the detached runner handles that itself.
# build (nightly, pinned by rust-toolchain.toml inside fuzz/; run from fuzz/)
cargo fuzz build
# agent sessions: detached campaign (never foreground; CWD-independent)
FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh <target> [extra libfuzzer args...]
# corpus replay through the invariants (stable toolchain, no nightly)
cargo test --manifest-path fuzz/shared/Cargo.toml
# coverage
cargo fuzz coverage <target>
The fuzz workspace is excluded from the main workspace (exclude in
the root Cargo.toml); it pins its own nightly toolchain via
rust-toolchain.toml and does not affect the root workspace's stable
toolchain or MSRV.