3.8 KiB
Sandbox WASM Host Bridge
Package: @suaveplan/api-sandbox-wasm-host-bridge
Status: PROPOSED
Type: new-package (host-integration layer)
Why
Same shape as api-sandbox-vm-host-bridge but for the WASM tier. @suaveplan/api-sandbox-wasm ships a WasmSandboxHandle — a WebAssembly.Instance running under WASI snapshot_preview1 with permission-gated suaveplan_caps.* imports. @suaveplan/api-host.installPlugin(spec) takes a PluginModule. The bridge between them does not exist, so gate 2.8's "deploy under wasm tier" requirement is unreachable.
Concrete consumer
@suaveplan/api-host.installPlugin(spec)— consumes thePluginModuleproduced by this bridge.@suaveplan/e2e-foundation-stack-fullStage-8 closure smoke — deploys the WASM artifact produced by@suaveplan/wasm-compiler-javy(sibling change) through this bridge under the wasm tier.
Host bridge
This package IS the host-integration for api-sandbox-wasm. Required by §19.
End-to-end test
e2e/foundation-stack-full/tests/sandbox-wasm-deployment.spec.ts— deploys a WASM-compiledapi-plugin-examplevia this bridge, drives an HTTP request through the host, asserts response was produced by the WASM-sandboxed plugin.
What changes
Package surface
import { createWasmSandbox } from "@suaveplan/api-sandbox-wasm";
import { createSandboxedWasmPluginModule } from "@suaveplan/api-sandbox-wasm-host-bridge";
const sandbox = createWasmSandbox({
pluginName: "example",
policy,
capabilities,
checker,
wasmModule: await WebAssembly.compile(wasmBytes),
});
const module = await createSandboxedWasmPluginModule({
sandbox,
manifest: pluginManifest,
});
await host.installPlugin({ kind: "wasm-sandboxed", module, manifest });
Marshalling
Same SerializedRequest / SerializedResponse shapes from @suaveplan/api-types/sandbox-bridge (authored by the api-sandbox-vm-host-bridge change as the shared contract). Both bridges use the same wire format.
WASM-specific marshalling uses linear-memory buffer passing:
- Bridge allocates a buffer via the WASM
__suaveplan_alloc(len)export. - Writes the JSON-encoded
SerializedRequestbytes into linear memory at the allocated offset. - Calls the WASM
__suaveplan_handle_request(ptr, len)export, which returns(responsePtr, responseLen). - Reads the response bytes from linear memory.
- Frees both buffers via
__suaveplan_free(ptr). - Parses the
SerializedResponseand returns the hostResponse.
The WASM plugin must export __suaveplan_alloc, __suaveplan_free, __suaveplan_handle_request, __suaveplan_routes (returns a JSON-encoded routes array at init time), __suaveplan_start, __suaveplan_dispose. The @suaveplan/wasm-compiler-javy sibling produces artifacts that conform to this ABI.
Error codes
SAND_WASM_BRIDGE.* registry:
INVALID_PLUGIN_SURFACE— WASM module didn't export required functions.MEMORY_ALLOC_FAILED—__suaveplan_allocreturned 0 or trapped.MARSHAL_FAILED— JSON-encoding of the request failed.UNMARSHAL_FAILED— WASM returned non-conformant response bytes.HANDLER_TRAPPED— WASM execution trapped.CAPABILITY_DENIED— capability import returned WASI ACCES.LIFECYCLE_FAILED— start/dispose export call trapped.
Conformance harness
runWasmHostBridgeConformance({ buildBridge }) with five invariants mirroring the vm-bridge harness: route-registration, request-round-trip, lifecycle, error-propagation, capability-gate.
Out of scope
- The WASM compilation pipeline itself (sibling change
wasm-compiler-javy). - Component-model-based WASM plugins (this bridge targets WASI
snapshot_preview1; component-model is a follow-up). - Cross-instance shared memory (WASM linear-memory isolation is by design).