Prompt and context
The runtime, tenant isolation, and execution budgets already exist. The problem is narrower: hundreds of installed components implement several generations of WebAssembly Interface Types (WIT), and a workflow may compose a parser, redactor, and exporter. Design the compatibility and composition layer that lets the platform change contracts safely.
WIT defines contracts rather than behavior. A world lists what a component imports and exports, and the Component Model allows components to be composed. Those facts make compatibility testable, but they do not decide which changes are safe or which capabilities a composed graph should receive. The platform must supply that policy.
What the interviewer evaluates
- Whether you separate source-language compatibility, WIT shape compatibility, and behavioral compatibility.
- Whether every import in the final component graph is authorized explicitly instead of inherited accidentally.
- Whether adapters are versioned, observable, and removable rather than becoming permanent hidden logic.
- Whether rollback preserves the data and resource semantics expected by the previous component.
Clarifying questions to ask
- Which installed interface generations must remain runnable, and for how long?
- Are calls stateless values, owned or borrowed resources, streams, or long-lived handles?
- Can a plugin call another plugin directly, or must all calls pass through the host?
- Which changes are additive, and which change meaning, errors, or ownership?
- Must a workflow roll back atomically, or may components use different versions?
Stateless value adapters can often run inline; resource ownership changes may require a new major contract and migration. Direct composition is acceptable only when the linker can prove that the final imports remain inside the tenant's grant.
A 30-second answer
“I would register every WIT package and world by immutable version, compute its imports, exports, types, and resource ownership, then check a proposed graph before instantiation. Existing plugins keep their old contract. A small explicit adapter may translate old values into the new interface, but it cannot invent capabilities or silently change failure semantics. The host grants only the intersection of tenant policy and the final graph's declared imports. I would compare both versions on recorded fixtures, canary by tenant, and keep the old graph until rollback and retirement criteria pass.”
Step-by-step deep dive
1. Build an interface registry and compatibility report
Store the WIT package version, world, imports, exports, type definitions, ownership rules, artifact digest, and adapter chain. On publication, compare the new contract with every supported predecessor. Adding an optional field may be adaptable; removing a variant case, changing an error meaning, or turning a borrowed handle into an owned resource needs an explicit migration decision.
2. Keep adapters narrow and directional
An adapter translates one known contract into another. It may rename a field, supply a documented default, or map an old error into a new variant. It must reject information that cannot be represented safely. Limit adapter chains to one supported hop where possible; long chains multiply ambiguity and make rollback hard to reason about.
3. Authorize the composed graph
Before linking, resolve every import to a provider and produce a final capability manifest. The host checks each filesystem, network, clock, random, or business-service import against the tenant and plugin grants. A redactor that imports no network access must not gain it merely because it is composed with an exporter. Route privileged calls through host-owned interfaces so policy and audit remain at one boundary.
4. Prove behavior before retiring a contract
Run old and new graphs on deterministic fixtures. Compare typed outputs, declared errors, resource creation and release, host calls, and bounded performance. Shadow real requests only when inputs can be handled safely, then canary by tenant. Rollback switches the graph descriptor to the last compatible artifact; stateful resources require migration or draining rather than a blind pointer flip.
A strong sample answer
“I would treat the WIT world as a deployable contract. Each immutable registry entry records the package version, world, complete import and export set, type shapes, resource ownership, artifact digest, and supported predecessor. Value-only additive changes may use one reviewed adapter; changed ownership, missing variant cases, or new side effects require a new contract and explicit consumer migration.
Composition happens from a graph descriptor. The linker resolves providers, but the host separately authorizes the graph's complete import set. Capabilities are bounded by tenant policy and each component's declared grant. For rollout I replay fixtures through both graphs and compare values, errors, host calls, and resource lifecycles. Then I canary by tenant and retain the previous graph until mismatch, error, latency, and leak thresholds pass. This answers interface evolution and safe composition, not the general problem of building a Wasm sandbox.”
Common mistakes
- Calling every additive change compatible → a new required import or changed default can alter behavior → classify type, capability, ownership, and semantic changes separately.
- Letting composition union all imports → a low-privilege component gains another component's authority → authorize the resolved graph against explicit grants.
- Stacking adapters indefinitely → behavior depends on an opaque chain → support bounded hops and retire adapters with usage evidence.
- Comparing only successful output → errors, host calls, and resource leaks can regress → compare failures, side effects, ownership, and budgets.
- Rolling back stateful components instantly → live handles may belong to the new contract → drain, migrate, or pin sessions before switching.
Follow-up questions and responses
How do you handle a new required capability?
Treat it as a permission change. Publish a new graph, show the requested capability to the tenant administrator, and activate it only after an explicit grant. Existing installations remain on the old contract.
What if an old value cannot be represented by the new type?
The adapter returns a typed incompatibility error and the rollout blocks for affected fixtures or tenants. Inventing a default would hide data loss. Use a migration tool or parallel major version.
How do you retire an adapter?
Track installed callers, real invocations, mismatch rates, and last-use time by version. Stop new installations first, migrate callers, keep a rollback window, and delete the adapter only when no supported graph references it.
Can components call each other without the host?
They can be statically composed when resolved imports and resource semantics are known, but privileged operations still pass through host-provided interfaces. Authorization, quotas, and audit stay at a controlled boundary.