Skip to content

wasi-leg: WasiLeg + WasiRuntime on wasm32-wasip2 (Section B)#2

Merged
snaart merged 10 commits into
mainfrom
feature/wasi-leg
May 29, 2026
Merged

wasi-leg: WasiLeg + WasiRuntime on wasm32-wasip2 (Section B)#2
snaart merged 10 commits into
mainfrom
feature/wasi-leg

Conversation

@snaart
Copy link
Copy Markdown
Owner

@snaart snaart commented May 24, 2026

Summary

Section B of docs/superpowers/plans/2026-05-24-pre-1.0-deferred-followups-detailed.mdwasm32-wasip2 is a hard CI gate. Adds the wasi-leg Cargo feature, exposing WasiLeg (TCP SessionTransport over wasi:sockets/tcp) + WasiRuntime (single-task executor over wasi:io/poll + wasi:clocks) for embedders running Phantom Core inside a WASI Preview 2 host (Wasmtime, WasmEdge, Spin, wasmCloud, Cloudflare Workers WASI sandbox).

Per-commit walkthrough (B1–B7):

  • B1 build(wasi)wasi = 0.14 optional dep + wasi-leg feature + compile_error guard on wasm32-unknown-unknown.
  • B2 runtime(wasi)WasiRuntime scaffold: Mutex<Vec<TaskSlot>> queue, drive() polls each task once, poll_until_progress(max_wait) blocks on wasi:io/poll with a subscribe_duration watchdog.
  • B3 transport(wasi)WasiLeg::connect(SocketAddr) + length-prefix-framed send_bytes/recv_bytes via blocking_write_and_flush / blocking_read. Client-only; server-side accept deferred per Decision Point 3.
  • B4 cfg(wasi) — re-exports + module gates + rustdoc cross-references.
  • B5 tests(wasi) — host integration test under wasmtime + bindings Cargo feature split (UniFFI scaffolding moved out of std so the WASI guest can drop it; wasm-component-ld cannot ingest UniFFI exports). Also narrowed the browser-WASM-only dep block from cfg(target_arch = "wasm32")cfg(all(target_arch = "wasm32", target_os = "unknown")) so WASI builds skip wasm-bindgen, web-sys, js-sys.
  • B6 ci(wasi)cross.yml flips wasm32-wasi (was allow_failure: true) → hard wasm32-wasip2 matrix entry + new wasi-integration job that installs wasmtime and runs the host driver. The 12-target matrix now has zero allow_failure: true rows.
  • B7 docs(wasi)wasi.md rewritten as a quickstart; CHANGELOG.md adds the Added — wasi-leg Cargo feature section.

Wire-format / API surface

  • New public symbols: phantom_core::transport::legs::wasi::WasiLeg, phantom_core::runtime::wasi_runtime::WasiRuntime. Both gated on cfg(all(feature = "wasi-leg", target_os = "wasi")).
  • New Cargo feature: bindings (default-on). Native FFI consumers (Swift / Kotlin / Python / C) unchanged. WASI guests opt out via --no-default-features --features std,wasi-leg.

Test Plan

  • cargo test --lib — 233 tests pass (host default-features build, no regressions).
  • cargo test --lib --no-default-features --features std — 231 tests pass (the 2 missing are uniffi-gated tests).
  • cargo clippy --lib -- -D warnings — clean.
  • cargo check --target wasm32-wasip2 --no-default-features --features std,wasi-leg --lib — clean.
  • cargo build --manifest-path core/tests/fixtures/wasi-guest/Cargo.toml --target wasm32-wasip2 — clean.
  • cargo test --test wasi_integration -- --ignored — passes locally on wasmtime 45.0.0.
  • CI (cross.yml wasm32-wasip2 row + wasi-integration job) — to verify on PR.
  • CI (cross.yml wasm32-unknown-unknown) — confirm browser surface unchanged.

Out of scope (follow-up)

  • Server-side WasiLeg::accept / WasiListener (running phantom-server as a WASI guest — Decision Point 3 defers).
  • Full PhantomSession handshake over WasiLeg. The B5 host test exercises the byte pipe; the handshake needs connect_with_transport_with_runtime wiring against WasiRuntime::drive + poll_until_progress, plus the host echo server has to become a real PhantomListener.

Quickstart for embedders is in docs/operations/wasi.md.

