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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions