refactor(core,agent)!: make the WASM support matrix explicit and true - #2213
Merged
Conversation
…ve-only `rig-core` already distinguishes browser wasm precisely — `target_family = "wasm"` for "any wasm", `all(target_arch = "wasm32", target_os = "unknown")` for things that need a JS host — and documents why. `rig-agent` discarded that precision: a bare `cfg(target_arch = "wasm32")` pulled `wasm-bindgen-futures` and forced `rig-core/wasm` on WASI too, where a browser shim is wrong-by-construction. The `Send`-relaxed stream aliases had the same bare predicate, so they disagreed with `rig-core`'s `WasmCompat*` markers on any non-browser wasm target. MCP is native-only, and cannot be otherwise from this side. rmcp's `ClientHandler` is declared `Sized + Send + Sync + 'static` unconditionally; its `local` feature relaxes the future bounds (`MaybeSendFuture`) but not the handler. This crate's handler owns the tool registry, whose `Arc<dyn ErasedTool>` is deliberately neither `Send` nor `Sync` on wasm. Two maybe-`Send` abstractions that do not compose. Requesting `rmcp` on wasm previously produced a wall of `dyn ErasedTool` trait errors; it now produces one sentence naming the cause and the fix. Every `rmcp` cfg site carries the native predicate, including the two complementary `not(feature = "rmcp")` arms in `tool/server.rs` — with only the positive arms narrowed, wasm builds bound neither arm. That is also why the follow-up CI check asserts the error *count*, not just the message. `wasm-bindgen-futures` leaves the dependency graph entirely: its only user was the MCP cancellation dispatch, which is now unconditionally `tokio::spawn`. Not a breaking change: `rmcp` never compiled for any wasm target and WASI never built at all, so no working configuration changes behaviour.
The `check-wasm-runtimes` comment claimed the lane guarded "the target-gated MCP cancellation dispatch", which a bare default-feature check never exercised — `rmcp` is not a default feature. Drop the false claim and add a job that actually pins the contract. The assertion checks three things, because each has its own failure mode: the build must fail (a success means the gate is gone), the failure must be our `compile_error!` (not some unrelated breakage), and it must be the *only* error. The count matters — a newly added `#[cfg(feature = "rmcp")]` without the target predicate compiles the rmcp surface back into the wasm build and leaks the trait-error wall behind our message. Verified: with one such gate reverted the count goes from 1 to 14.
The root README claimed "Full WASM compatibility for the portable core and classic runtime". Browser wasm is supported; WASI is not, and `rmcp` is native-only on every target. State the matrix once, with the reasons, so the next person does not re-derive it from compiler output. WASI is unsupported because the dependency graph does not build for it, not because it is untested: `rig-core` depends unconditionally on `reqwest`, which pulls `hyper`/`socket2` and a tokio feature set WASI rejects. Supporting it means making `reqwest` optional and supplying a `wasi:http` client behind `rig_core::http_client` — a deliberate project, not a `cfg` fix. Recording that distinction is the point of the section.
`rig-agent`'s `wasm` feature did nothing in every configuration that builds: - browser wasm already enables `rig-core/wasm` through this crate's target table, so passing the feature was redundant; - `rig-core`'s gate is `all(feature = "wasm", target_arch = "wasm32")`, so on native the feature was inert; - WASI is the one target where it would have had an effect, and WASI does not build. Worse than useless, in fact: on WASI it would relax `rig-core`'s `WasmCompat*` markers while this crate's stream aliases still require `Send`, so the two halves would disagree. Narrowing the target table to `target_os = "unknown"` removed the feature's last theoretical purpose, so retire it. Building for `wasm32-unknown-unknown` is the entire opt-in. The root `rig` facade keeps its `wasm` feature and now forwards only `rig-core/wasm`: an agent-less facade build has no target table of its own and still needs a way to ask for the relaxed bounds. That path is exercised by `examples/candle_wasm_chat`, which depends on the facade with `default-features = false, features = ["candle", "wasm"]`. Breaking only for direct `rig-agent` dependents that list `features = ["wasm"]`; they should drop it. No behaviour changes anywhere.
…it in CI
`candle_wasm_chat` imports `rig::agent::{Agent, AgentBuilder}` and
`rig::completion::Chat` while declaring `default-features = false` without
`agent`, so it never built standalone:
cargo check -p candle_wasm_chat --target wasm32-unknown-unknown
error[E0432]: unresolved imports `rig::agent`, `rig::completion::Chat`
CI missed it because cargo unifies features across packages built in one
invocation. The only job compiling workspace members is the doctest job's
`cargo test --doc --workspace --all-features`, which enables `agent` on the
shared `rig` build and supplies what this manifest forgot. Standalone: 4 errors.
Alongside `-p rig --all-features`: zero. No job ever built the example with only
its own features.
So fix both halves. The manifest declares `agent`, and the wasm matrix gains
`candle_wasm_chat` so each package is checked on its own and an under-declared
manifest fails instead of being masked. The matrix entries are separate parallel
jobs, and this one is the cheapest of the three — `default-features = false`
trims its tree to 544 crates against `rig`'s 3089.
Pre-existing on main; fixed here because this is the only consumer exercising
the browser-wasm facade path the rest of this PR documents.
…asm gates Two loose ends from review. `candle_wasm_chat` listed `wasm` alongside `agent`, but with `agent` enabled `rig-agent`'s browser target table already supplies `rig-core/wasm`, so the feature did nothing — the same no-op this release removed from `rig-agent` itself. Drop it and record why, so it does not look like an oversight. The rest of this PR normalizes every rig-agent wasm gate onto `all(target_arch = "wasm32", target_os = "unknown")`, and nothing enforced that. Add a grep guard beside the existing CWD-relative-fixture check: in rig-agent, any `target_arch = "wasm32"` must name a `target_os`. rig-core is out of scope — it uses bare `not(target_arch = "wasm32")` for native-only exclusions and `all(feature = "wasm", target_arch = "wasm32")` for its `WasmCompat` markers, both correct there. One trap worth the comment it now carries: the exemption for prose cannot simply skip lines beginning with `#`. That is a TOML comment *and* the first character of a Rust `#[cfg(...)]` attribute, so the obvious pattern silently ignores every gate the guard exists to catch. Caught it by testing the guard against a reintroduced bare gate in both a `.rs` and a `.toml` file; the first version passed the `.rs` case while seeing nothing.
Browser wasm now needs no feature flags at all. `cargo build --target
wasm32-unknown-unknown` is the entire opt-in.
`rig-core`'s `wasm` feature turned out to carry no information the target did
not already have. Its only dependency payload, `wasm-bindgen-futures`, was
never referenced anywhere in `rig-core` — it was a pure `cfg` switch. And every
consumer already flipped that switch purely from the target: `rig-agent` and
`rig-candle` each enabled it from a `[target.'cfg(...)']` table, leaving the
facade's `wasm` as a manual escape hatch that, after the previous commit, had no
consumers at all.
So key the `WasmCompat*` markers on `all(target_arch = "wasm32", target_os =
"unknown")` directly and delete the feature from all three crates, along with
the two target tables that existed only to set it and the unused optional
dependency. Relaxing bounds cannot break user code: the relaxed markers are
blanket-implemented (`impl<T> WasmCompatSend for T {}`), so anything satisfying
the strict form satisfies the relaxed one.
Doing this surfaced a latent bug in `rig-core`'s SSE aliases, where the arms did
not partition — `not(target_arch = "wasm32")` opposite a browser-scoped arm left
WASI matching neither, so `ResponseFuture` and `EventStream` were undefined
there. Pre-existing (the old feature-based arm had the same hole) and invisible
because WASI never built, but it is exactly the failure mode the bare-predicate
guard exists to prevent, so both arms now share one predicate. With rig-core
uniformly browser-scoped, that guard extends to cover it.
WASI was mentioned 17 times across the README, CHANGELOG, and CI comments — far past what the reader needs. It earns exactly two mentions: one row in the support matrix saying it is unsupported, and one sentence explaining why wasm gates name a `target_os` at all (a bare `target_arch = "wasm32"` matches WASI, which has no JS host). Everything else was repetition. Also folds three overlapping CHANGELOG entries for this change into four distinct ones, so a reader gets the outcome — no wasm features, rmcp is native-only, the matrix is documented, one `cfg` fix — instead of the same rationale three times. Down to 8 mentions, most of them the unavoidable ones in the matrix table.
`wasm-bindgen-futures` appears nowhere in this crate's source, so it reads as an unused dependency — I removed it on exactly that reasoning and the build broke. `#[wasm_bindgen]` on an `async fn` expands to code that references `wasm_bindgen_futures`, so the reference is macro-generated and invisible to a grep for the crate name. Record that where the next person will look, since the surrounding commits removed this same crate from `rig-core` (genuinely unused) and `rig-agent` (its only user became native-only), which makes the remaining copy look like an oversight.
This PR deletes the facade's `wasm` feature, but the fixture's `all_root`
list still named it, so `tests/tool_facade_features.rs` failed before
compiling anything:
package `rig-tool-facade-fixture` depends on `rig` with feature `wasm`
but `rig` does not have that feature.
That test's `--all-features` leg runs in CI — the root package has no
`default-members` exclusion, and the workspace `cargo nextest run
--all-features` lane turns on `facade-build-tests`. So the lane this PR
adds to catch under-declared manifests was itself broken by an
over-declared one.
Nothing to note in the CHANGELOG: the feature this removes was only ever
referenced by a feature introduced and deleted within this same PR.
Two holes in the guard added earlier in this PR. It matched only the space-normalized `target_arch = "wasm32"`, justified by the `cargo fmt --check` step above it. That reasoning holds for `.rs` files and not for manifests: `cargo fmt` does not format `Cargo.toml` and there is no TOML formatter in CI, so `cfg(target_arch="wasm32")` in a `[target.'cfg(...)']` table sailed straight through the guard whose whole purpose is catching that table. Match tolerates whitespace now. Its path list was also just rig-core and rig-agent, leaving rig-candle, the other member crates, the facade's own `src`/`Cargo.toml`, and every `tests/` directory unguarded. None of them names `target_arch` today — they all use `target_family = "wasm"` — which is exactly when widening the scope is free. Tested in four directions: silent on the clean tree, fires on a spaced bare gate in a `.rs` file, fires on an unspaced bare gate in a `Cargo.toml` (the case that previously passed), and still exempts prose in both `//` and `#` comments.
Leftovers from rewriting this PR in place. `prompt_request/streaming.rs` still told the reader to keep its predicate "in step with that target table" and named `rig-core/wasm` — both deleted two commits later. The advice was right, the referent was gone; point it at the predicate `rig-core` actually keys on. `rig-agent`'s crate docs had two sentences merged into one carrying three em-dash clauses and a 110-column line in a file that wraps at 78. Split and rewrapped. The CHANGELOG grew a second `### Changed` heading inside `[Unreleased]` when this PR's entries were prepended; folded back into one. Also softened "relaxing the bounds cannot break user code". True for implementors, since the relaxed markers are blanket-implemented, but a generic *consumer* that wrote `T: WasmCompatSend` and then relied on `T: Send` internally does break — on browser wasm, and only if it was building with the feature off. Narrow enough to be a parenthetical, not narrow enough to state the absolute.
The `rmcp rejected on wasm` job asserts the native-only `compile_error!`
is the *only* error, counting lines that match `^error(\[|:)`. But the
workflow sets `CARGO_TERM_COLOR: always`, so rustc prefixes every
diagnostic with SGR escapes:
^[[1m^[[91merror^[[0m^[[1m: the `rmcp` feature is native-only: ...
The anchor never matched, the count came out 0, and the job failed with
"expected exactly 1 error, got 0 — an ungated #[cfg(feature = "rmcp")]
is leaking follow-on errors" while the log directly above it showed
exactly one error and nothing else. The gate was fine; the parser was
reading past a color code.
Parse a decolorized copy instead of turning color off for the step, so
the check does not silently depend on the ambient `CARGO_TERM_COLOR`.
The echoed log keeps its colors.
Verified by running the step verbatim under `bash -e` with
`CARGO_TERM_COLOR=always`: exits 0, "rmcp on wasm fails with exactly one
actionable error". Before this it exited 1.
The guard was a line-scoped grep: find `target_arch = "wasm32"`, drop
lines that also say `target_os`. But `cargo fmt` wraps an attribute wider
than 100 columns across several lines, which splits `target_arch` from
the `target_os` that answers for it:
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "...",
))]
The guard flags line 2 of that and advises adding a `target_os` that is
already on line 3. Worse, the `cargo fmt --check` step above it *demands*
the wrapped form, so once a gate grows past the column limit the two
steps contradict each other and no source text satisfies both. No gate in
the tree is long enough to hit this today; the first one that grows a
third predicate is.
Scope the check to the whole `cfg(...)` group via balanced-paren
recursion, so wrapping is invisible to it. That also makes whitespace
around the `=` structurally irrelevant rather than allowed for by regex,
and brings `cfg!(...)` into scope. Comments are stripped per language
first (`//` in Rust, `#` not followed by `[` in TOML) — keeping the two
syntaxes separate is what preserves the prose exemption without
swallowing every `#[cfg(...)]`, the trap the previous version documented.
Group-anchoring could become its own blind spot, so a `target_arch =
"wasm32"` outside any `cfg`/`cfg!` group — a `cfg_attr` predicate, say —
is reported separately. Only `.rs` and `.toml` are read: the only places
a gate can live, and it keeps the walk off the cassette fixtures under
`tests/`.
Tested against the real tree, six directions: silent on the clean tree
and on a rustfmt-canonical wrapped gate (which the old guard flagged
while `rustfmt --check` called the same file canonical); fires on a bare
spaced gate in a `.rs` file, on a bare unspaced gate in a `Cargo.toml`,
and on a `cfg_attr` predicate; still exempts prose in `//`, `//!`, and
`#` comments. 0.02s over 763 files.
The guard's path list was the part that kept rotting. The first version
named only rig-core and rig-agent, leaving rig-candle, the other member
crates, the facade's own sources and every `tests/` directory unguarded.
Widening it to `crates/*/{src,tests,Cargo.toml}` plus the facade fixed
that instance and left `examples/` out — which is where `candle_wasm_chat`
lives, the one package this PR adds to the wasm CI matrix precisely
because it is the only consumer exercising the browser-wasm facade path.
The guard did not cover the crate it was written alongside.
Enumerating directories is the failure mode; stop enumerating. `git
ls-files -- '*.rs' '*.toml'` sweeps the repo, inherits `.gitignore` so
`target/` stays out, and the extension filter keeps the walk off the
cassette YAMLs under `tests/`. Nothing outside the normalized gates names
`target_arch`, so the breadth costs nothing: 932 files in 0.02s, against
763 before.
Only tracked files are listed — right for CI, where every file in a PR is
committed, but it means a local run before `git add` will not see a new
file. Noted in the step comment.
Verified: silent on the clean tree; fires on a bare gate planted in
`examples/candle_wasm_chat/src/` and in `crates/rig-mongodb/examples/`,
neither of which the previous scope read. The existing cases still hold —
fires on a bare gate in `crates/rig-agent/src`, silent on a
rustfmt-wrapped correct gate and on prose in both comment syntaxes.
`pub use rig_agent::tool::rmcp` was the one `rmcp` site left on a bare `feature = "rmcp"` after every other gained `not(target_family = "wasm")`. The module it names does not exist on wasm. Nothing breaks today: reaching this re-export requires `rig-agent` to have compiled, which its own `compile_error!` prevents, so the build stops one crate earlier. Confirmed — `cargo check -p rig --features rmcp --target wasm32-unknown-unknown` still reports exactly one error, the native-only diagnostic. Fixing it anyway, because the CI error-count assertion only builds `-p rig-agent`. If this predicate ever became load-bearing, no lane would notice. The uniform gate is also what makes "every `rmcp` cfg site carries the native predicate" a claim a reader can check by grepping. Verified: `cargo check -p rig --features rmcp` (native) and `cargo check -p rig --target wasm32-unknown-unknown` both clean.
Removing the `wasm` feature also changed what these two expand to, and that deserves its own entry rather than riding along in the feature removal, because the two break differently. `if_wasm!`/`if_not_wasm!` are `#[macro_export]`ed, and a `cfg` inside a macro expansion is evaluated in the *calling* crate. The old expansion therefore tested whether the **caller** had a feature named `wasm` — not `rig-core`. A downstream crate without one took the `if_not_wasm!` branch on every target, browser wasm included. Keying on the target is the fix, but it is a silent one: unlike the feature removal, which Cargo rejects at resolution, a caller that did define a `wasm` feature and expected it to drive these macros now gets the target's answer with nothing to fail on.
The grep guard cost more than it caught. It rejected correctly written `cfg_attr` predicates outright — `\bcfg!?\(` does not match `cfg_attr(`, so a correct `cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), ...)` fell to the outside-any-group fallback and failed with an error advising the exact spelling already on the line. It also flagged `/* */` prose (only `//` was stripped) and commented-out TOML tables starting with `#[`. What it guarded remains covered by jobs that compile rather than pattern-match: `check-wasm` and `check-wasm-runtimes` build rig-core, rig-agent, rig, and candle_wasm_chat for wasm32-unknown-unknown individually, and `check-rmcp-native-only` asserts rmcp on wasm fails with exactly one error. The residual gap is a bare `target_arch = "wasm32"` that builds fine on browser wasm but misbehaves on WASI — a target that does not build and is documented as unsupported, so nothing exercised it either way. The convention itself stays documented in crates/rig-agent/README.md.
This was referenced Jul 27, 2026
Merged
Closed
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
TL;DR: the WASM support matrix is now explicit and true. Browser wasm
(
wasm32-unknown-unknown) is the supported wasm target, WASI is documented asunsupported with the reason, and
rmcpis native-only with a one-sentencediagnostic instead of a wall of trait errors. Every wasm feature flag in the
workspace is gone —
cargo build --target wasm32-unknown-unknownis now theentire opt-in. Breaking only in feature surface; no behaviour changes anywhere.
Closes the last open finding from the #2197 architectural review
("Browser-WASM / WASI /
rmcpmatrix"); #2205, #2206, #2207, and #2208 closedthe others.
Why
The review filed one item; it is really three problems with different owners.
1. rig-agent's
cfgpredicates were bluntrig-corealready distinguishes precisely —target_family = "wasm"for "anywasm",
all(target_arch = "wasm32", target_os = "unknown")for things needing aJS host — and its manifest explains why.
rig-agentused a barecfg(target_arch = "wasm32"), so WASI builds pulledwasm-bindgen-futuresand
rig-core/wasm: a browser shim on a non-JS host. TheSend-relaxed streamaliases had the same bare predicate and so disagreed with
rig-core'sWasmCompat*markers on any non-browser wasm target.2.
rmcpcannot work on wasm — and not for a fixable reasonrmcp has its own maybe-
Sendabstraction, so enabling itslocalfeature lookslike the fix. It is not.
localrelaxes the future bound(
MaybeSendFuture); the handler bound is unconditional:rig's handler owns the tool registry, whose
Arc<dyn ErasedTool>isdeliberately neither
SendnorSyncon wasm (WasmCompatSend/WasmCompatSyncare no-op markers there). Two maybe-Sendabstractions that donot compose, with the mismatch upstream. I verified this by enabling
local:the
on_tool_list_changederror disappears and theClientHandlerboundimmediately replaces it.
3. WASI never built at all
Not "untested" — does not compile, with or without default features:
These are dependency-level:
rig-coredepends onreqwestunconditionally(the
reqwestfeature only adds features, it does not gatedep:), whichpulls
hyper/socket2and a tokio feature set WASI rejects. Supporting WASImeans making
reqwestoptional and supplying awasi:httpclient behindrig_core::http_client— a deliberate project, out of scope here. So there wasno WASI support to drop; there was only an inaccurate implication of one.
What changed
cfg(all(target_arch = "wasm32", target_os = "unknown")), matchingrig-core.The
Send-relaxed stream aliases use the same predicate, with a comment tyingthem to
rig-core'sWasmCompat*gate.rmcpnative-only. The dependency is scoped tocfg(not(target_family = "wasm")), everyrmcpcfg site carries the nativepredicate, and a targeted
compile_error!explains the situation. Requestingit on wasm now fails with exactly one error.
wasm-bindgen-futuresis gone fromrig-agent— its only user was the MCPcancellation dispatch, now unconditionally
tokio::spawn.a new ungated
#[cfg(feature = "rmcp")]recompiles the rmcp surface into wasmand leaks the trait wall behind our message. Verified — reverting one gate
takes the count from 1 to 14.
rig-core'swasm,rig-agent's, and the facade's. The feature carried no information the targetdid not already have: its only dependency payload
(
wasm-bindgen-futures) was never referenced inrig-core, making it a purecfgswitch, and bothrig-agentandrig-candlealready flipped it from a[target.'cfg(...)']table.WasmCompat*now keys onall(target_arch = "wasm32", target_os = "unknown")directly. Dependentspassing
features = ["wasm"]drop it; nothing replaces it.cfgbug fixed inrig-core's SSE aliases. The arms did notpartition:
not(target_arch = "wasm32")opposite a browser-scoped arm leftWASI matching neither, so
ResponseFuture/EventStreamwere undefinedthere. Pre-existing and invisible because WASI never built. Both arms now
share one predicate.
crates/rig-agent/README.mdand the cratedocs; the root README's "Full WASM compatibility" claim corrected.
Subtlety worth review attention
tool/server.rshad two complementary#[cfg(feature = "rmcp")]/#[cfg(not(feature = "rmcp"))]pairs. Narrowing only the positive arms leftwasm+rmcp binding neither, producing
cannot find valueerrors. Both armsnow carry matching predicates. This is exactly the failure the CI error-count
assertion is designed to catch.
Compatibility
One breaking change, and it is a feature-surface break with no behaviour behind
it: any dependent listing
features = ["wasm"]onrig,rig-core, orrig-agentmust drop it. Cargo rejects an unknown feature outright, so thisfails loudly at resolution rather than silently changing behaviour. Nothing
replaces it — build for
wasm32-unknown-unknownand the bounds relax.Relaxing those bounds cannot break implementors: the relaxed markers are
blanket-implemented (
impl<T> WasmCompatSend for T {}), so every type thatsatisfied the strict form satisfies the relaxed one. The one exception is a
generic consumer on browser wasm that wrote
T: WasmCompatSendand thenrelied on
T: Sendinternally — and only if it was previously building with thefeature off, which neither
rig-agentnorrig-candledid.Otherwise nothing that worked stops working.
rmcpnever compiled for any wasmtarget; WASI never built. What changes is the failure mode (one sentence instead
of ~34 errors), the honesty of the gates, and one fewer dependency in the wasm
graph.
Also fixed:
candle_wasm_chatnever built standalonePre-existing on
main, fixed here because it is the only consumer exercising thebrowser-wasm facade path this PR documents.
The example imports
rig::agent::{Agent, AgentBuilder}andrig::completion::Chatwhile declaringdefault-features = falsewithoutagent:CI missed it because cargo unifies features across packages built in one
invocation. The only job that compiles workspace members is the doctest job's
cargo test --doc --workspace --all-features, which enablesagenton theshared
rigbuild and supplies what this manifest forgot:cargo check -p candle_wasm_chat(its own features)cargo check -p candle_wasm_chat -p rig --all-features(unified)Both halves are fixed: the manifest declares
agent, andcandle_wasm_chatjoins the wasm matrix so every package is checked on its own. Matrix entries
are separate parallel jobs and this is the cheapest of the three —
default-features = falsetrims its tree to 544 crates againstrig's 3089.Verified the lane bites: reverting the manifest fix takes it from 0 errors to 4.
Found in review
From a review pass over the finished branch.
The feature deletion broke a test fixture.
tests/fixtures/tool_facade/declares an
all_rootfeature enumerating every facade feature, and it stillnamed
rig/wasm.tests/tool_facade_features.rsbuilds that fixture under--all-features, so it failed before compiling anything:That leg runs in CI (workspace
cargo nextest run --all-featuresturns onfacade-build-tests), so the branch was red from the feature-deletion commituntil this fix. Some irony: the lane this PR adds to catch an under-declared
manifest was broken by an over-declared one, and for the same underlying
reason — a feature list nothing checks against reality.
Stale prose.
prompt_request/streaming.rsstill told the reader to keep itspredicate "in step with that target table" and named
rig-core/wasm— bothdeleted two commits later. The crate docs had two sentences merged into one
carrying three em-dash clauses. The CHANGELOG grew a second
### Changedheading inside
[Unreleased]. And "relaxing the bounds cannot break user code"overstated the case; it is now the accurate version quoted under Compatibility.
Drift guard: added, then removed
Earlier commits on this branch added a grep check asserting that every
target_arch = "wasm32"names atarget_os. It has been removed — it costmore than it caught, in three ways:
cfg_attrpredicates.\bcfg!?\(does notmatch
cfg_attr(, so a correctcfg_attr(all(target_arch = "wasm32", target_os = "unknown"), ...)fellthrough to the outside-any-group fallback and failed — with an error advising
the exact spelling already on the line. There was no way to write a passing
wasm
cfg_attr./* */prose. Only//was stripped, so the "comments are exempt"property held for one of Rust's two comment syntaxes.
#[.The commits are left in history rather than squashed away, so the reasoning is
still readable; the final tree has no such step.
What it was standing in for is covered by jobs that compile rather than
pattern-match:
check-wasmandcheck-wasm-runtimesbuildrig-core,rig-agent,rig, andcandle_wasm_chatforwasm32-unknown-unknownindividually, and
check-rmcp-native-onlyasserts the rmcp diagnostic is theonly error. The residual gap is a bare
target_arch = "wasm32"that builds fineon browser wasm but misbehaves on WASI — a target that does not build and is
documented unsupported, so nothing exercised it either way. The convention stays
documented in
crates/rig-agent/README.md.Verification
cargo fmt --all --check;cargo clippy -p rig-core -p rig-agent --all-features --all-targets -- -D warnings— cleancargo test -p rig-agent --all-features --lib— 485 pass;-p rig-core --lib— 932 passcargo doc --no-deps -p rig-agent --all-features— cleanrmcp/derive,test-utils,audio,image),rig-agent + rig on
wasm32-unknown-unknown, rig +rmcpnative — all passcargo check -p rig-agent --features rmcp --target wasm32-unknown-unknown—fails with exactly one error, the native-only diagnostic
cargo check -p rig --no-default-features --features rmcp --target wasm32-unknown-unknown—also exactly one error, so the facade path behaves the same as
rig-agentdirect (CI asserts the count only for
rig-agent)cargo check -p rig-core --target wasm32-wasip1— still fails at thedependency level, unchanged in kind by this PR
cargo check --package {rig-agent,rig,candle_wasm_chat} --target wasm32-unknown-unknown— all clean (the new matrix, run verbatim)cargo metadata --manifest-path tests/fixtures/tool_facade/Cargo.toml --all-features— resolves (this is what the fixture fix restores)