snaart added 8 commits May 24, 2026 12:00
B1 — opens the rollout of the WasiLeg + WasiRuntime surface
(Section B of the pre-1.0 deferred-followups plan). Pulls in
`wasi = 0.14` (the WIT-bindgen-based Preview 2 binding, the
hard prerequisite for the rest of Section B) under a fresh
`wasi-leg` Cargo feature.

`core/Cargo.toml`:
- New `wasi-leg = ["std", "dep:wasi"]` feature in `[features]`.
- New `[target.'cfg(target_os = "wasi")'.dependencies]` block
  with `wasi = { version = "0.14", optional = true }`. The
  target gate scopes the dep to wasm32-wasi* triples
  (wasm32-wasi, wasm32-wasip1, wasm32-wasip2); host builds and
  wasm32-unknown-unknown never see the crate.

`core/src/lib.rs`:
- New `compile_error!` for the `wasi-leg + wasm32-unknown-unknown`
  combination — that target doesn't support the wasi crate's WIT
  bindings, and the project's wasm32-unknown-unknown story is
  WebSocketLeg + WasmRuntime, not WasiLeg.

Verification (local toolchain stable 1.93, wasi 0.14.7+wasi-0.2.4
on crates.io):
- `cargo check --manifest-path core/Cargo.toml` — clean (no
  regression on host).
- `cargo check --target wasm32-wasip2 --features wasi-leg` —
  clean; pulls in `wasi 0.14.7+wasi-0.2.4`.
- `cargo check --target wasm32-unknown-unknown --features wasi-leg`
  — fails with the mutual-exclusion `compile_error` pointing at
  the right alternative.

The wasi-leg modules themselves (WasiRuntime, WasiLeg) land in
B2 / B3; this commit is the dep + feature scaffold only. CI
flips wasm32-wasi(p2) from `allow_failure` to a hard gate in B6.
B2 — adds the WASI Preview 2 `Runtime` impl that pairs with the
`WasiLeg` transport landing in B3.

`core/src/runtime/wasi_runtime.rs` (new module):
- `WasiRuntime` holds a private `Arc<Mutex<Vec<TaskSlot>>>` task
  queue. `spawn` pushes onto the queue and returns a `SpawnHandle`
  whose abort flips an `AtomicBool` that the task slot's poll loop
  checks (mirrors `EmbeddedRuntime`).
- `drive()` polls every queued task exactly once with a no-op
  waker, retains the ones that returned `Pending`, drops the
  completed-or-aborted ones, and returns the remaining count.
- `poll_until_progress(max_wait)` blocks the host on
  `wasi::io::poll::poll` for at most `max_wait`, using
  `wasi::clocks::monotonic_clock::subscribe_duration` as the
  bounded watchdog so the drive loop always makes eventual
  progress even if a future never registers a Pollable
  (defense against the "starved task" failure mode flagged in
  the plan).
