feat(leaf): pin the WASI build of the leaf and ship a host shim example - #487
Merged
Merged
Conversation
CI builds and lints `offline-protocol-leaf` for `wasm32-wasip1` in both halves: without default features, where getrandom reads the runtime's `random_get` and `bare-metal-rng` is not used, and with `std` through the new `wasi_host_shim` example, which drives a device from a runtime over its standard streams with the time and the frames supplied by the host. `wasm32-unknown-unknown` is not claimed. There the pinned mls-rs enables getrandom's `js` feature, which getrandom 0.2 selects ahead of `custom`, so a host-registered entropy backend would be a symbol nothing calls and the module would import browser glue. A green build there proves nothing about a non-browser host, and the CI comment, the leaf README, CLAUDE.md and ADR 0021 say so. An example is built with the crate's dev-dependencies, and OpenMLS does not compile for WebAssembly, so the phone-side dev-dependency moves under `cfg(not(target_arch = "wasm32"))` and the interop test that needs it gates itself off the same targets. Nothing changes on a host.
bahdotsh
force-pushed
the
ci/headless-leaf-wasi
branch
from
September 30, 2026 15:56
1f8e9c3 to
0f34585
Compare
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The leaf node now builds for a WebAssembly host, on the WASI target, and CI gates it. A host shim example shows the shape a runtime drives: the time and the frames come in from the host, the frames the device owes go back out.
wasm32-wasip1in theembedded-corejob, in both halves of the crate. Theno_stdhalf builds and lints with nobare-metal-rng: on WASIgetrandom's backend is chosen by the target ahead of any feature and reads the runtime'srandom_get, so the host's entropy is what the MLS library draws and there is no symbol for the host to register. Thestdhalf builds through the new example, so the example cannot rot into a file nobody compiles.examples/wasi_host_shim.rsopens a device in aMemoryStore, mints the key package frame for a peer at a host-suppliednow_unix_secs, hands every JSON frame on standard input toLeafDevice::handleat that time, prints the frames it owes and the events it raised, and at end of input seals one message to the peer. With no Welcome among the input, that seal is refused withNoSessionand the shim prints the refusal: a sealed frame exists only for a session that exists, and the example does not fake a peer to produce one. The header gives the exact build and run lines, a "what this deliberately does not show" list (a pairing completing, durable storage, a clock, authorization), and the entropy warning: on WASI every key is exactly as strong as the runtime'srandom_get.wasm32-unknown-unknownis deliberately not claimed, and the CI comment, the leaf README,CLAUDE.mdand ADR 0021's consequences all say why: on that target the pinned mls-rs enablesgetrandom'sjsfeature, andgetrandom0.2 selectsjsahead ofcustom, so a host that registered its own entropy backend would supply a symbol nothing calls and the module would import browser glue. A green build there proves nothing about a non-browser host. The tree carried no WASM claim before this PR; the one that existed lived in a session note and was wrong for exactly this reason.What the module imports
The release build of the example for
wasm32-wasip1is 1.2 MiB and its import section names one module,wasi_snapshot_preview1, with eight functions:args_get,args_sizes_get,environ_get,environ_sizes_get,fd_read,fd_write,proc_exitandrandom_get. No clock is imported, which is the crate's time obligation made visible: the leaf never reads one. No browser glue is imported. The wasm-bindgen crate is compiled in, because mls-rs depends on it unconditionally on every wasm32 target, and it leaves three unused export symbols (__wbindgen_malloc,__wbindgen_free,__wbindgen_exn_store) in the module; nothing imports through it.The phone side is absent on WebAssembly
Building an example builds the crate's dev-dependencies, and OpenMLS 0.7.4 does not compile for
wasm32-wasip1: its key package lifetime readsfluvio_wasm_timer, which only itsjsfeature supplies. Theoffline-protocol-mlsdev-dependency is now declared undercfg(not(target_arch = "wasm32")), andtests/phone_interop.rs, its only user, gates itself off the same targets. On every host the interop tests run exactly as before.Validation
cargo fmt --all -- --check,cargo clippy --workspace -- -D warnings.--no-default-features --locked --target wasm32-wasip1 -- -D warnings, and the example build for the target.thumbv8m.main-none-eabihf(clippy, withbare-metal-rng) and onthumbv6m-none-eabi(build), which is where arequired-featuresmistake would have failed.cargo build -p offline-protocol-leaf --locked,cargo test -p offline-protocol-leaf(the interop tests and the manifest guard, and the example compiled on the host),cargo clippy -p offline-protocol-leaf --examples -- -D warnings, rustdoc for the leaf under-D warnings.scripts/check-crate-readmes.shandscripts/tests/test-generate-bindings.sh(54 guards), the two scripts that read files this PR touches.Ignored { reason: "frame is addressed to another node" }for the frame, and theNoSessionrefusal. The import section was read from the release.wasmwith a short parser of the binary format; no wasm tooling is installed on this machine.Not in this PR
wasmtimenorwasmeris installed here and nothing was installed for this PR. The module imports onlywasi_snapshot_preview1, and the same source runs natively, but "runs under a runtime" is not claimed.phone_interopandtools/mls-interop, on the host.tools/embedded-footprintis Cortex-M only and cannot measure a.wasm.getrandomfeatures opt-in. Not needed for the claim this PR makes; it would only widen it to the browser target.Notes for reviewers
stdfeature andrequired-features = ["std"], so the--no-default-featuresbuilds skip it instead of failing on it. CI builds it in its own step for that reason.--features bare-metal-rng, and passing it would change nothing:getrandom0.2 tries the target's own backend (wasi) beforejsand beforecustom, and mls-rs already enablescustomon every wasm32 target, so the example links on WASI with the feature on and nothing registered. The feature is left off because it names an obligation the firmware takes on, and on WASI there is none: the entropy is the runtime's. The manifest comment on the feature now says this; it previously claimed the feature would replace an operating system's entropy, which is wrong wherevercustomis the last fallback.