--- id: fuzz-target-name-validation name: Fuzz target — `name_validation` (core name validation) status: completed depends_on: [fuzz-scaffold] scope: narrow risk: trivial impact: component level: implementation tags: [wave-7, fuzzing] --- ## Description The §3.3 target: `name_validation` — the cheapest target, driving core's already-`pub` validation surface (`alkstore/src/validation.rs`: `validate_name` over all `NameKind`s, `validate_shared_name`, `validate_local_name`). No exposure needed. - **`fuzz_targets/name_validation.rs`** + **`shared/src/ name_validation.rs`** (invariants + `#[cfg(test)]` replay), `[[bin]]` entry. Input: arbitrary `&[u8]` → strings (lossy + a structured path), lengths spanning the caps, all `NameKind`s. - **Invariants** (§3.3, verbatim): total (no panic on any input); deterministic accept/reject (same input → same verdict, pinned by double-invocation in the invariant fn); rejection only via typed `Error::InvalidName` (or `ReservedName` where the reserved-prefix rule fires — both are the taxonomy's typed rejections; nothing else, no panic); the reserved-prefix rule (`__alkstore_`, ADR-014 §1's shared namespace) rejects in every casing/boundary form the fuzzer finds; length-boundary exactness at the cap (the ±1 neighborhood — a name at the cap is accepted, one past it rejected, per the pinned rule). - **Seeds** via `gen_fuzz_seeds.py`'s `name_validation` function: cap-boundary lengths (±1, ±2), the reserved prefix in plain / suffixed / embedded forms, empty and whitespace-only strings, unicode and control-char shapes, the reserved listener-reconnected string. Corpus committed; gitignore negation. - **`fuzz/README.md`** targets-table row. Low expected yield (the doc says so) — the value is the entry-point domain guard demonstrated over the whole string space, the gate's guard-order discipline made continuous. ## Acceptance Criteria - [ ] `fuzz_targets/name_validation.rs` + `shared/src/ name_validation.rs` exist; `cargo fuzz build` builds the target - [ ] All §3.3 invariants implemented and exercised by both the fuzz path and the replay module - [ ] Seed corpus committed, deterministic-generated, gitignore negation in place - [ ] `cargo test --manifest-path fuzz/shared/Cargo.toml` green; `shared` clippy/fmt gates green - [ ] Root workspace gates still green ## References - docs/research/fuzzing.md §3.3 (target spec), §4, §7 - docs/architecture/core-contract.md §Naming / reserved namespace - docs/architecture/decisions/008-contract-v1-pinning.md §4/§5 — the reserved-prefix rule and the typed-rejection taxonomy the target demonstrates (the description's `014-naming-and-reserved-namespace.md` reference was stale: ADR-014 is the outbox ADR; the reserved namespace is ADR-008 §4, with the kinds list extended by ADR-014 outbox and ADR-009 schedule — corrected here) - alkstore/src/validation.rs (the surface) ## Notes - **No length cap is pinned — the "length-boundary exactness at the cap" invariant was rendered as verdict-class length-independence.** The description (and fuzzing.md §3.3) pinned a cap with a ±1 neighborhood ("a name at the cap is accepted, one past it rejected"), but no cap exists anywhere in the normative stack: ADR-008 §4 pins only non-empty (whitespace-only-rejected) + the reserved-prefix rule, core-contract §Naming adds nothing about length, and `alkstore/src/validation.rs` checks nothing about length. Per AGENTS.md the ADR wins, so the shipped invariant pins what is true: the verdict class is length-independent (no length is ever a rejection cause) across probed neighborhoods (0↔1 empty boundary, 11↔12 prefix length, 63↔64 pg-identifier bytes, 4095↔4096 target max_len, 65536 payload-cap scale). The only exact boundaries in the pinned rule — empty↔one-char, whitespace-only↔content, and the byte-exact prefix — are pinned exactly. - **The "rejects in every casing/boundary form" invariant is pinned with its exact-case direction.** The rule is byte-exact and leading-only (`starts_with(RESERVED_PREFIX)`), so the battery pins the byte-exact prefix rejecting (alone, suffixed, whitespace-suffixed — anything not all-whitespace) **and** the near-miss forms accepting on the shared kind (casing deviations, missing trailing underscore, embedded and whitespace-led occurrences) plus the local kind taking the non-empty rule only (leading reserved prefix legal there, per ADR-008 §4's annotation and the `NameKind::Local` doc). - **Corpus-replay floor set at 32** (44 seeds committed), following the sibling targets' pattern (64 / 48). ## Summary Landed the §3.3 target: `fuzz_targets/name_validation.rs` (thin `fuzz_target!` wrapper, like the sibling targets) + `shared/src/name_validation.rs` (invariants + `#[cfg(test)]` corpus replay), its `[[bin]]` entry, `pub mod` in shared's lib.rs, the `name_validation_seeds()` generator (44 deterministic seeds committed: cap-neighborhood lengths, empty/whitespace-only, the reserved prefix plain/suffixed/at-length plus the listener-reconnected string, seven near-miss forms, unicode/control/invalid-UTF-8 shapes), the `!corpus/name_validation` gitignore negation, and the README targets-table row. Invariants: total (no-panic over lossy and `arbitrary`-structured strings plus derived boundary forms); determinism (double invocation on both kinds); typed-only rejections (narrowed to a `Verdict` enum that panics on anything but `Accept`/`InvalidName`/`ReservedName`); wrapper equivalence (`validate_shared_name`/`validate_local_name` vs `validate_name` on each `NameKind`); classification asserted against a locally recomputed rule of record (verbatim-name-carrying errors — names not trimmed); the casing/boundary battery; and the length-independence battery (see Notes). Verified: `cargo test --manifest-path fuzz/shared/Cargo.toml` green (6/6, 44-seed replay included); shared clippy `-D warnings` + fmt `--check` green; `cargo fuzz build` green from `fuzz/` (nightly pin); root `cargo build` / `cargo test` (all suites pass) / clippy `-D warnings` / fmt `--check` green; seed regeneration deterministic; corpus files trackable (negation verified via `git check-ignore`). Campaign runs are `fuzz-campaigns-and-gate`'s concern (this task is one of its three dependencies).