- `sleep` is deadline-checking via `std::time::Instant::now()`
  (libstd's WASI port routes through `wasi::clocks::monotonic_clock`).
- `now_monotonic` / `now_wall_clock` delegate to
  `std::time::{Instant, SystemTime}::now()` — same wrappers as
  the other `Runtime` impls so the trait surface stays uniform.

`core/src/runtime/mod.rs` — `pub mod wasi_runtime` + re-export
guarded on `cfg(all(feature = "wasi-leg", target_os = "wasi"))`.

Tests (compile + run only on `wasm32-wasi*`):
- `wasi_runtime_is_object_safe` — pins the `dyn Runtime` trait
  object compatibility.
- `monotonic_clock_does_not_go_backwards` — guards against the
  WASI clock wrapper inverting deltas.
- `wall_clock_is_after_unix_epoch` — host-clock sanity.
- `spawn_noop_completes_on_first_drive` — task lifecycle smoke.
- `abort_short_circuits_pending_future` — confirms the abort
  flag is observed across a `drive()` round.

The host integration test that actually exercises a WASI guest
under `wasmtime` lands in B5; this commit ships the runtime
surface only.

Verification:
- `cargo check --manifest-path core/Cargo.toml` — clean (no
  module visible on host).
- `cargo check --target wasm32-wasip2 --features wasi-leg` —
  clean; module compiles against `wasi 0.14.7+wasi-0.2.4`.
- `cargo test --lib` — 233 tests pass; no host-side regressions.
B3 — adds the WASI Preview 2 TCP transport leg that pairs with
`WasiRuntime` (B2). Provides the same length-prefix-framed
`SessionTransport` shape as `TcpSessionTransport` so a WASI guest
running inside `wasmtime` / `wasmer` / `jco` can drive the
unchanged Phantom session machinery.

`core/src/transport/legs/wasi.rs` (new module):
- `WasiLeg::connect(SocketAddr)` — converts `std::net::SocketAddr`
  into the WIT `IpSocketAddress` enum (Ipv4SocketAddress or
  Ipv6SocketAddress), creates a TCP socket on the default network
  via `wasi::sockets::tcp_create_socket::create_tcp_socket`, kicks
  off `start_connect`, blocks on the resulting `subscribe()`
  Pollable via `wasi::io::poll::poll`, then `finish_connect`s into
  the `(InputStream, OutputStream)` pair the leg holds.
- `send_bytes` writes the 4-byte big-endian length prefix + payload
  via `OutputStream::blocking_write_and_flush`. Rejects oversized
  frames (> 16 MiB) up front — same cap as `TcpSessionTransport`.
- `recv_bytes` reads the length prefix, validates against the cap,
  then reads `len` bytes into a persistent `BytesMut` accumulator
  (one allocation amortized across the steady state), and hands
  the caller an O(1) `split_to(len).freeze()` slice. `blocking_read`
  may return fewer bytes than requested; a `read_exact` helper
  loops until satisfied or EOF.
- `ip_socket_address_from_std` is split out + unit-tested for the
  Ipv4/Ipv6 octet conversions.

`unsafe` opt-in (`#![allow(unsafe_code)]`):
- The WIT-bindgen-generated `TcpSocket` / `InputStream` /
  `OutputStream` resources are not auto-`Send`/`Sync` (they wrap an
  opaque numeric handle). WASI Preview 2 is single-task per
  instance, so the manual `unsafe impl Send / Sync for WasiLeg`
  blocks are sound — the `Mutex` wrappers inside `WasiLeg`
  provide the only real synchronisation. Justified inline at the
  module-level attribute.

`core/src/transport/legs/mod.rs` — `pub mod wasi` + `pub use
wasi::WasiLeg` guarded on `cfg(all(feature = "wasi-leg", target_os
= "wasi"))`.

Client-only for this commit. Server-side accept (`start_listen` /
`finish_listen` / `accept`) is explicitly out of scope per the
plan's Decision Point 3 — phantom-server-on-WASI is deferred.

Tests:
- `ipv4_addr_conversion_round_trips` (host-runnable) — pins the
  IpAddressFamily / Ipv4SocketAddress conversion.
- `ipv6_addr_conversion_round_trips` (host-runnable) — same for
  IPv6 segments + flowinfo + scope_id.

End-to-end round-trip through a real `wasmtime` host lands in B5.

Verification:
- `cargo check --manifest-path core/Cargo.toml` — clean (no module
  visible on host beyond the conversion helper).
- `cargo check --target wasm32-wasip2 --features wasi-leg` —
  clean.
- `cargo test --lib` — 233 tests pass; no host-side regressions.
B4 — pins the WASI-leg module gates and surfaces them in the
public-API rustdoc, completing the visibility plumbing for the
`wasi-leg` feature.

`core/src/api/mod.rs` — top-of-module doc comment now points at
`crate::transport::legs::wasi::WasiLeg` (paired with
`crate::runtime::wasi_runtime::WasiRuntime`) as the
`wasm32-wasi*` `SessionTransport`, alongside the existing
`WebSocketLeg` callout for `wasm32-unknown-unknown`.

The `pub mod wasi;` / `pub use wasi::WasiLeg;` re-exports under
`core/src/transport/legs/mod.rs` and
`core/src/runtime/mod.rs` were added in B3 / B2 respectively;
both are guarded on `cfg(all(feature = "wasi-leg", target_os =
"wasi"))`. The `core/src/lib.rs` `compile_error!` from B1 still
rejects `--features wasi-leg` on `wasm32-unknown-unknown`.

Verification:
- `cargo check --manifest-path core/Cargo.toml` — clean (no
  module visible on host).
- `cargo check --target wasm32-wasip2 --features wasi-leg` —
  clean (modules visible, symbols defined).
- `cargo doc --target wasm32-wasip2 --features wasi-leg --no-deps`
  — generates `target/wasm32-wasip2/doc/phantom_core/runtime/
  wasi_runtime/` and `target/wasm32-wasip2/doc/phantom_core/
  transport/legs/wasi/` directories, confirming both symbols
  render in the public surface.
B5 — the WASI-leg surface is now exercised end-to-end. The host
test compiles `core/tests/fixtures/wasi-guest/` for
`wasm32-wasip2`, stands up a native length-prefix-aware TCP echo
server on a loopback OS-chosen port, spawns the guest under
`wasmtime` (granted the `inherit-network` socket capability with
`PHANTOM_PORT` plumbed via env), and asserts byte-equality between
the sent and echoed payload.

To make the build path work, B5 also lands the supporting Cargo
plumbing:

- New **`bindings`** Cargo feature (default-on) — pulls `dep:uniffi`
  on its own rather than via the kitchen-sink `std` feature. The
  WASI guest sets `--features wasi-leg` without `bindings` so
  UniFFI's exported-symbol metadata (incompatible with
  `wasm-component-ld`, the wasm32-wasip2 linker) stays out of the
  guest's dependency graph. Every `#[uniffi::*]` attribute /
  derive in `core/src/{api/*,errors,config,lib,bin/uniffi-bindgen}`
  is gated via `#[cfg_attr(feature = "bindings", ...)]`; the bin
  also gains a `cfg(not(feature = "bindings"))` fallback that
  exits with a clear "rebuild with --features bindings" message.

- The browser-only `[target.cfg(target_arch = "wasm32")]`
  dependency block in `core/Cargo.toml` narrows to
  `cfg(all(target_arch = "wasm32", target_os = "unknown"))`. The
  `WebSocketLeg` module + `WasmRuntime` module cfg gates also
  narrow to `target_os = "unknown"`. Net effect: `wasm-bindgen`,
  `web-sys`, `js-sys`, the `getrandom = { features = ["js"] }`
  shim, etc. are no longer pulled into the wasm32-wasi* tree —
  the WASI guest gets a clean dependency graph.

`core/tests/fixtures/wasi-guest/` (new):
- Standalone Cargo project (own `[workspace]`) depending on
  `phantom_core` via path, `default-features = false`,
  `features = ["std", "wasi-leg"]`.
- `src/main.rs` reads `PHANTOM_PORT` from env, opens a `WasiLeg`
  TCP connection to `127.0.0.1:PHANTOM_PORT`, calls
  `SessionTransport::send_bytes` then `recv_bytes` with a fixed
  payload, exits 0 on byte-equal echo, 2 on mismatch.

`core/tests/wasi_integration.rs` (new):
- `wasi_guest_round_trips_payload_through_wasmtime` (`#[ignore]`).
- Gracefully skips if `wasm32-wasip2` is not installed or
  `wasmtime` is not on PATH.
- Builds the guest via `cargo build --target wasm32-wasip2`,
  starts the echo server on an OS-chosen loopback port, spawns
  `wasmtime run -S inherit-network --env PHANTOM_PORT=…` against
  the guest binary, and asserts exit code 0 + the guest's "OK:
  round-tripped …" stderr marker.

Verification:
- `cargo test --lib` — 233 tests pass (no host-side regressions
  from the bindings feature split).
- `cargo build --manifest-path core/tests/fixtures/wasi-guest/Cargo.toml
  --target wasm32-wasip2` — clean.
- `cargo test --test wasi_integration -- --ignored` — passes
  locally on `wasmtime 45.0.0` against a 31-byte payload.

Scope: this is a TCP-byte-pipe round-trip through `WasiLeg`, NOT
a full `PhantomSession` handshake. The latter needs
`connect_with_transport_with_runtime` wiring against
`WasiRuntime`'s `drive`/`poll_until_progress` loop plus
fips-disabled crypto, which is a separate follow-up. The B-section
deliverable (`wasm32-wasi` becomes a hard CI gate) is unchanged
either way.
B6 — replaces the `wasm32-wasi` `allow_failure: true` row in
`.github/workflows/cross.yml` with a hard-gated `wasm32-wasip2`
entry, completing the deprecation of the only `allow_failure`
matrix slot in the 12-target cross-compile workflow.

