Context: I'm building asterai, a WASM component registry & runtime (to bundle & run components on top of wasmtime). Hit this issue when resolving a dependency on a published component's interface.
The issue is with wasm-pkg-core's dependency resolution when a dependency is a compiled Component binary (rather than a WIT-only package). The component's runtime world (with all its WASI imports) gets merged into the dep resolution, which causes wit-parser to panic.
Versions
wasm-pkg-core 0.15.0
wit-parser 0.244.0
wit-component 0.244.0
Reproduction
1. Publish a compiled component to an OCI registry
The component's WIT source is simple, no WASI deps:
package asterai:discord@0.1.0;
interface types {
record message {
content: string,
channel-id: string,
}
}
interface discord {
send-message: func(content: string, channel-id: string) -> string;
}
world component {
export discord;
export types;
}
The compiled component.wasm (built from Rust with cargo component build) includes WASI runtime imports: wasi:io, wasi:cli, wasi:filesystem, wasi:http, etc.
Push the compiled component binary to an OCI registry at the version tag asterai/discord:0.1.0.
2. Build a WIT package that depends on it
package asterai:discord-message-listener@0.1.0;
interface incoming-message {
use asterai:discord/types@0.1.0.{message};
handle: func(msg: message);
}
world component {
export wasi:cli/run@0.2.0;
}
Call resolve_dependencies followed by generate_resolve (or build_package / fetch_dependencies which call it internally).
3. Result
thread 'tokio-runtime-worker' panicked at wit-parser-0.244.0/src/resolve.rs:1807:25:
world import of wasi:filesystem/types@0.2.0 is missing transitive dep of wasi:io/streams@0.2.0
Without the asterai:discord dependency (i.e. only WIT-only deps from wasi.dev), everything works fine.
Root cause
In resolver.rs (L740), generate_resolve handles DecodedWasm::Component identically to DecodedWasm::WitPackage:
DecodedDependency::Wasm { resolution, decoded } => {
let resolve = match decoded {
DecodedWasm::WitPackage(resolve, _) => resolve,
// merges full runtime resolve!
DecodedWasm::Component(resolve, _) => resolve,
};
merged.merge(resolve).with_context(|| ...)?;
}
For a Component, the resolve includes:
- The component's exported package interfaces (what consumers actually need)
- A runtime world with imports for all WASI interfaces (
wasi:io, wasi:cli, wasi:filesystem, etc.)
- Full definitions of all those WASI packages
When this is merged, and then the separately-fetched wasi.dev WIT packages are also merged, assert_valid() iterates all worlds in the resolve, including the component's runtime world, and assert_world_elaborated finds broken cross-references from the duplicate WASI package merging.
I believe the runtime world is an implementation detail that shouldn't leak into dep resolution. Consumers only need the package's exported interfaces.
Suggested fix
When decoded is DecodedWasm::Component, sanitize before merging:
- Find the interface package by matching
resolve.packages against the dependency name (note: resolve.worlds[world_id].package points to a synthetic root:component package, not the actual interface package)
- Strip worlds from that package
- Re-encode as WIT-only
- Decode the result (yields a
DecodedWasm::WitPackage with a clean resolve)
- Merge that clean resolve instead
This is what we're doing as a workaround on our side (reimplementing generate_resolve with the sanitization step) and it resolves the issue.
I'm happy to submit a PR if this approach looks right.
By the way: assert_world_elaborated in wit-parser (resolve.rs:1807) uses assert! instead of returning an Err. So the validation failure panics the process. In a server context, this crashes the tokio worker thread. This might warrant a separate issue on bytecodealliance/wasm-tools? Though maybe assert! is reasonable in this scenario.
Context: I'm building asterai, a WASM component registry & runtime (to bundle & run components on top of wasmtime). Hit this issue when resolving a dependency on a published component's interface.
The issue is with
wasm-pkg-core's dependency resolution when a dependency is a compiled Component binary (rather than a WIT-only package). The component's runtime world (with all its WASI imports) gets merged into the dep resolution, which causeswit-parserto panic.Versions
wasm-pkg-core0.15.0wit-parser0.244.0wit-component0.244.0Reproduction
1. Publish a compiled component to an OCI registry
The component's WIT source is simple, no WASI deps:
The compiled
component.wasm(built from Rust withcargo component build) includes WASI runtime imports:wasi:io,wasi:cli,wasi:filesystem,wasi:http, etc.Push the compiled component binary to an OCI registry at the version tag
asterai/discord:0.1.0.2. Build a WIT package that depends on it
Call
resolve_dependenciesfollowed bygenerate_resolve(orbuild_package/fetch_dependencieswhich call it internally).3. Result
Without the
asterai:discorddependency (i.e. only WIT-only deps from wasi.dev), everything works fine.Root cause
In
resolver.rs(L740),generate_resolvehandlesDecodedWasm::Componentidentically toDecodedWasm::WitPackage:For a
Component, the resolve includes:wasi:io,wasi:cli,wasi:filesystem, etc.)When this is merged, and then the separately-fetched wasi.dev WIT packages are also merged,
assert_valid()iterates all worlds in the resolve, including the component's runtime world, andassert_world_elaboratedfinds broken cross-references from the duplicate WASI package merging.I believe the runtime world is an implementation detail that shouldn't leak into dep resolution. Consumers only need the package's exported interfaces.
Suggested fix
When
decodedisDecodedWasm::Component, sanitize before merging:resolve.packagesagainst the dependency name (note:resolve.worlds[world_id].packagepoints to a syntheticroot:componentpackage, not the actual interface package)DecodedWasm::WitPackagewith a clean resolve)This is what we're doing as a workaround on our side (reimplementing
generate_resolvewith the sanitization step) and it resolves the issue.I'm happy to submit a PR if this approach looks right.
By the way:
assert_world_elaboratedinwit-parser(resolve.rs:1807) usesassert!instead of returning anErr. So the validation failure panics the process. In a server context, this crashes the tokio worker thread. This might warrant a separate issue onbytecodealliance/wasm-tools? Though maybeassert!is reasonable in this scenario.