Skip to content

generate_resolve merges full Component resolve, causing dep resolution panics #191

@rellfy

Description

@rellfy

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:

  1. 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)
  2. Strip worlds from that package
  3. Re-encode as WIT-only
  4. Decode the result (yields a DecodedWasm::WitPackage with a clean resolve)
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions