# Proposal: WASI Host Adapter

How capOS should host WebAssembly modules through the WebAssembly System
Interface, without recreating ambient authority and without committing to a
runtime that the userspace baseline cannot support today.


## Problem

WASI is the natural sandboxed-execution path for capOS:

- It is already designed to remove ambient authority. Preview 1 requires
  preopens — every file descriptor a module sees was granted by the host at
  startup. Preview 2 makes typed handles first-class through the
  Component Model.
- A single host adapter unlocks every language with a useful WASI target:
  Rust, C/C++, Go (`GOOS=wasip1`), TinyGo, Python, Zig, AssemblyScript,
  any interpreter compiled to wasm.
- Wasm linear-memory bounds checks plus capability scoping give defence in
  depth for untrusted plugins and third-party code without weakening the
  capOS isolation model.

The risk pattern is the same as POSIX: a host adapter that grants ambient
authority would erase the property that makes WASI worth doing. Every WASI
import must be backed by a typed capability the host process already holds.
If the host does not hold the cap, the module cannot reach it.

WASI is **not** a substitute for native ports of languages that need real OS
threads, full asynchronous I/O, signals, or large POSIX surfaces. Those
remain the native runtime tracks. WASI **is** the right tool for sandboxing
untrusted plugins, third-party scripts, isolated workloads, CPU-bound
portable tools, and language ecosystems whose native capOS port has not
yet been built.

## Scope

In scope:

- A `capos-wasm` userspace host adapter built on `capos-rt`.
- A WASI Preview 1 surface whose imports map 1:1 to typed capOS capabilities.
- Per-instance CapSet projection: each module sees only the caps the host
  grants for that instance.
- Phase decomposition that picks one runtime for v0, lets later phases
  migrate to the Component Model and richer runtimes, and stays explicitly
  outside ambient authority.
- Validation through QEMU smokes that prove granted and ungranted paths.

Out of scope for the first implementation:

- `wasi-threads` (requires `shared-memory` + `atomics` + `bulk-memory`).
- `fork()`-shaped semantics. Cannot clone wasm linear memory; same
  constraint as the browser-wasm proposal.
- Synchronous signal delivery inside a wasm module. Fuel exhaustion plus
  host-driven termination are the only deterministic interruptions.
- File-backed `MAP_SHARED` mmap.
- Treating the wasm sandbox as the only isolation boundary for hostile
  modules — the capOS process boundary remains the primary boundary.
- A custom non-portable WIT dialect with externref-typed cap handles. This
  proposal explicitly defers richer cap handles to Component Model resources
  (Phase W.7).

## Current Manual Pages

- [Programming Languages](../programming-languages.md) summarizes WASI's
  current status relative to Rust, Python, Go, C/C++, Lua, and POSIX adapter
  tracks.
- [Userspace Binaries](userspace-binaries-proposal.md) Part 5 sketches the
  WASI host adapter at a higher level. This proposal supersedes that sketch
  with a full design surface; the userspace-binaries proposal continues to
  own the broader native-binary, language, and POSIX-adapter roadmap.
- [Userspace Runtime](../architecture/userspace-runtime.md) documents the
  implemented `capos-rt` surface that the host adapter consumes.
- [Browser/WASM](browser-wasm-proposal.md) covers the separate
  browser-hosted wasm experiment. The two proposals share wasm-runtime
  insight but target different substrates: WASI host adapter runs on capOS
  hardware; the browser proposal runs capOS concepts in a browser tab.
- [Lua Scripting](lua-scripting-proposal.md) covers a similar
  capability-scoped script runner shape; the WASI track is the untrusted /
  portable counterpart to that proposal's trusted native runner.
- [Go Runtime](go-runtime-proposal.md) covers the native `GOOS=capos`
  alternative to Go-on-WASI.

## Research Grounding

Relevant research and external references:

- WASI Preview 2 launch — Bytecode Alliance,
  ["WASI 0.2 Launched"](https://bytecodealliance.org/articles/WASI-0.2).
- Component Model status — eunomia,
  ["WASI and the WebAssembly Component Model: Current Status"](https://eunomia.dev/blog/2025/02/16/wasi-and-the-webassembly-component-model-current-status/).
- WIT resources / portable plugins — Medium,
  ["WASI 2.0 Components: Portable, Fast Plugins"](https://medium.com/@hadiyolworld007/wasi-2-0-components-portable-fast-plugins-58c24d891584).
- Externref design — Bytecode Alliance,
  ["WebAssembly Reference Types in Wasmtime"](https://bytecodealliance.org/articles/reference-types-in-wasmtime).
- Rust target stabilization — Rust Blog,
  ["Changes to Rust's WASI targets"](https://blog.rust-lang.org/2024/04/09/updates-to-rusts-wasi-targets/)
  and ["wasm32-wasip2 Tier 2"](https://blog.rust-lang.org/2024/11/26/wasip2-tier-2/).
- TinyGo WASI — [TinyGo WASI guide](https://tinygo.org/docs/guides/webassembly/wasi/),
  wasmCloud,
  ["Compile Go directly to WebAssembly components with TinyGo and WASI P2"](https://wasmcloud.com/blog/compile-go-directly-to-webassembly-components-with-tinygo-and-wasi-p2/).
- Runtime survey —
  [Wasmi v0.32 release notes](https://wasmi-labs.github.io/blog/posts/wasmi-v0.32/),
  arXiv 2404.12621
  ["Research on WebAssembly Runtimes"](https://arxiv.org/html/2404.12621v1),
  Colin Breck,
  ["Choosing a WebAssembly Run-Time"](https://blog.colinbreck.com/choosing-a-webassembly-run-time/).
- Runtime repos — [wasmi](https://github.com/wasmi-labs/wasmi),
  [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime),
  [wasmtime](https://github.com/bytecodealliance/wasmtime),
  [wasm3](https://github.com/wasm3/wasm3),
  [wasmer](https://github.com/wasmerio/wasmer).

In-tree references: this proposal lifts the capability-mapping table from
`docs/proposals/userspace-binaries-proposal.md` Part 5 and the runtime
survey/phase decomposition shape from comparable language-runtime planning
work; concrete repo evidence appears inline below.

## Design Principles

1. **WASI is not a kernel feature.** The kernel sees a normal userspace
   process with a CapSet and a capability ring. The host adapter is one
   of many `capos-rt`-based binaries.
2. **The host adapter's CapSet is the authority.** WASI module bytes are
   data. They cannot create authority. Every import is satisfied by a cap
   the host already holds; absent caps are refused, not synthesised.
3. **Per-instance CapSets are subsets, not supersets.** Each loaded module
   gets only the caps the manifest grants for that instance. The host's own
   CapSet may be larger; the module never sees the parent.
4. **The wasm sandbox is defence in depth, not the isolation boundary.**
   The capOS process boundary remains primary. Wasm bounds checking and
   immutable `Module` validation add a second software-enforced boundary
   inside the host process so an entire untrusted module image can be
   confined.
5. **Schema-first capability mapping.** Each WASI function is backed by a
   typed capability, not by emulated POSIX semantics. POSIX-shaped
   integer fds in Preview 1 are a Preview 1 ABI requirement, not a
   capability model concession.
6. **Pick portable WASI, skip non-portable extensions.** Custom imports
   with `externref`-typed cap handles would lock capOS into a non-portable
   WIT dialect that no other host implements. The Component Model's typed
   resources are the right answer for first-class typed cap handles in
   wasm; defer to that path rather than inventing a one-vendor dialect.
7. **Fail closed.** Any unimplemented WASI call returns `ERRNO_NOSYS`.
   Any cap lookup that fails returns the appropriate Preview 1 errno
   (`ERRNO_BADF`, `ERRNO_ACCES`, `ERRNO_NOSYS`). Modules cannot probe
   absent caps for ambient behavior.

## Architecture

```mermaid
flowchart TD
    Manifest[boot manifest:<br/>system-wasm-host.cue] --> Host[capos-wasm process]
    Host --> Runtime[wasm runtime<br/>wasmi v0]
    Host --> Rt[capos-rt typed clients]
    Rt --> Ring[capability ring]
    Ring --> Kernel[kernel CapObject dispatch]
    Ring --> Services[userspace services]

    Runtime --> Module[wasm module instance]
    Module --> Imports{WASI imports}
    Imports --> FdTable[per-instance fd table /<br/>Preview 2 resource handles]
    FdTable --> Caps[granted typed caps]
    Caps --> Rt
```

`capos-wasm` is one userspace process. It hosts one or more wasm module
instances. The runtime engine (wasmi for v0; see Runtime Selection below)
is linked into that process. WASI imports are resolved by the host
adapter's import-resolver module against typed capOS clients. Each instance
has its own per-instance fd table (Preview 1) or resource bundle
(Preview 2) populated from the manifest grants for that instance.

The runtime exposes only what the host process can fulfil. If the host
does not hold an `EntropySource` cap, `random_get` returns `ERRNO_NOSYS`.
If the manifest did not grant a `home` namespace, the module's preopen
table does not contain it and `path_open("/home/...")` resolves to
nothing.

## Runtime Selection

For v0 (Phases W.1 through W.6), use **wasmi**. For W.7+, evaluate
migration to **wasmtime** when capOS userspace gains `std` support and a
futures executor, or to **WAMR** if minimal footprint becomes the dominant
constraint and the C build path lands.

| Constraint | wasmi | WAMR | wasm3 | wasmtime |
|---|---|---|---|---|
| Pure Rust, drops into capOS workspace | yes | C (needs `cc`/build glue, no `libcapos` yet) | C (same problem) | yes |
| `no_std + alloc` | yes, advertised explicitly | partial (embedded, libc-shaped) | yes (bare metal) | no (needs `std` and a futures executor) |
| License | Apache-2.0 / MIT | Apache-2.0 with LLVM exception | MIT | Apache-2.0 |
| Footprint | small register-based bytecode (v0.32 5x speedup) | ~29 KB AOT, ~58 KB interpreter | ~64 KB code, ~10 KB RAM | large (Cranelift JIT) |
| Sandboxing | wasm spec + execution-engine isolation | wasm spec + AOT validation | wasm spec | wasm spec + Cranelift verifier |
| Fuel/gas metering | yes, built-in | not advertised | yes | yes |
| Capability transfer | externref since 0.24; component model on roadmap | reference types yes; component model partial | partial reference types | full component model (best-in-class) |
| WASI versions | preview1 stable; preview2 on roadmap | preview1 stable; preview2 partial | preview1 partial | preview1 + preview2 + components |
| Host function interface | mirrors wasmtime API | C API; Rust through `wamr-rust-sdk` | C API | Rust + C |
| Maintenance | wasmi-labs, two security audits (2023, 2024) | Bytecode Alliance, TSC-governed | maintainer in minimal-maintenance phase | Bytecode Alliance flagship |
| Threading | not in current scope | yes (wasi-threads) | no | yes |

**Why wasmi for v0:**

- Pure Rust drops directly into the capOS workspace. No C build chain
  required — the same chain `libcapos` does not yet provide.
- Genuine `no_std + alloc` support means no host-side OS abstraction is
  required for the runtime itself; it sits cleanly on `capos-rt`.
- Built-in fuel metering matches capOS's preference for explicit resource
  accounting.
- `externref` support is sufficient for any future v1 capability-handle
  experiment that does not block on the Component Model.
- Mirroring the wasmtime API means that migrating to wasmtime in W.7 is
  rewiring imports, not rewriting host calls.

**Not chosen for v0:**

- **wasmtime** needs `std` userspace and a futures executor. capOS
  userspace is `no_std + alloc` today; this is the same blocker that keeps
  the Rust `capnp-rpc` crate (v0.25) off `capos-rt` and queues the
  remote-session-client capnp-rpc rewrite behind an async runtime decision.
- **wasm3** is in maintainer-declared minimal-maintenance phase; not a
  good fit for a long-horizon capOS substrate.
- **wasmer** has similar weight to wasmtime and does not align as cleanly
  with the Bytecode Alliance Preview 2 trajectory.
- **WAMR** is a strong candidate when a C toolchain and `libcapos` exist
  and minimal footprint is the goal. It is the migration target for
  high-density wasm hosting later, but it is not the v0 baseline because
  the C substrate is not in tree.

## WASI Version Stance

- **Preview 1** for v0 (Phases W.1 through W.6). POSIX-shaped,
  file-descriptor-based, C-friendly. Tier 2 in upstream Rust since 1.78
  (May 2024); supported by Go 1.21+ (`GOOS=wasip1 GOARCH=wasm`),
  TinyGo, Clang `--target=wasm32-wasi`, Zig. This is the immediate
  unlock.
- **Preview 2 / Component Model** for W.7+. Resources are first-class
  typed handles. They are the natural mapping for capOS capabilities —
  closer in shape to `OwnedCapability<T>` than to integer fds. WIT
  interfaces let cap-aware Rust crates export typed APIs that a wasm
  component on capOS or a native capOS service can consume the same way
  it consumes a capnp interface.

Skipping Preview 1 entirely and starting at Preview 2 is possible with
wasmtime today, but harder with wasmi; doing so would push the entire v0
unlock behind the std-userspace decision. The Preview 1 first / Preview 2
later sequencing is the smaller-step path to running C, Rust, Go, Python,
TinyGo on capOS.

## Capability Mapping Surface

### Preview 1: per-import mapping

Each Preview 1 import is backed by a typed capOS capability the host
adapter already holds. POSIX inherits ambient authority through global
path namespaces, integer fds, and a process credential table; WASI removes
that by requiring preopens, and capOS pushes it further by requiring an
explicit per-import cap mapping in the host adapter.

| WASI preview1 import | capOS host-adapter implementation |
|---|---|
| `args_get` / `args_sizes_get` | Read from a future capOS `LaunchParameters` cap or per-instance arena. Empty by default until that surface lands. |
| `environ_get` / `environ_sizes_get` | Read from a `KeyValueScope` / `ConfigOverlay` cap when one exists; empty by default. Open question §6. |
| `clock_time_get(MONOTONIC)` | `Timer.now()` over the host's `TimerClient`. |
| `clock_time_get(REALTIME)` | Future wall-clock cap; until then return `ERRNO_NOSYS` or `ERRNO_INVAL`. |
| `proc_exit(code)` | Map to a host-internal "instance exited with code" status. The host process does not exit; the wasm instance does. |
| `random_get` | The kernel `EntropySource` cap (the in-tree CSPRNG capability; see `schema/capos.capnp` interface `EntropySource` and `KernelCapSource::EntropySource`). Refuse with `ERRNO_NOSYS` when the host adapter was not granted entropy authority. |
| `fd_write(1, ...)` / `fd_write(2, ...)` | Pre-opened fd 1 to host's `Console` / `TerminalSession` write path; fd 2 to same or a separate log cap if granted. |
| `fd_read(0, ...)` | Pre-opened fd 0 to a granted `TerminalSession` or future `StdIO` input cap if available; else `ERRNO_BADF`. No bare in-tree `StdinReader` cap exists today; non-terminal stdin requires a future input cap. |
| `path_open(preopened_dir_fd, path, ...)` | Resolve `path` inside the `Namespace` cap mounted as that preopen, then open through the namespace's `Store` / `File` capability. |
| `fd_read` / `fd_write` on opened files | Translate to the typed `File` capability behind the host-side fd table entry. |
| `fd_close` | Drop the typed cap handle (release-on-drop in capos-rt). |
| `fd_seek` / `fd_tell` / `fd_filestat_get` | Methods on the `File` cap. |
| `fd_prestat_get` / `fd_prestat_dir_name` | Enumerate the host adapter's preopened-directory table built from manifest grants. |
| `sock_send` / `sock_recv` / `sock_shutdown` | Translate to typed `TcpSocket` / `UdpSocket` cap calls. |
| `poll_oneoff` | Multiplex over the host's capability ring; CQEs are the event source. Open question §3. |
| `fd_advise` / `fd_allocate` / `fd_renumber` | Stub or `ERRNO_NOSYS` until needed. |
| `sched_yield` | No-op or single-tick yield through the runtime's scheduler. |

### Preview 2: WIT-resource mapping

When the host adapter migrates to Preview 2 (Phase W.7+), the imports
become typed capOS capabilities directly through WIT resources:

| WIT package / interface | capOS host-side cap |
|---|---|
| `wasi:io/streams` (`input-stream`, `output-stream` resources) | Wrap one capOS cap per stream (`Console` / `TerminalSession` / `File` / `TcpSocket`). The resource handle in wasm corresponds 1:1 to a host-side `OwnedCapability<T>`. |
| `wasi:filesystem/types` (`descriptor` resource) | One `OwnedCapability<File>` or `OwnedCapability<Directory>` per descriptor. Preopened dirs become resource handles passed at instantiation. |
| `wasi:clocks/{monotonic-clock,wall-clock}` | `Timer` / future wall-clock cap. |
| `wasi:random/{random,insecure}` | `EntropySource` cap. |
| `wasi:sockets/tcp` (`tcp-socket` resource) | `TcpSocket` cap. |
| `wasi:cli/{stdin,stdout,stderr,environment,exit}` | Per-instance CapSet projection. |
| `wasi:http/incoming-handler` / `outgoing-handler` | Match capOS `HttpEndpoint` / `Fetch` (drafted in `service-architecture-proposal.md`). |

Components in the same store can pass resources to other components; the
host mediates the move. This maps directly to capOS capability transfer
semantics — the same shape as the kernel's result-cap insertion for typed
cap returns from a CALL.

## Capability Handle Path in the Module

How a wasm module receives and refers to a capOS capability is one of the
load-bearing design questions. Three options:

1. **Preview 1 + integer fds, host-side fd table only (recommended for v0).**
   All caps live in the host process. The module sees integer fds. The
   host adapter maps fds to `OwnedCapability<T>` slots in its own per-
   instance table. Works with every existing `wasip1` binary unchanged. A
   wasm module cannot pass a typed cap to another wasm module without
   going through the host.
2. **Custom `externref` import (alternative; not recommended).** Requires
   the `reference-types` proposal (supported by wasmi >=0.24, wasmtime,
   wasmer; partial in wasm3). The host adapter exports custom imports
   like `cap_call_ref` that take an `externref` typed handle. This is
   non-standard and locks capOS into a one-vendor WIT dialect that no
   other host implements; it would also delay Preview 2 adoption because
   the dialect would need its own mapping policy.
3. **Preview 2 / Component Model resources (target for W.7+).**
   Resources in the Component Model are unforgeable typed handles.
   Components that import `wasi:filesystem/types.descriptor` receive a
   handle that is the host-side `OwnedCapability<File>`. Components can
   pass resources to other components in the same store; the host
   mediates. Direct match to capOS capability transfer semantics.

**Recommendation:** ship Preview 1 + integer fds for v0; defer rich
typed-cap-in-module support to Preview 2 in W.7. Skip the `externref`
custom-import path entirely.

## Per-Instance vs Per-Process Model

Two reasonable shapes:

1. **One wasm instance per `capos-wasm` process (recommended for v0).**
   Faults are isolated at the capOS process boundary. Fuel and budget
   enforcement are per-process and use the existing capOS resource
   accounting. Manifest-grant shape stays simple: each manifest entry
   names one binary and one cap bundle.
2. **Many instances per `capos-wasm` process (alternative).** Better
   density. Suits hosting many small modules (plugin systems, embedded
   scripts). Adds host-side scheduling concerns: a runaway instance can
   starve siblings; fuel/budget enforcement now has to demultiplex; the
   `poll_oneoff` reactor question becomes load-bearing.

**Recommendation:** one instance per process for v0. Revisit when
instance count actually matters. The capOS process boundary is already a
strong isolation primitive; trading it away for density before density is
needed adds complexity for no v0 unlock.

## Per-Instance CapSet Plumbing

Each loaded module gets a per-instance capability bundle. The host adapter
receives manifest grants and projects them onto WASI imports.

The shape needs to land alongside argv/env passing — argv for wasm
modules has the same lifecycle question as argv for native processes.
When a future capOS `LaunchParameters` surface lands it becomes the
canonical source for both argv and env. Until then, a small bounded
text grant in the host adapter manifest is acceptable for v0
(Open Question §6 / §7).

Sketch of the manifest shape (pre-LaunchParameters):

```text
wasm_host: {
    binary: "thing.wasm"
    args: ["--input", "data"]
    caps: {
        console:   @console
        timer:     @timer
        random:    @random
        // preopen 3 → home namespace; preopen 4 → tmp namespace, etc.
        preopens: [
            { fd: 3, namespace: @home_namespace, name: "/home" }
            { fd: 4, namespace: @tmp_namespace,  name: "/tmp" }
        ]
    }
}
```

Same authority model the rest of capOS uses: every cap the module sees is
named in the manifest and granted by the parent. The wasm sandbox is
defence in depth on top of capability scoping, not a replacement.

## Trust Boundaries

| Boundary | Native capOS service | WASI host adapter + module |
|---|---|---|
| Authority source | Process CapSet | Host CapSet then per-instance subset |
| Memory isolation | Page tables | Wasm linear-memory bounds-check plus page tables (host process) |
| Code integrity | W^X + NX | Wasm module validation plus immutable `WebAssembly.Module` |
| Cap forgery | Kernel-owned `CapTable` | Host-owned per-instance fd table or resource-handle table; module sees opaque ints/handles only |
| Resource limits | Kernel quotas | Wasm fuel + memory cap + host-side per-instance time/byte budgets |
| Side channels | Hardware-level (Spectre etc.) | Same hardware level, plus wasm-specific (e.g. timer resolution) |

Wasm does not weaken capOS isolation; it adds a second software-enforced
boundary that contains an entire untrusted module image. This is exactly
the property that makes WASI a good fit for plugin and script loading.

## What WASI Does Not Solve

- **`fork()`**: cannot clone wasm linear memory mid-execution. Same
  reason the browser-wasm proposal documents. POSIX programs that
  fork-then-exec must use `posix_spawn`-shaped equivalents, or the host
  adapter must spawn a new wasm instance.
- **Synchronous signals**: no preemption inside a wasm module without
  cooperative yield points or interrupted execution. Fuel exhaustion is
  the only deterministic interruption; gross preemption is "host kills
  the instance". Acceptable for plugins.
- **Threads without `wasm-threads`**: requires `shared-memory` +
  `atomics` + `bulk-memory` features and a runtime that supports them.
  Out of scope for v0.
- **Live `mmap` of files**: wasm linear memory is not file-backed.
  Workable only for small read-or-write cycles.

## Phase Decomposition

Smallest reviewable slices ordered by dependency. Each phase is
independently demoable and gates the next.

### Phase W.0 — Decision and host runtime selection (planning)

- Decide runtime: wasmi vs WAMR (recommendation above).
- Land this proposal and the matching `docs/plans/wasi-host-adapter.md`
  ralphex plan.
- Resolve cross-cutting open questions §1, §3, §6, §7, and §8 below
  (the §8 vendoring posture decision gates the W.1 scaffold layout).

**Deliverable:** agreed proposal + plan file. No code.

### Phase W.1 — `capos-wasm` host process scaffold (no WASI yet)

- New crate `capos-wasm/` — userspace process built on `capos-rt`.
- Vendor the chosen runtime (wasmi recommended; one local cargo dep
  patched for `no_std + alloc` if needed).
- Host process can `WebAssembly.compile(bytes)` then `instantiate(no
  imports)` then run an empty `_start`. No imports resolved yet.
- Manifest: new `system-wasm-host.cue` boots one host process with
  one embedded `.wasm` blob (the smoke binary).
- Smoke: `make run-wasm-host` boots, host loads the empty blob,
  prints `[wasm-host] empty module instantiated and exited`, host
  exits cleanly.

**Deliverable:** a wasm runtime runs as a capOS process on top of
`capos-rt`. No imports, no host functions, no WASI. Validates the
runtime crate works in `no_std + alloc` userspace.

### Phase W.2 — WASI Preview 1 stdout-only

- Implement `args_get`, `args_sizes_get`, `environ_get`,
  `environ_sizes_get` (all return empty), `clock_time_get(MONOTONIC)`
  → `Timer.now()`, `proc_exit`, `random_get` (gated on a granted
  `EntropySource` cap; refuse with `ERRNO_NOSYS` if absent),
  `fd_write(1, …)` → host's `Console.write_line`, `fd_write(2, …)` →
  same.
- Stub everything else as `ERRNO_NOSYS`.
- Build a "hello, wasi" smoke binary in two languages:
  Rust `cargo build --target wasm32-wasip1` and C
  `clang --target=wasm32-wasi`. Both print `Hello from WASI on capOS`,
  exit cleanly.
- Smokes: `make run-wasi-hello-rust`, `make run-wasi-hello-c`.

**Deliverable:** the first non-Rust-native userspace code paths land
on capOS. C runs on capOS without any `libcapos`/POSIX work in tree.

### Phase W.3 — Per-instance CapSet plumbing + LaunchParameters

- Define how the host adapter receives per-instance grants from the
  manifest. Likely shape: extend `system.cue` so a wasm host entry
  lists `{ binary = "thing.wasm", caps = [...] }`. Each cap grant
  becomes an entry in the per-instance preopen / argv table.
- Cross-cutting dependency on a future capOS `LaunchParameters` surface
  — argv passing for wasm needs the same plumbing as native argv. Until
  that surface exists, ship a smaller workaround: wasm modules read
  argv from a bounded text grant in the host adapter manifest.
- Smoke: a wasm module reads `argv[1]` and prints it back.

**Deliverable:** per-instance CapSet selection works.

### Phase W.4 — WASI Preview 1 random + clocks production-ready

- Wire the kernel `EntropySource` cap (the in-tree CSPRNG capability;
  see `EntropySourceClient` and `KernelCapSource::EntropySource`) through
  the host adapter as the backing for `random_get`. The same cap is the
  natural future analogue of the browser's `crypto.getRandomValues`
  surface.
- Wall-clock support deferred until capOS has a wall-clock cap.

**Deliverable:** every Preview 1 call that does not need filesystem
or sockets is implemented honestly.

### Phase W.5 — WASI Preview 1 filesystem (gated on Namespace/File caps)

- Map preopened-dir fds to `Namespace` caps from the manifest.
- Implement `path_open`, `fd_read`, `fd_write`, `fd_seek`, `fd_close`,
  `fd_filestat_get`, `fd_prestat_get`, `fd_prestat_dir_name` against
  whatever `Namespace` / `File` / `Store` cap surface exists at the
  time.
- **Blocked** on the missing storage caps: `Namespace`, `File`, `Store`,
  and `Directory` do not yet exist in the in-tree capability set, so
  there is no host-side authority to translate a preopen fd to.
  Capabilities the host adapter can already use for stdio, time,
  entropy, terminal, and memory (`Console`, `TerminalSession`, `Timer`,
  `EntropySource`, `VirtualMemory`) are sufficient for Phases W.2–W.4
  but do not cover filesystem semantics. This phase waits on storage
  proposals (`docs/proposals/storage-and-naming-proposal.md`).

**Deliverable:** a wasm module can read and write files inside a
preopened capOS namespace.

### Phase W.6 — WASI Preview 1 sockets (gated on userspace network stack)

- `sock_send`, `sock_recv`, etc. against `TcpSocket` / `UdpSocket`
  caps when the userspace network stack lands.
- Until then, an HTTP client over `Fetch` / `HttpEndpoint` is a
  reasonable shim for HTTP-only use.

**Deliverable:** a wasm module can serve HTTP requests inside a capOS
process.

### Phase W.7 — Move to wasmtime or migrate to WASI Preview 2 / Component Model

- If the runtime selected in W.0 was wasmi, decide whether to swap to
  wasmtime once `std`/futures runtime is available in capOS userspace.
- Or instead promote wasmi to wasip2 / Component Model support
  (wasmi roadmap covers components, but maturity is behind wasmtime).
- Map WIT resources to typed `OwnedCapability<T>` slots. This is the
  natural place to bridge capOS capabilities into wasm as
  **first-class typed handles**. Capability transfer between wasm
  components becomes a host-mediated resource handoff.
- Component-Model support enables cap-aware Rust crates to export
  their typed interfaces as WIT, which a Rust capOS service can
  consume the same way it consumes a capnp interface.
- Schema serial-surface coordination: this phase will likely add new
  variants under `schema/capos.capnp` for component-model resource
  bridging. Serialise with other schema-touching plans
  (`docs/plans/README.md` Concurrency Notes).

**Deliverable:** a wasm component on capOS exports a typed interface
that a native capOS process can call.

### Phase W.8 — TinyGo / Go-on-WASI integration for CUE

- Build a CUE evaluator binary against TinyGo or upstream Go's
  `GOOS=wasip1`. Run it in the host adapter against a CUE source
  blob granted as a `ScriptPackage` (future package-cap surface, same
  shape as the planned `LaunchParameters` work).
- Reuses existing CUE workflows; capOS just hosts the evaluator.

**Deliverable:** capOS can evaluate CUE manifests at runtime
without the host toolchain. Bridges to the eventual native Go track
(`go-runtime-proposal.md`).

## Languages Targeting WASI

What capOS gets "for free" once the host adapter exists, ranked by how
mature each language's WASI target is. This is the leverage argument: one
host adapter unlocks every row at once.

| Language | WASI status | Toolchain | Native capOS alternative | When WASI wins |
|---|---|---|---|---|
| **Rust** | `wasm32-wasip1` Tier 2 since 1.78; `wasm32-wasip2` Tier 2 since 1.82 | `cargo build --target wasm32-wasip2` | `targets/x86_64-unknown-capos.json` (implemented) | Untrusted Rust plugins. Cross-compiled tools. |
| **C / C++** | wasi-libc + Clang `--target=wasm32-wasi`; wasi-sdk packaged | `clang --target=wasm32-wasi` | future `libcapos` | Any C/C++ tool needing portability before `libcapos` lands. CPython-on-WASI today is the canonical example. |
| **Go (upstream)** | `GOOS=wasip1` since Go 1.21 (Aug 2023). Single-thread, blocking I/O, no goroutine parallelism. | `GOOS=wasip1 GOARCH=wasm go build` | future `GOOS=capos` (`go-runtime-proposal.md`) | CUE evaluation, `go run` style tools, single-goroutine compute. |
| **TinyGo** | wasip1 supported; wasip2 supported in dev branch | `tinygo build -target=wasip2` | n/a | Smaller Go binaries; Component Model export of typed interfaces. |
| **Python (CPython)** | `wasm32-unknown-wasip1` Tier 2 (PEP 11) | Upstream CPython build | future native CPython through POSIX adapter | Sandboxed Python plugins, configuration scripts. |
| **AssemblyScript** | Designed for wasm; WASI host integration via runtime | `asc` | n/a | Lightweight typed scripting. Less interesting on capOS than Lua. |
| **Zig** | Native wasm32-wasi target; no runtime overhead | `zig build-exe -target wasm32-wasi` | n/a | Zig systems code in a sandbox. |
| **Lua / interpreters in general** | A Lua interpreter compiled to wasi runs Lua scripts in a wasm sandbox | Compile any C interpreter to `wasm32-wasi` | Lua piccolo runner (`lua-scripting-proposal.md`) | When Lua scripts are *untrusted*. The piccolo native-Rust runner remains the right answer for *trusted* capOS scripting. |
| **JavaScript** | QuickJS-on-wasi works today | Compile QuickJS to `wasm32-wasi` | QuickJS native runner (future) | Untrusted JS plugins; portable JS without writing a native QuickJS runtime. |
| **.NET (mono-wasi)** | Experimental | dotnet wasi-experimental | n/a | If a port of a .NET tool is required. Low priority. |

## When WASI vs Native

These are complementary tracks, not competitors.

- **Native wins** for foundational services, performance-critical code,
  anything calling typed capOS caps directly, anything needing real
  threads, full async I/O, or first-class participation in the cap
  graph.
- **WASI wins** for portability or untrusted code execution, for any
  existing C/C++ program with wasi-libc support that cannot wait for
  `libcapos`, for CPU-bound CUE evaluation before native Go lands, and
  for sandboxed user-submitted scripts.

The browser-wasm proposal captures the same intuition: the cap-ring
layer is the only stable interface that survives substrate swaps. The
WASI host adapter is another substrate swap, this time at the language
level instead of the hardware level.

## Validation

The first implementation is not complete until it has QEMU evidence:

- A wasm module prints through a granted `Console` / `TerminalSession`.
- The same module cannot use `fd_write` to a fd it was not granted, cannot
  open a path outside its preopened namespaces, and cannot call an
  unimplemented WASI function without receiving `ERRNO_NOSYS`.
- A missing or wrong-interface cap lookup returns the appropriate WASI
  errno (not a host-side panic, not silent success).
- An owned result cap is released deterministically when the instance
  exits.
- The host adapter exits cleanly and does not wedge the kernel.

Host tests should cover WASI value conversion and import-resolver
generation once those pieces are pure enough to test outside QEMU. Do not
claim "WASI works" from host tests alone; the useful behavior is
authority-shaped wasm execution in capOS.

## Open Questions

1. **Per-instance vs per-process.** One wasm instance per `capos-wasm`
   process (recommended) or many? Affects fuel/budget enforcement and the
   manifest shape. **Working answer:** one per process for v0; revisit
   when instance count matters.

2. **Capability handle path: extension import or pure WASI-only?**
   Custom externref imports lock capOS into a non-portable WIT dialect.
   **Working answer:** skip the custom-import path entirely; jump
   straight to Preview 2 / Component Model in Phase W.7.

3. **`poll_oneoff` semantics over the capOS ring.** Block the host
   process's `cap_enter` (simple, scales to one instance per process), or
   run a single-thread reactor that drives multiple instances in
   round-robin (scales to many instances per process)? Coupled to Q1.
   **Working answer:** block synchronously in v0. Revisit reactors when
   multi-instance hosting becomes a goal.

4. **Fuel budget defaults and exhaustion semantics.** wasmi exposes
   fuel; what is the default budget per instance, and what is the
   exhaustion behaviour (instance traps and exits, or instance pauses
   pending refill from a `FuelGrant` cap)? Affects the cap surface.
   **Working answer:** trap-and-exit default; defer the `FuelGrant`
   cap until long-running plugins exist.

5. **Typed result-cap from a host call into a wasm module.** Preview 1
   has no `externref`. How does the host hand a typed cap back to the
   instance after a CALL that returns a transferred result cap?
   **Working answer:** v0 reifies result caps as integer fds in the
   per-instance fd table; the host returns fd numbers from
   capability-issuing imports. Defer typed caps in wasm imports to
   Preview 2 / Component Model in Phase W.7, where WIT resources match
   the shape directly.

6. **`environ_get` source.** Empty-by-default, or backed by a
   `KeyValueScope` / `ConfigOverlay` cap? **Working answer:** empty for
   v0; bind to whatever environment cap a future capOS
   `LaunchParameters` surface produces (no in-tree plan owns this yet;
   the shell proposal sketches the broader launch-args/environment
   discussion).

7. **`args_get` source.** Reuse a future capOS `LaunchParameters`
   surface (not yet in tree), or ship a wasm-host-specific text grant
   in the manifest until that surface lands? **Working answer:** ship a
   small bounded text grant for v0; migrate to the future
   `LaunchParameters` surface once it exists.

8. **Vendoring posture for wasmi.** `vendor/wasmi-no_std/` (forked,
   patched) or a `cargo-vendor`-style mirror of upstream
   `default-features = false`? Same question as the piccolo Lua track.
   **Working answer:** match whatever the Lua track picks; the chosen
   posture must remain correct (`no_std + alloc` userspace, no host
   side-effects), and consistency across the two tracks is preferred
   over a WASI-only bespoke layout.

9. **WASI module distribution and versioning.** Shipped inline in a
   manifest blob (today), or via a future `Store`/`Namespace`?
   **Working answer:** inline blobs for v0; revisit after the storage
   proposals land.

10. **Component-Model adoption timeline.** Skip Preview 1 entirely and
    target Preview 2 from day one? Possible with wasmtime, harder with
    wasmi today. **Working answer:** ship Preview 1 first because it
    unlocks Rust, C, Go, Python, TinyGo immediately; layer Preview 2 on
    once wasmi's component support hardens or migrate to wasmtime.

11. **Out-of-tree wasm packaging.** Will capOS ship pre-built `.wasm`
    binaries from the boot manifest only, or will operators bring their
    own? Same scoping question as the future `LaunchParameters` /
    package-cap surfaces. **Working answer:** in-tree only for v0–v6;
    out-of-tree once a `Store` cap can hold blobs.

12. **Audit cap shape for wasm instance lifecycle events.** Same open
    question as Lua scripting Phase 4. Component-Model paths benefit
    from per-instance audit because resource handoffs are interesting
    events to record. **Working answer:** defer until the userspace
    audit cap surface exists.

## Relationship to Other Proposals

- **[Userspace Binaries](userspace-binaries-proposal.md)** owns the
  broader native-binary, language, and POSIX-adapter roadmap. This
  proposal supersedes Part 5 of that proposal with the full WASI host
  adapter design.
- **[Programming Languages](../programming-languages.md)** is the
  reader-facing summary of language support; the WASI row points at
  this proposal.
- **[Browser/WASM](browser-wasm-proposal.md)** is the separate
  browser-hosted wasm experiment. Both proposals share wasm-runtime
  insight but target different substrates.
- **[Lua Scripting](lua-scripting-proposal.md)** is the trusted
  capability-scoped script runner using a native (likely piccolo) Lua
  VM. WASI-hosted Lua is the untrusted alternative.
- **[Go Runtime](go-runtime-proposal.md)** is the native `GOOS=capos`
  alternative to Go-on-WASI. Go-on-WASI is the v0 path for CUE
  evaluation; native Go is the path for full Go runtime semantics.
- **[Storage and Naming](storage-and-naming-proposal.md)** defines the
  `Directory` / `File` / `Store` / `Namespace` surfaces that Phase W.5
  consumes.
- **[Networking](networking-proposal.md)** defines the
  `TcpSocket` / `UdpSocket` surfaces that Phase W.6 consumes.
- **[Service Architecture](service-architecture-proposal.md)** defines
  `Fetch` / `HttpEndpoint`, useful as the v0 networking shim before the
  full userspace network stack lands.
