Plan 1 complete. 37 commits. The question this branch existed to answer is answered: yes.
A Rust y-crdt crate compiles to plain wasm32-unknown-unknown with a C ABI and zero imports, runs under wazero inside a cgo-free Go binary with no JavaScript host anywhere, and stays byte-compatible with browser yjs in both directions including formatting. The branch was explicitly allowed to conclude the architecture was wrong; it does not, and a whole-branch review at the highest tier reproduced every claim rather than reading them.
What it contains
Seven tasks: the Rust crate; a Node fixture generator using real yjs 13.6.27; JS→Rust and Rust→JS compatibility with the fixtures as a contract; the C ABI; the Go wazero host; and .amadan/ci.d/10-gate. Plus the eleven spec commits that record what execution discovered.
What execution found that the design did not anticipate
An upstream soundness bug in yrs. src/encoding/read.rs:137 calls std::str::from_utf8_unchecked on wire data with no validation. A mutation fuzz hit it on 0.4–5.1% of single-byte corruptions of our own fixtures — purely random bytes are harmless, which is exactly why a naive garbage-input test passes and proves nothing. Not fixable from our layer; contained by the per-document wasm instance. Still to file upstream with y-crdt.
That makes the wasm sandbox load-bearing for security, not just portability. Inside wasm the fault is a contained trap; a native release build corrupts silently. Nobody made that argument when the design was chosen.
But the containment claim needed correcting, and the branch's own tests said so. Some corrupted updates are silently accepted — rc 0, invalid UTF-8 in document content, straight through Go's string(). §5.2 now says so plainly, and that downstream must not assume document text is valid UTF-8.
A denial-of-service vector invisible to correctness tests. A 36-byte corrupted fixture ran apply for 163 real seconds and returned the correct answer. The host now caps execution with WithCloseOnContextDone and a 1s deadline — the original 5s never fired for the input it was added for, which ran 4.3s.
Falsification-grade, not production-grade
Stated plainly because it would be easy to overclaim. ABI v1 has no state export, so trap recovery is capped in-memory log replay rather than snapshot restore. The 4096-update cap bounds count, not bytes, has no recovery path, and would make a document with more history than the cap unloadable. Blocking before internal/core gets a real caller — nothing imports it today, verified by grep.
The four accumulated deviations cohere
A custom getrandom hook that refuses entropy, a vendored-and-patched fastrand, three banned yrs constructors enforced by clippy, and a GUID/client-id supplied by the host. The final review's verdict: "one design, not a pile of props" — all enforcing one invariant, no identity or entropy originates inside the sandbox; the host owns both.
Gates, each proven to fail before being trusted
Zero host imports; Yjs wire compatibility both directions with fixture-drift detection; a seeded mutation fuzz asserting trap containment; a clippy ban with all three paths individually proven to bite; a reproducible wasm build (identical sha256 across different $HOME and a different repo path). CI green on the real runner.
Every gate here started out checking something adjacent to its purpose and was corrected. The zero-imports test passed by skipping; the trap test passed against a host that never discarded; the containment fuzz checked liveness while discarding the bytes it was meant to compare. That pattern — a check that reports success without checking — is the single most repeated finding of this work.
Reviewed at the highest tier. Verdict: ready to merge.