fuzz campaigns + gate: wave 7 close — three 15-min detached campaigns clean (payload_codegen finding harness-model: serde_json default f64 parse drifts 1-2 ULPs; invariant pinned to 2-ULP float-leaf drift + decode determinism, crash input promoted as payload_codec/seed-900), fuzz/.gitignore retightened to seed-* tracking, corpus-replay gate joins AGENTS.md verification list, research doc §9 implementation log, README finalized
This commit is contained in:
1 parent
5043f7bcd8
commit
f7de30e57e
7 files changed
+236
-24
No files matched your search
@@ -88,6 +88,7 @@ gate:
|
||||
```
|
||||
cargo build # workspace compiles
|
||||
cargo test # workspace tests (task-scoped: cargo test -p <crate>)
|
||||
cargo test --manifest-path fuzz/shared/Cargo.toml # fuzz gate — corpus replay through the invariants
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo fmt --check
|
||||
```
|
||||
@@ -96,11 +97,9 @@ 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.
|
||||
`cargo test` gate (above), and campaigns run only via the detached
|
||||
runner (`fuzz/run-detached.sh`) — never as a foreground child of an
|
||||
agent session (§5 of the research doc).
|
||||
|
||||
## Architecture Context
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# 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
|
||||
**Status:** landed — adopted (2026-10-10), inserted as wave 7 in
|
||||
`docs/plans/implementation.md` (release readiness renumbered to 8);
|
||||
wave 1's three targets implemented, seeds committed, detached campaigns
|
||||
run (§9 log), the corpus-replay gate on AGENTS.md's verification list.
|
||||
Wave 2 (`mem_opseq`, §3.4) stays deferred post-release. Campaigns 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
|
||||
@@ -114,7 +116,7 @@ Upstream layout rules that carry over, in alkstore terms:
|
||||
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.
|
||||
it is on AGENTS.md's verification commands (landed with wave 7, §9).
|
||||
- **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
|
||||
@@ -318,8 +320,8 @@ 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
|
||||
committed seed through the invariant functions. It is on
|
||||
AGENTS.md's verification commands (landed with wave 7, §9). Campaigns run
|
||||
manually via the detached runner — e.g. before the release, or after
|
||||
touching `payload.rs`, the three parsers, or `validation.rs`.
|
||||
|
||||
@@ -356,3 +358,79 @@ Campaign flag starting points (per target, tuned at wave 7):
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
## 9. Implementation log — wave 7 (2026-10-10)
|
||||
|
||||
- **2026-10-10 — wave 1's three targets implemented** (the
|
||||
decomposition's target tasks, all via the §2 layout: stable
|
||||
`fuzz/shared` invariant library, thin `fuzz_target!` binaries,
|
||||
committed seed corpora from `gen_fuzz_seeds.py` — 97 + 66 + 44).
|
||||
§3.2 resolved to the option-*(b)* `#[doc(hidden)]` exposure
|
||||
(doc-hidden re-exports of the three parsers + `skip_forward`; the
|
||||
every_parse campaign's two earlier findings — the boundary-arithmetic
|
||||
division-by-zero/negative-interval wrap and the i64 gap-subtraction
|
||||
overflow, fixed crate-side and registered as PROVENANCE D-38). The
|
||||
§3.2 suite-companion option-*(a)* added no suite rows — suite-pinned
|
||||
rows already carried equivalence; noted here per the decomposition's
|
||||
resolution scope.
|
||||
- **2026-10-10 — first campaign finding: the `Value` round-trip
|
||||
invariant over-tightened (harness-model, not a crate bug).** The
|
||||
`payload_codec` campaign crashed ~38 s in on raw input
|
||||
`46741844073709.51615` (`crash-0ce050f1…`, exit 77):
|
||||
`decode(encode(v)) ≠ v` — `46741844073709.52` vs `46741844073709.516`.
|
||||
Reproduced and characterized probe-side (serde_json 1.0.152): its
|
||||
**default fast-path f64 parser is not exactly correctly-rounded** —
|
||||
parsing serde_json's own ryu serialization can drift 1–2 ULPs
|
||||
(measured over 600k random-bits finite f64s: ~30% fail one
|
||||
parse∘serialize∘parse pass, `max drift = 2.00` ULP, overwhelmingly
|
||||
exactly 1 ULP; the one-pass re-parse is **not a fixpoint** at extreme
|
||||
significand/exponent shapes), so the byte-level closure holds only
|
||||
outside float leaves. This is serde_json's *documented shipping
|
||||
behavior*, and [ADR-020](../architecture/decisions/020-enqueue-opt-semantics-and-bridges.md)
|
||||
§4 pins exactly that — "not pinned to a serde_json *feature flavor*
|
||||
… beyond default shipping behavior" — so enabling its
|
||||
`float_roundtrip` feature would be an ADR deviation, not a fix.
|
||||
The harness was the defect: it pinned the suite's example-based
|
||||
round-trip equality (authored floats are ryu-stable) over the whole
|
||||
raw input space. Fixed in `fuzz/shared`: the closure is now exact
|
||||
outside `Number` float leaves, each float leaf drift-bounded at
|
||||
2 ULPs (pinned-not-regressed), plus a decode-determinism pin (two
|
||||
parses of the same stored bytes are identical). The crash input is
|
||||
promoted to `corpus/payload_codec/seed-900`; the campaign re-ran
|
||||
clean.
|
||||
- **2026-10-10 — wave-1 15-minute detached campaigns run** (§6 runner,
|
||||
`-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25`;
|
||||
flags per §7): all three exited 0, `oom/timeout/crash: 0/0/0`
|
||||
throughout, empty per-target artifact dirs at completion (the one
|
||||
first-run crash artifact above is the promoted finding's evidence).
|
||||
- `payload_codec` (post-fix re-run; the pre-fix run died at the
|
||||
finding) — 927 s, ~586k execs (~650 exec/s), 41 fork jobs,
|
||||
coverage 2395 edges (ft 7608, in-memory corp 1544 from the 98
|
||||
committed seeds).
|
||||
- `every_parse` — 927 s, ~3.7M execs (~4.1k exec/s), 41 fork jobs,
|
||||
coverage saturated at 285 edges (ft 3898, corp 805 from the 66
|
||||
committed seeds) — the three parsers' grammar space is fully
|
||||
enumerated post-fix; the earlier boundary-arithmetic findings were
|
||||
the campaign-relevant depth.
|
||||
- `name_validation` — 939 s, ~59k execs (~66 exec/s), 41 fork jobs,
|
||||
coverage 327 edges (ft 3248, corp 378 from the 44 committed
|
||||
seeds); the corpus-replay seeds already covered the domain guards,
|
||||
and the slow exec/s profile is the long-string shapes, not a
|
||||
harness defect.
|
||||
- Grown corpora were **not** merged into the committed seeds (the
|
||||
§2 policy; `fuzz/.gitignore` now tracks only `seed-*` files —
|
||||
the scaffold's directory-level negation leaked SHA-named grown
|
||||
units into `git status` during the campaigns, fixed here).
|
||||
- **NO crate bugs found by the campaigns.** The sole campaign finding
|
||||
was the harness-model defect above (fixed harness-side); every
|
||||
contract invariant held across the adversarial input space: the
|
||||
typed-failure partition (`Codec` only), deep-input loudness at
|
||||
serde_json's recursion limit, the empty-byte-store loudness
|
||||
(ADR-023 §1), the typed round-trip batteries, the three-way parser
|
||||
agreement and overflow posture, and name-validation
|
||||
determinism/typedness. That is the family's
|
||||
successful-negative-result posture (alksocks §7's first campaigns:
|
||||
zero crate bugs after the two earlier wave-7 crate-side
|
||||
boundary-arithmetic fixes, which the every_parse task itself landed).
|
||||
@@ -1,7 +1,13 @@
|
||||
target
|
||||
corpus/*
|
||||
!corpus/payload_codec
|
||||
corpus/payload_codec/*
|
||||
!corpus/payload_codec/seed-*
|
||||
!corpus/every_parse
|
||||
corpus/every_parse/*
|
||||
!corpus/every_parse/seed-*
|
||||
!corpus/name_validation
|
||||
corpus/name_validation/*
|
||||
!corpus/name_validation/seed-*
|
||||
artifacts
|
||||
coverage
|
||||
+7
-6
@@ -5,8 +5,7 @@ libFuzzer targets for alkstore's parse/codec surfaces (see
|
||||
|
||||
## Layout
|
||||
|
||||
- `fuzz_targets/` — one binary per target; thin `fuzz_target!` wrappers
|
||||
(land with the wave-7 target tasks).
|
||||
- `fuzz_targets/` — one binary per target; thin `fuzz_target!` wrappers.
|
||||
- `shared/` — the invariant logic, as a plain library so normal
|
||||
`cargo test` (stable toolchain) can replay the committed corpora
|
||||
through the same invariants (`fuzz/shared/src/*.rs` `#[cfg(test)]`
|
||||
@@ -14,9 +13,11 @@ libFuzzer targets for alkstore's parse/codec surfaces (see
|
||||
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 with
|
||||
`python3 fuzz/gen_fuzz_seeds.py` from the repo root). Grown corpora
|
||||
and artifacts are gitignored; each target task adds its `!`-negation
|
||||
line when its corpus lands.
|
||||
`python3 fuzz/gen_fuzz_seeds.py` from the repo root — must be a no-op
|
||||
diff on the committed set) plus promoted campaign crash inputs
|
||||
(`seed-900`+ numbering). Only `seed-*` 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: wraps
|
||||
`cargo fuzz run` in `setsid` + `nohup` + log redirection so an OOM
|
||||
in a target can never take down the agent host (research doc §5).
|
||||
@@ -27,7 +28,7 @@ libFuzzer targets for alkstore's parse/codec surfaces (see
|
||||
|
||||
| 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 (serde_json canonicalization pinned); typed round-trip for a representative payload set |
|
||||
| `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 |
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
46741844073709.51615
|
||||
@@ -69,13 +69,23 @@ pub fn fuzz_payload_codec(data: &[u8]) {
|
||||
assert_decode_partition::<Vec<Value>>(data);
|
||||
|
||||
// A successfully-decoded `Value` re-encodes byte-stably: the Value
|
||||
// round-trip closes over the raw input, with serde_json's
|
||||
// canonicalization (duplicate-key last-write-wins, number-to-NormalizedNumber,
|
||||
// key ordering) pinned — not re-derived, just not regressed.
|
||||
// round-trip closes over the raw input up to serde_json's
|
||||
// canonicalization (duplicate-key last-write-wins, key ordering) —
|
||||
// pinned, not re-derived. serde_json's default fast-path f64
|
||||
// parser is not exactly correctly-rounded (its parse of its own
|
||||
// ryu serialization may drift 1–2 ULPs; the ADR-020 §4 posture
|
||||
// pins default serde_json, not the `float_roundtrip` flavor), so
|
||||
// float leaves are compared under the pinned drift bound; every
|
||||
// other shape must be exactly equal.
|
||||
if let Ok(v) = payload_as::<Value>(data) {
|
||||
let bytes = encode_payload(&v).expect("a decoded `Value` must re-encode");
|
||||
let back: Value = payload_as(&bytes).expect("the encoded `Value` must decode");
|
||||
assert_eq!(back, v, "the `Value` round-trip must close over raw input");
|
||||
assert_canonicalized_closure(&v, &back);
|
||||
let back_again: Value = payload_as(&bytes).expect("the encoded `Value` must decode");
|
||||
assert_eq!(
|
||||
back, back_again,
|
||||
"payload decode must be deterministic over stored bytes"
|
||||
);
|
||||
}
|
||||
|
||||
assert_deep_input_loudness();
|
||||
@@ -92,6 +102,68 @@ fn assert_decode_partition<T: serde::de::DeserializeOwned>(data: &[u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// serde_json's default fast-path f64 parse is not exactly
|
||||
/// correctly-rounded: parsing its own ryu serialization can drift by
|
||||
/// at most this many ULPs (one canonicalization step; measured over
|
||||
/// randomized campaigns — see the research doc's implementation log).
|
||||
const FLOAT_DRIFT_ULPS: f64 = 2.0;
|
||||
|
||||
fn assert_canonicalized_closure(v: &Value, back: &Value) {
|
||||
match (v, back) {
|
||||
(Value::Number(a), Value::Number(b)) => {
|
||||
if a.is_f64() && b.is_f64() {
|
||||
let x = a.as_f64().expect("checked is_f64");
|
||||
let y = b.as_f64().expect("checked is_f64");
|
||||
if y == x {
|
||||
return;
|
||||
}
|
||||
let drift = (y - x).abs() / canonicalization_ulp(x);
|
||||
assert!(
|
||||
drift <= FLOAT_DRIFT_ULPS,
|
||||
"float canonicalization drifted {drift} ULPs, over the \
|
||||
{FLOAT_DRIFT_ULPS}-ULP bound: {x} -> {y}"
|
||||
);
|
||||
} else {
|
||||
assert_eq!(a, b, "an integer leaf lost exactness across the round-trip");
|
||||
}
|
||||
}
|
||||
(Value::Array(a), Value::Array(b)) => {
|
||||
assert_eq!(
|
||||
a.len(),
|
||||
b.len(),
|
||||
"array length changed across the round-trip"
|
||||
);
|
||||
for (x, y) in a.iter().zip(b.iter()) {
|
||||
assert_canonicalized_closure(x, y);
|
||||
}
|
||||
}
|
||||
(Value::Object(a), Value::Object(b)) => {
|
||||
assert_eq!(
|
||||
a.len(),
|
||||
b.len(),
|
||||
"object key set changed across the round-trip"
|
||||
);
|
||||
for (key, x) in a {
|
||||
let y = b
|
||||
.get(key)
|
||||
.unwrap_or_else(|| panic!("key {key:?} lost across the round-trip"));
|
||||
assert_canonicalized_closure(x, y);
|
||||
}
|
||||
}
|
||||
_ => assert_eq!(v, back, "the `Value` round-trip must close over raw input"),
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalization_ulp(x: f64) -> f64 {
|
||||
let a = x.abs();
|
||||
let up = a.next_up();
|
||||
if up > a {
|
||||
up - a
|
||||
} else {
|
||||
a - a.next_down()
|
||||
}
|
||||
}
|
||||
|
||||
/// Deep inputs decode loud and typed: past serde_json's recursion
|
||||
/// limit, deserialization fails with `Error::Codec` — never a stack
|
||||
/// overflow, never an untyped panic. The encode side handles deep
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: fuzz-campaigns-and-gate
|
||||
name: Fuzz campaigns (detached) + corpus-replay gate wiring + AGENTS.md verification-list flip
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [fuzz-target-payload-codec, fuzz-target-every-parse, fuzz-target-name-validation]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
@@ -82,8 +82,63 @@ wiring deliverables the plan names:
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
- **One campaign finding, harness-model — no crate bugs (§9 log in the
|
||||
research doc carries the full disposition).** The `payload_codec`
|
||||
campaign crashed ~38 s in on `"46741844073709.51615"`:
|
||||
`decode(encode(v)) ≠ v` for f64 leaves. serde_json's default
|
||||
fast-path f64 parse of its own ryu output drifts 1–2 ULPs
|
||||
(probe-measured: ~30% of random-bits finite f64s fail one
|
||||
parse∘serialize∘parse pass; max drift exactly 2.00 ULP; not a
|
||||
fixpoint at extreme significand shapes). ADR-020 §4 pins default
|
||||
serde_json ("not pinned to a feature flavor"), so the crate-side
|
||||
`float_roundtrip` route is an ADR deviation, not a fix — harness
|
||||
defect. Fixed in `fuzz/shared/src/payload_codec.rs`: exact closure
|
||||
outside `Number` float leaves, float leaves drift-bounded at
|
||||
2 ULPs (`FLOAT_DRIFT_ULPS`, pinned-not-regressed), plus a
|
||||
decode-determinism pin. Crash input promoted to
|
||||
`corpus/payload_codec/seed-900`; re-run clean.
|
||||
- **`fuzz/.gitignore` retightened (its final state was this task's to
|
||||
set).** The scaffold's directory-level negations (`!corpus/<target>`)
|
||||
leaked SHA-named grown-corpus units into `git status` while
|
||||
campaigns ran (libFuzzer fork mode merges new units into the first
|
||||
corpus dir). Now: dirs re-included, contents ignored,
|
||||
`!corpus/<target>/seed-*` back — committed seeds + promoted crash
|
||||
inputs in git, grown corpora invisible.
|
||||
- **Campaigns ran in parallel** (15-min budget each, `FUZZ_RUNTIME_SECS=
|
||||
900`): logs `fuzz/artifacts/{payload_codec-20261010-221350,221951,
|
||||
every_parse-20261010-221351,name_validation-20261010-221352}.log`
|
||||
(the 221350 log is the pre-fix crash evidence; the 215619 log is the
|
||||
every_parse task's earlier 180 s smoke run, superseded). Polling
|
||||
note: the runner's children match `pgrep -af cargo-fuzz` (hyphen),
|
||||
not `cargo fuzz`.
|
||||
- **`gen_fuzz_seeds.py` no-op verified twice**: with the wave's edits
|
||||
stashed and with them applied, a re-run produced a zero diff on the
|
||||
committed corpus set (seed-900 is promotion, not generator output).
|
||||
- **serde_json version pin observed**: the drift characterization was
|
||||
against 1.0.152 (`fuzz/Cargo.lock`); the 2-ULP bound is
|
||||
pinned-not-regressed — a serde_json upgrade may only tighten it.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Wave 7's operational close landed. Three 15-minute detached campaigns
|
||||
(`run-detached.sh`, `-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048
|
||||
-timeout=25`; `payload_codec -max_len=65536 -dict=json.dict`,
|
||||
`every_parse`/`name_validation -max_len=4096 -use_value_profile=1`)
|
||||
all exited 0, `oom/timeout/crash: 0/0/0`, empty artifact dirs at
|
||||
completion: `payload_codec` ~586k execs (~650 exec/s, 2395 edges, corp
|
||||
1544); `every_parse` ~3.7M execs (~4.1k exec/s, 285 edges saturated,
|
||||
corp 805); `name_validation` ~59k execs (327 edges, corp 378). The one
|
||||
finding (payload_codec, ~38 s in) was harness-model — the `Value`
|
||||
round-trip invariant over-tightened against serde_json's default f64
|
||||
parse-precision posture — fixed harness-side crash-input-promoted, and
|
||||
the re-run is clean; no crate bugs (the research doc's
|
||||
successful-negative-result §9). Corpus policy final state verified:
|
||||
only `seed-*` tracked, grown corpora ignored, generator re-run a
|
||||
no-op. The gate joined AGENTS.md's Verification Commands and the
|
||||
fuzzing paragraph reflects the landed state; `fuzz/README.md` finalized
|
||||
(targets table incl. the float-drift pin, commands, corpus policy);
|
||||
research doc §9 appended and its §2/§6 "when wave 7 lands" clauses
|
||||
updated. Verified: workspace build/test, root + `shared`
|
||||
clippy/fmt, the `shared` corpus-replay gate (6 tests incl. the 208
|
||||
committed seeds), and `cargo check -p alkstore-mem --target
|
||||
wasm32-unknown-unknown` all green.
|
||||
Reference in new issue
Block a user