`wasm32-wasi` was the legacy alias for what rustup now calls
`wasm32-wasip1` (Preview 1). The `wasi-leg` rollout targets
Preview 2 (`wasm32-wasip2`) since `wasi:sockets/tcp` lives only
there — `wasi 0.14` (the WIT-bindgen Preview 2 crate) cannot
target `wasm32-wasip1` at runtime even though it links.

Matrix entry:
- `target: wasm32-wasip2`
- `cargo_args: "--no-default-features --features std,wasi-leg"`
  drops the `bindings` feature so UniFFI's exported-symbol
  metadata stays out of the dep graph (incompatible with
  `wasm-component-ld`, the wasm32-wasip2 linker).

New `wasi-integration` job (single, not part of the compile-check
matrix):
- Installs `wasmtime` via the pinned official installer script.
- Runs `cargo test --test wasi_integration -- --ignored`, which
  builds the `phantom-wasi-guest` fixture under wasm32-wasip2,
  spawns it under wasmtime against a native length-prefix echo
  server, and asserts byte-equal round-trip.

The 12-target matrix has zero `allow_failure: true` rows after
this commit; every cross-compile target is a hard gate.

Verification:
- `cargo check --manifest-path core/Cargo.toml --lib --target
  wasm32-wasip2 --no-default-features --features std,wasi-leg` —
  clean locally (the exact command CI runs).
- `cargo test --test wasi_integration -- --ignored` — passes
  locally on wasmtime 45.0.0.
B7 — closes Section B of the pre-1.0 deferred-followups plan.

`docs/operations/wasi.md` — rewritten from the "deferred" gap
analysis into a quickstart for the now-shipped surface:
- TL;DR with a minimal WASI Preview 2 guest using `WasiLeg`.
- Public-surface table (`WasiLeg`, `WasiRuntime`) with module
  paths and what each does.
- "Why `--no-default-features --features std,wasi-leg`?" section
  explaining the `bindings` feature split (UniFFI + wasm-component-
  ld incompatibility).
- Browser vs WASI vs native dep-graph split table.
- Reproducing the host integration test locally
  (`rustup target add wasm32-wasip2`, `brew install wasmtime`).
- Explicit "Out of scope" list (no server-side accept, no full
  `PhantomSession` over `WasiLeg` yet).

`CHANGELOG.md` — new "Added — `wasi-leg` Cargo feature" section
under `[Unreleased]` summarising the surface (`WasiLeg`,
`WasiRuntime`), the `bindings` feature split, the browser-WASM-
only dep block narrowing to `target_os = "unknown"`, and the CI
flip (wasm32-wasi → wasm32-wasip2 hard gate + new
`wasi-integration` job). The 12-target cross-compile matrix now
has zero `allow_failure: true` rows.

Verification:
- Doc-only commit. CHANGELOG diff readable.
- Cross-reviewed each surface claim against the actual code
  shipped in B1–B6.
Addresses the PR #2 review comments. Three blockers + five notable
items + the documentation drift sweep.

`docs/security/panic-sites.md` — five new rows (sites 9-13) for the
private `std::sync::Mutex` `expect()` sites added by `WasiRuntime`
(drive / spawn / tasks_pending) and `WasiLeg` (send_bytes /
recv_bytes). Each row names the mutex, its scope, and the invariant
that makes the poison case unrecoverable (mirrors the existing
`EmbeddedRuntime` rows). `wasi.rs` added to the Unsafe Blocks table
as the third opt-in alongside `udp_transport.rs` and `websocket.rs`.

`core/src/transport/legs/wasi.rs` — rewrote the `unsafe impl Send /
Sync for WasiLeg` SAFETY comment. The original argument leaned on
"WASI is single-threaded today"; the new argument is that the
internal `Mutex` wrappers are the only access path for the WIT
resource handles after construction, so the single-accessor
discipline holds even if a future `wasi-threads` proposal
stabilizes. Also switched the internal import to the canonical
`crate::transport::session_transport::SessionTransport` (was
re-export via `crate::api::session`).

`core/src/runtime/wasi_runtime.rs` — added a `WasiSleep` docstring
that calls out its coupling to `WasiRuntime::drive`: the future
ignores its `Context` and only progresses on the next `drive()`
call, so the `poll_until_progress` watchdog timeout becomes the
effective sleep granularity. Using `WasiSleep` under any other
executor would deadlock.

`core/tests/fixtures/wasi-guest/src/main.rs` — added a
`PHANTOM_MODE=runtime` branch that drives `WasiLeg` via
`WasiRuntime::spawn` + `drive` + `poll_until_progress`. Closes the
review gap that the two new wasi primitives shipped without any
joint integration coverage. Outcome bookkeeping via `Arc<AtomicU8>`
with explicit exit codes (2 mismatch, 3 I/O error, 4 executor bug).

`core/tests/wasi_integration.rs` — factored `run_guest_round_trip`
helper that takes a `(mode, expected_marker)` pair; the existing
B5 test calls it with `("", "OK: round-tripped")`, the new B7
`wasi_guest_round_trips_payload_via_runtime_through_wasmtime` test
calls it with `("runtime", "OK: runtime-driven round-trip")`. Both
now use `env!("CARGO")` so the spawned `cargo build` uses the same
toolchain that compiled the test binary.

`.github/workflows/cross.yml` — clarified the `wasi-integration`
job header to call out that both integration tests run there.
Also documented the dev-dep blocker that prevents the in-tree
`#[cfg(test)]` unit tests in `wasi_runtime.rs` / `wasi.rs` from
running under `cargo test --target wasm32-wasip2 --lib`
(`proptest`'s `fork` feature pulls `rusty-fork` → `wait-timeout`
which has no wasi `imp` module; `criterion` / `uniffi` test
helpers have similar issues). Closing that gap is a separate dev-
dep target-gating PR; the integration tests cover the wasi-
specific behaviour today.

`CHANGELOG.md` — fixed the placeholder commit-hash range
(`9b31266..e41b583` → `f4828c2..307b43e`), and added an explicit
"Breaking for `--no-default-features --features std` consumers"
paragraph documenting the UniFFI feature split.

`docs/operations/wasi.md` — same commit-hash fix as CHANGELOG.
Replaced the broken `[lib] crate-type = ["cdylib"]` quickstart
example (incompatible with the `fn main()` body shown below it)
with a `[[bin]]` form that matches the wasi-guest fixture and
actually works with `wasmtime run …wasm`.

`docs/PROGRESS.md` — flipped the four "wasm32-wasi remains
`allow_failure: true`" mentions to reflect the shipped state
(Phase 3.5, 3.10, 3.11, Phase 3 verdict). Updated the dashboard
counts at the top: unsafe opt-ins 1 → 3 (now correctly listing
`udp_transport`, `websocket`, `wasi`), panic sites 6 → 13.

Verification: `cargo check --lib` clean, `cargo clippy --lib`
clean, `cargo test --lib` 233/233 passing, `cargo check --target
wasm32-wasip2 --no-default-features --features std,wasi-leg --lib`
clean, `cargo test --test wasi_integration -- --ignored` 2/2
passing (both B5 byte-pipe and the new B7 runtime+leg composition
round-trips succeed under wasmtime 45.0.0).
snaart added a commit that referenced this pull request May 25, 2026
Addresses the PR #2 review comments. Three blockers + five notable
items + the documentation drift sweep.

`docs/security/panic-sites.md` — five new rows (sites 9-13) for the
private `std::sync::Mutex` `expect()` sites added by `WasiRuntime`
(drive / spawn / tasks_pending) and `WasiLeg` (send_bytes /
recv_bytes). Each row names the mutex, its scope, and the invariant
that makes the poison case unrecoverable (mirrors the existing
`EmbeddedRuntime` rows). `wasi.rs` added to the Unsafe Blocks table
as the third opt-in alongside `udp_transport.rs` and `websocket.rs`.

`core/src/transport/legs/wasi.rs` — rewrote the `unsafe impl Send /
Sync for WasiLeg` SAFETY comment. The original argument leaned on
"WASI is single-threaded today"; the new argument is that the
internal `Mutex` wrappers are the only access path for the WIT
resource handles after construction, so the single-accessor
discipline holds even if a future `wasi-threads` proposal
stabilizes. Also switched the internal import to the canonical
`crate::transport::session_transport::SessionTransport` (was
re-export via `crate::api::session`).

