Status: Accepted — implemented. rust/src/kernel/{json,cli}.rs, rust/project/{json_codec,registry}.rb, bin/project_wasm, branch feat/rust-projection. Narrows §9 “WASM embedded Bluebook” — see that section’s own status note.
0007 already committed to one shape: “WASM is a compile target of the same generated artifact, not a separate implementation.” §9 of the implementation plan sketched what that would look like — a browser-facing JS API (const domain = await Hecks.load("domain.wasm"); await domain.dispatch(verb, args)), per-era-compiled modules, and a host-adapter boundary keeping browser-only capabilities (IndexedDB, files, networking, rendering, auth tokens) outside the WASM module as narrow imports. That design was written before 0011 landed and explicitly flagged itself as needing to be “re-scoped against the as-built architecture before starting” — the as-built generator has no era concept at all, and §9’s {era: 12} dispatch option assumed one.
Separately, rust/src/main.rs had never been anything but a hardcoded demo — one Pizzas scenario built from Rust struct literals, printed with println!. bin/rust_conformance (the differential harness 0010 describes) already defined a real JSON contract — {"steps": [...]} in, {"instances", "events", "refusals"} out, the exact shape Ruby’s Fuzzing::Replay.call produces — but its own header comment said outright it “does not invoke Rust itself… until then, ‘give me a JSON file to compare against’ is the whole interface,” because there was no binary with a matching CLI contract to shell out to.
The immediate goal driving this pass wasn’t a browser embedding — it was making the compiled Rust artifact runnable in non-browser WASM hosts (Wasmtime-based server/edge runtimes, the kind relevant to “run domain logic in workers” on a cloud platform), and doing it without adding Cargo dependencies to a crate that had none.
Give the compiled artifact ONE stdin/stdout JSON CLI contract — bin/rust_conformance’s own {"steps"} -> {"instances","events","refusals"} shape — and compile that SAME source for both aarch64-apple-darwin (native) and wasm32-wasip1 (WASM via WASI preview 1).
rust/src/kernel/json.rs is a hand-rolled Json value type, recursive-descent parser, and serializer — zero Cargo dependencies, the same constraint expr.rs/dispatch.rs already hold themselves to. rust/project/json_codec.rb extends the existing per-type code generation (fielded.rb’s own pattern) with paired to_json/from_json methods on every value object and command Args struct, to_json on every entity and aggregate record, and a per-aggregate extract_id that reads identified_by’s dotted paths straight off incoming JSON (the JSON-CLI counterpart to mutations.rb’s identity_components, which only ever ran for a creating command’s typed args before this). rust/project/registry.rb emits one Store (an InMemoryRepository per aggregate) and one dispatch_by_name(store, verb, args_json) router per domain, covering every command the existing command_skip_reason gate already lets through — nothing new is skipped by this layer except a command whose identity needs an extra caller-supplied parameter no JSON step shape carries (Bluebook::Command.Declare’s owner_id, the one live example).
rust/src/kernel/cli.rs (hand-written, generic) drives all of it: parse stdin as {"steps": [...]}, dispatch each step’s verb/args through generated::active::dispatch_by_name (bin/project_rust’s own domain-agnostic alias, rewritten on every regeneration), print the accumulated result in the exact shape bin/rust_conformance already expects. rust/src/main.rs is now just this loop plus stdin/stdout plumbing — nothing domain-specific, nothing that needs hand-editing when a different domain gets regenerated in its place.
Because that CLI touches nothing but std stdin/stdout — no filesystem, no network, no OS calls anywhere in the kernel — the identical source cross-compiles for wasm32-wasip1 unchanged: WASI implements stdio the same way a native process does. bin/project_wasm <domain> is the only genuinely new piece of automation: check the target is installed, run bin/project_rust <domain> to regenerate, cargo build --release --target wasm32-wasip1, copy the artifact to rust/dist/<domain>.wasm. bin/rust_conformance gained a matching subprocess mode (native or a .wasm path) so the differential harness can now actually run either artifact instead of only diffing a pre-existing file — closing the gap its own header comment named.
std-only; Json is a second, deliberately separate value type from expr::Value (which never needs Object/Array, and is never JSON-serialized).script.json and diffing structurally — instances matched byte-for-byte across Ruby (oracle), native Rust, and WASM-under-wasmtime on the first real cross-domain test (Banking: 10 aggregates, composite identities, closed-set data tables, all through the same registry).Hecks.load(...)/domain.dispatch(...) JS API, no per-era module selection (the as-built generator still has no era concept — a real, separate, still-open gap §8 already named), and no host-adapter import boundary for IndexedDB/files/networking/rendering. A browser embedding remains a plausible FUTURE layer on top of this same WASI-stdio contract (a thin JS shim shelling JSON at it, or a separate wasm-bindgen build) — not what this decision builds.Event.payload is BTreeMap<String,String> of {:?}-debug-formatted values, not Ruby’s structured args hash, so events[].payload diffs even when everything else matches; and a GivenNotMet refusal’s Rust wording is the bare given description, while Ruby prefixes it with the command name (“Debit refused — the balance covers it” vs “the balance covers it”). Both are message/shape fidelity gaps in already-built machinery, not something this WASM pass introduced or was scoped to fix — flagged here so they don’t get mistaken for a WASM-specific defect later.emit_closed_set_table’s “data table” shape — Banking::StatementFrequency is the live example) has &'static str fields, not String, because it’s backed by a pub const array of struct literals. from_json for that shape can’t construct a fresh instance from arbitrary runtime JSON at all — it can only SELECT one of the table’s own fixed rows and clone it (json_codec.rb’s emit_closed_set_table_codec), the same match bridging.rb’s literal_hash_bridgeable? already performs for a literal then_set/append source.wasm-bindgen + js-sys, targeting wasm32-unknown-unknown, per §9’s original JS-facing sketch. Would give a real ergonomic domain.dispatch(name, args) browser API directly, but adds the crate’s first Cargo dependencies, only runs where a JS engine is present (rules out a Wasmtime-hosted worker with no JS layer), and would have meant designing a second CLI-adjacent boundary instead of reusing bin/rust_conformance’s already-designed contract. Not rejected forever — a plausible future layer ON TOP of the WASI contract this decision builds, per the Consequences note above.wasm32-unknown-unknown with a hand-rolled ABI (exported i32/pointer functions, manual memory marshaling). Also zero-dependency and JS-independent, but throws away WASI’s stdio for a bespoke memory-passing convention this project would have to invent and hand-maintain, for no benefit over reusing the stdin/stdout contract bin/rust_conformance already had.rust/project.rb’s Rust output. Rejected on the same grounds 0007 already rejected per-host runtimes generally: “WASM is a compile target of the same generated artifact, not a separate implementation.” Everything new here (json_codec.rb, registry.rb) extends the existing generator’s output for BOTH targets, not a WASM-only fork of it.