Status: Accepted — implemented. rust/ (Cargo crate + Ruby generator, bin/project_rust the driver), branch feat/rust-projection. Supersedes 0007’s central claim.
0007 specified a pure code generator: canonical IR, read once at build time, emits real Rust source for every command — argument gates, role checks, given evaluation, mutation application, event emission, all as compiled control flow. The explicit, load-bearing non-goal was “no generic kernel that parses or interprets canonical IR at runtime — the generator emits real code that decides behavior.”
That was tried, in an early slice of the actual build, against Pizzas. It generated one bespoke Rust function per command shape: a plain creating command, an acting command with a given and an append mutation, and each new shape after that — a lifecycle transition, an ensures, an increment — needed a new hand-written case in the generator. This didn’t converge, and it’s the same failure mode 0007 itself already diagnosed for the retired Rust runtime, one level down: that runtime hand-wrote a second dispatcher to keep in parity with Ruby’s; per-shape codegen was hand-writing a second dispatcher’s worth of cases to keep in parity with CommandInterpreter’s own shapes, just inside a generator instead of inside a runtime. Ruby’s own CommandInterpreter#call isn’t N methods, one per command shape — it’s one method walking DISPATCH_ORDER generically over whatever IR a command carries. Fighting that shape in the generator was fighting the actual structure of the problem.
Compile type shapes ahead of time; interpret dispatch behavior at runtime, through one small, hand-written, generic interpreter.
bin/project_rust (Ruby, build-time only) emits real Rust types — structs and enums for every value object, entity, and aggregate record, each with a Fielded trait impl (fn field(&self, name: &str) -> Option<Field>) giving generic, name-based field lookup over otherwise fully static, typed data. It also parses each command’s given/ensures/invariant text through the real Ruby parser (Bluebook::Expression::Evaluator.parse — not a re-implementation) and emits the resulting AST as Expr data literals (Expr::Compare { op: ..., left: ..., right: ... }), not as compiled boolean expressions.
rust/src/kernel/{expr.rs,dispatch.rs} is hand-written exactly once and never regenerated. interpret(expr, ctx) walks any Expr tree — a direct structural port of Evaluator/Resolver’s node kinds, not a reimplementation of parsing (parsing still only ever happens once, in Ruby, at generator build time). dispatch(...) walks the real DISPATCH_ORDER steps generically, driven by whatever GivenSpec/EnsuresSpec/mutation-closure a generated function hands it — one function for every command shape, the same way CommandInterpreter#call is one method.
This means given/ensures/invariant text genuinely is interpreted, as data, at the compiled binary’s own runtime — the literal thing 0007’s non-goal ruled out. What’s compiled instead, and what actually gives Rust its value here, is narrower and more honest: the type shapes. A Money value is a real struct Money { cents: i64, currency: String }, not a dynamic map; reading args.amount.cents is a real, typed field access the compiler checks, not a runtime lookup. Only the small, closed set of behavior the expression grammar can express (comparisons, arithmetic, membership, sign tests — the same closed grammar spec/operator_conformance_spec.rb already holds Ruby to) is interpreted, by one hand-written, thoroughly-covered function.
command_skip_reason/emit_command are one function each, handling every shape the kernel can run, not one function per shape.then_set ops (including cross-VO-type coercion by matching field names, not a hardcoded case), multi-field closed sets, composite identity, and — later — entities as append targets with auto-minted identity/lifecycle fields. None of these needed a new kind of generated control flow, only new data for the same generic dispatch()/interpret() to walk.syntax.bluebook’s own Keyword/Argument tables), a Rust parser generated from that description — rather than a second, hand-maintained one — became viable and was built, verified byte-exact against Ruby’s own Exporter output for the entire real corpus. This decision’s own central claim (compile type shapes, interpret dispatch behavior through the hand-written kernel) is untouched; only this one consequence about parsing’s permanent location was wrong in the “permanently” part.bin/rust_conformance (0007’s own validation strategy, restated in 0010) — verification so far has been direct cargo run smoke tests against known scenarios plus Ruby-side coverage gates (spec/port_shape_coverage_spec.rb), not automated diffing. Wiring the harness in remains real, valuable, unstarted work.Reference<X> and Hecks::RustProjection’s own design conversations) — rejected because it gives up exactly what makes Rust worth using here: static, compiler-checked field types. The line this decision draws — compile shapes, interpret behavior — is deliberately narrower than “interpret everything” for that reason.interpret() is — but worth naming as the alternative to “compile boolean expressions to Rust source”: the generator could still emit a > b-shaped compiled comparisons instead of Expr data. Not pursued, because it reintroduces exactly the per-shape generation problem this decision exists to avoid, just at the expression level instead of the command level.