`core/src/runtime/wasi_runtime.rs` — added a `WasiSleep` docstring
that calls out its coupling to `WasiRuntime::drive`: the future
ignores its `Context` and only progresses on the next `drive()`
call, so the `poll_until_progress` watchdog timeout becomes the
effective sleep granularity. Using `WasiSleep` under any other
executor would deadlock.

`core/tests/fixtures/wasi-guest/src/main.rs` — added a
`PHANTOM_MODE=runtime` branch that drives `WasiLeg` via
`WasiRuntime::spawn` + `drive` + `poll_until_progress`. Closes the
review gap that the two new wasi primitives shipped without any
joint integration coverage. Outcome bookkeeping via `Arc<AtomicU8>`
with explicit exit codes (2 mismatch, 3 I/O error, 4 executor bug).

`core/tests/wasi_integration.rs` — factored `run_guest_round_trip`
helper that takes a `(mode, expected_marker)` pair; the existing
B5 test calls it with `("", "OK: round-tripped")`, the new B7
`wasi_guest_round_trips_payload_via_runtime_through_wasmtime` test
calls it with `("runtime", "OK: runtime-driven round-trip")`. Both
now use `env!("CARGO")` so the spawned `cargo build` uses the same
toolchain that compiled the test binary.

`.github/workflows/cross.yml` — clarified the `wasi-integration`
job header to call out that both integration tests run there.
Also documented the dev-dep blocker that prevents the in-tree
`#[cfg(test)]` unit tests in `wasi_runtime.rs` / `wasi.rs` from
running under `cargo test --target wasm32-wasip2 --lib`
(`proptest`'s `fork` feature pulls `rusty-fork` → `wait-timeout`
which has no wasi `imp` module; `criterion` / `uniffi` test
helpers have similar issues). Closing that gap is a separate dev-
dep target-gating PR; the integration tests cover the wasi-
specific behaviour today.

`CHANGELOG.md` — fixed the placeholder commit-hash range
(`9b31266..e41b583` → `f4828c2..307b43e`), and added an explicit
"Breaking for `--no-default-features --features std` consumers"
paragraph documenting the UniFFI feature split.

`docs/operations/wasi.md` — same commit-hash fix as CHANGELOG.
Replaced the broken `[lib] crate-type = ["cdylib"]` quickstart
example (incompatible with the `fn main()` body shown below it)
with a `[[bin]]` form that matches the wasi-guest fixture and
actually works with `wasmtime run …wasm`.

`docs/PROGRESS.md` — flipped the four "wasm32-wasi remains
`allow_failure: true`" mentions to reflect the shipped state
(Phase 3.5, 3.10, 3.11, Phase 3 verdict). Updated the dashboard
counts at the top: unsafe opt-ins 1 → 3 (now correctly listing
`udp_transport`, `websocket`, `wasi`), panic sites 6 → 13.

Verification: `cargo check --lib` clean, `cargo clippy --lib`
clean, `cargo test --lib` 233/233 passing, `cargo check --target
wasm32-wasip2 --no-default-features --features std,wasi-leg --lib`
clean, `cargo test --test wasi_integration -- --ignored` 2/2
passing (both B5 byte-pipe and the new B7 runtime+leg composition
round-trips succeed under wasmtime 45.0.0).
Resolve conflicts in CHANGELOG.md and docs/security/panic-sites.md by
keeping both sides:

- CHANGELOG [Unreleased]: retain both the wasi-leg "Added" block and the
  FIPS primitive-swap "Security" block, ahead of the Phase 8 section.
- panic-sites.md: merge both new panic-site sets — FIPS sites stay 9-11,
  WASI runtime/leg sites renumbered 9-13 -> 12-16, with internal
  "Site N" cross-references fixed (9 -> 12, 12 -> 15).

All non-doc files auto-merged cleanly; only the two doc files conflicted.
@snaart snaart force-pushed the feature/wasi-leg branch from 38c382c to 13f95d6 Compare May 29, 2026 13:05
The wasi-leg (Section B) commit range was rewritten, so the old short
SHAs no longer resolve. Point the references in CHANGELOG.md,
docs/PROGRESS.md and docs/operations/wasi.md at the current SHAs
(f6c0c0a..255be95, B6 -> e42ca56).
@snaart snaart merged commit 2fe42ed into main May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant