Twin-impl spine names the architecture: the same backend spine — auth,
db, http, realtime, actions — ships in two implementations, TypeScript in
fuz_app and Rust in the fuz spine crates (./rust-spine), held observably
equivalent on the wire. Consumers pick one or both. This is a user-facing
capability, not just a development practice: a project can ignore Rust,
ignore TS, or run both for robustness and measurement.
Twin-impl convergence names the discipline: whichever implementation lands the better shape — security, correctness, abstraction design, forensic detail — becomes the canonical reference, and the other ports to converge. Bidirectional: TS decisions flow to Rust, Rust improvements flow back.
fuz_forge is the canonical twin consumer: its TS (Hono) server and Rust
(fuzfd, axum) server are co-maintained at full wire parity.
deno run) as the parity twin for
tests, benches, and local dev. The Rust binary is the production deploy.
Compiling a never-shipped TS server is dead weight.Shared spine concepts — types, fields, error-reason literals, the named steps
of a shared algorithm — carry parallel identifiers across both spines,
modulo each language's case convention (post_commit_effects ↔
PostCommitEffects). A cross-impl name mismatch for the same concept is a
convergence defect, tracked and closed like a bug; when one side renames, the
other follows. Two subtleties:
pending_effects queue and a deferred post_commit_effects queue, the
Rust side that carries only the deferred one must not name it
PendingEffects — same-name-same-concept cuts both ways.Identifier parity is what lets an agent learn a concept once and find it in either spine — snake_case alignment across TS/Rust/SQL is what makes it cheap.
fuz_app) drives both backends with the
same requests and asserts responses byte-for-byte — status, body,
headers. Consumers inherit shared conformance principals (credential
type × context combinations, e.g. daemon-token-with-Origin, invalid-token
variants) so a new auth edge case added upstream tests every consumer.testing_spine_stub is the domain-free third consumer: it exercises
the Rust spine surface without any consumer's business logic, so
spine-level parity is tested independently of zzz/fuz_forge.serde_json::to_value(dto) == json!(…)) stand in as the parity
guard.Where twins silently diverge: paths tested on one backend only — especially auth/error negatives (401 anti-enumeration, malformed input, browser-context guards). Two hand-written stacks agree on the happy path and drift on the edges; port single-backend tests to cross tests. A live behavior difference is either converged or explicitly documented as intentional (e.g. a version value differs while the parity test asserts the shape).
Parity is largely self-policing where the substrate bottoms out in shared
upstream code — fuz_app on TS, the spine crates on Rust. A consumer's
real parity surface is only what it hand-writes twice: RPC handlers, domain
parsing, auth glue, env loading, subprocess use. Keep that surface small and
the twins stay cheap.
Hand-written wire shapes that both the Rust client and Rust server need —
input validators (slug/segment grammars) and typed output DTOs — live in a
dedicated *_wire crate (fuz_forge_wire), single-sourced instead of
implemented per binary. Pure logic, no spine dep. Boundaries:
fuz_http::JsonrpcErrorCode (TS twin: fuz_app's jsonrpc_errors),
not copied into a consumer's wire crate. A consumer references the enum,
never a magic number.skip_serializing_if — a
nullable field emits null like the TS side; #[serde(rename = "ref")] /
"type" for keyword fields; discriminated unions as
#[serde(tag = "kind", rename_all = "snake_case")] enums; DTOs carry the
full field set (never a client's duck-typed subset); field declaration
order matches the wire; booleans are real bool fields.git/read.ts + git/parse.ts, the Rust side splits
the same way — byte-format contracts (%H%x00… format strings, RS/NUL
framing) become diffable module-to-module instead of buried in a
monolith.The same discipline at micro scale — a Rust utility mirroring a TS one keeps
the twin's semantics and (case-adjusted) name: fuz_sys::env::parse_stringbool
↔ z.stringbool(), the DaemonInfo daemon-file schema shared between zzz's
Rust CLI and fuz_app TS, the lru-backed RateLimiter twinning
fuz_app's LruMap. When porting a utility across the language boundary,
find its twin first; diverging semantics under a shared name is the same
defect class as a name mismatch.
Rust ↔ hand-written TS — round-trip + coverage guard, no codegen dependency.
When a Rust crate owns a serde JSON boundary (#[serde(deny_unknown_fields)])
that a hand-written TypeScript layer authors against — e.g. a typed config
builder whose calls serialize to JSON that the Rust engine parses — keep the TS
types hand-written (best ergonomics, no codegen dependency) and guard them
against drift with a round-trip test, not schemars/ts-rs.
Why not codegen: a generated schema/types layer is a second encoding of the boundary that can itself drift from serde's tagging/rename. A round-trip test validates against the real serde parser — the code that runs in production — so it tests reality, not a model. Reserve codegen for when you need field-level coverage enforcement or a published JSON Schema for external consumers.
Two-layer guard (used in zap's TS config library):
import type'd against the TS types and `exportdefault`ing a builder function. One source, gated twice:
- gro typecheck includes it → catches types-too-strict (a valid shape
the TS types wrongly reject).
- A Rust integration test evaluates it and parses the emitted JSON with the
real config type → catches types-too-loose / false-green (a shape TS
accepts that serde rejects).
The import type is erased at runtime, so the evaluator needs no module
resolution — the same file is both typechecked and executed.
ResourceType::ALL const) and assert the fixture exercises every variant:
for v in ALL { assert!(seen.contains(&v), "kitchen-sink missing {v}") }.
This catches a whole type/variant added in Rust but absent from the TS surface
— which the round-trip alone can't see. Pair with a loud floor
(assert!(items.len() >= N)) so a vanished fixture fails instead of silently
passing.Optionally add a thin e2e smoke through the shipped path (built binary → real parse → exit code), skipping cleanly when the runtime (e.g. Deno) or binary is absent — the same skip discipline as DB/Deno-gated tests (./testing-patterns §Environment Flags).
Gotchas: if the evaluator stubs nondeterministic globals (clock/RNG) to throw, the fixture must use pure literals only. Gate the round-trip test on the evaluator runtime being present (skip-with-notice), matching the repo's Deno-gating posture.
fuz_template's ejector ships as symmetric twins — src/lib/molt.ts
(npm run molt) and the molt crate (cargo molt) — at full behavior
parity: same flags, same wizard, same plan, byte-identical output trees.
Unlike the spine there is no reference/production asymmetry: both are
shipping paths, chosen by which toolchain the user has (the TS twin exists
so ejecting never requires installing Rust; the Rust twin dogfoods the
ecosystem's CLI conventions). Its parity mechanics differ instructively
from the wire twins:
cargo test / gro test, both in CI) — an anchored template edit breaks
both checks at the same commit, so cross-twin drift surfaces without a
cross-backend harness. What can be single-sourced is: the output templates
live once in crates/molt/templates/ (compiled into the Rust binary via
include_str!, read at runtime by the TS twin).build_plan/verify/apply/
apply_gate/FEATURES…), with the TS module's sections mirroring the
crate's module seams, so each concept's twin is greppable by name.