--- id: fuzz-target-payload-codec name: Fuzz target — `payload_codec` (core payload encode/decode invariants) status: pending depends_on: [fuzz-scaffold] scope: moderate risk: low impact: component level: implementation tags: [wave-7, fuzzing] --- ## Description The §3.1 target: `payload_codec`, driving core's payload surface (`alkstore/src/payload.rs` — `encode_payload`, `payload_as::`) — already-`pub` API, no exposure needed. - **`fuzz_targets/payload_codec.rs`** — the thin `fuzz_target!` wrapper (3-line pattern) delegating to `alkstore_fuzz_shared::payload_codec::fuzz_payload_codec(data)`; the `[[bin]]` entry lands in `fuzz/Cargo.toml`. - **`shared/src/payload_codec.rs`** — the invariant logic, with the `#[cfg(test)]` corpus-replay module (the gate). Input: raw `&[u8]` plus `arbitrary`-derived `serde_json::Value`s (the `JsonValue` bounded-depth helper from the scaffold). Invariants (§3.1, verbatim): - **no-panic on any byte sequence** (the baseline); - **typed-failure partition**: every decode failure is `Error::Codec` — no other variant, no panic (the ADR-023 §1 fallibility pin, demonstrated across the input space); - **deep-input loudness**: serde_json's recursion limit surfaces as typed `Codec`, never a stack overflow; - **Value round-trip**: `encode_payload(v)` → `payload_as::` yields `v` for every arbitrary `Value` (canonicalization is serde_json's, pinned-not-regressed); - **typed round-trip**: for a representative typed-payload set, `encode(Value::from(t))` decodes back to `t` (the ADR-020 §4 decode side). - **Seeds** (§3.1's list, via `gen_fuzz_seeds.py` gaining the `payload_codec` function): valid JSON of every Value variant, truncations, deep nesting at and past the recursion limit, non-UTF-8 bytes, trailing garbage, oversized numbers, duplicate keys, the `Null`-decode shape (ADR-023 §1's empty-byte-store corruption note). Corpus committed under `fuzz/corpus/payload_codec/`; the `fuzz/.gitignore` gains the `!corpus/payload_codec` negation. - **`fuzz/README.md`** targets-table row. - `shared`'s manifest gains `alkstore` (path) if the scaffold didn't already carry it, plus `serde_json` + `arbitrary`. Campaign flags are the campaign task's concern (`-max_len=65536 -dict=json.dict` starting point, §7); this task lands target + seeds + replay. ## Acceptance Criteria - [ ] `fuzz_targets/payload_codec.rs` + `shared/src/payload_codec.rs` exist; `cargo fuzz build` builds the target (nightly, ASAN) - [ ] All five §3.1 invariants implemented in `shared` and exercised by both the fuzz path and the `#[cfg(test)]` replay module - [ ] Seed corpus committed (`fuzz/corpus/payload_codec/`), generated by `python3 fuzz/gen_fuzz_seeds.py` (deterministic — re-running is a no-op diff); gitignore negation in place - [ ] `cargo test --manifest-path fuzz/shared/Cargo.toml` green (replays the committed corpus through the invariants) - [ ] `shared` clippy/fmt gates green (`cargo clippy --manifest-path fuzz/shared/Cargo.toml --all-targets -- -D warnings`, `fmt --check`) - [ ] Root workspace gates still green ## References - docs/research/fuzzing.md §3.1 (target spec), §4 (wave-1 scope), §7 (flags) - docs/architecture/decisions/020-payload-encoding.md §4 (the encoding posture the round-trips pin) - docs/architecture/decisions/023-fourth-review-round.md §1 (encode fallibility) - /workspace/@alkdev/alksocks/fuzz/shared/src/datagram_codec.rs (the invariant-module + replay-test pattern) ## Notes > To be filled by implementation agent ## Summary > To be filled on completion