--target wasip2 (Component Model)

August 20, 2026 · View on GitHub

Status: Phase 1 of 0.18 "Span". This document is the contract. Anything not on this page is out of scope until the contract is updated and a decision block in decisions/architecture.av says otherwise.

--target wasip2 produces a WebAssembly Component (.component.wasm plus a sibling .wit) that imports WASI 0.2 worlds directly — no preview-1 adapter, no compatibility bridge. Aver effects lower to WIT/WASI imports; Aver values stay private inside the core module; WIT/canonical ABI is the only thing the host sees. The component is intended for WASI 0.2 Component Model hosts such as wasmtime, Spin, NGINX Unit, wasmCloud, and Fermyon Cloud. Exact support depends on the world and interfaces used by the generated WIT — Component Model alone is not sufficient; the host also has to provide the specific interfaces the world declares.

This is not a general "export Aver as a WIT library" feature. In 0.18 the only public export shape is wasi:cli/run (the entry function the wasi:cli/command world requires). Arbitrary Aver functions are not exported as WIT interfaces. The component is something a host runs, not a typed library other components link against.

Two targets, two jobs

TargetJobHosts
--target wasm-gcPortable core wasm with engine GC + tail calls. Self-contained binary, host wires aver/* imports.Browsers (Chrome 119+, Firefox 120+, Safari 18.2+), Cloudflare Workers (via --preset cloudflare --handler <fn>), Node 22+, Deno, Bun, embedded wasmtime
--target wasip2WASI 0.2 component whose public import/export surface is described by WIT. The wasm-gc emitter produces core imports/exports in canonical-ABI-compatible shapes; component-type metadata declares which WIT world they correspond to; ComponentEncoder builds the actual component boundary from the two.wasmtime, Spin 3.x, NGINX Unit, wasmCloud, Fermyon Cloud — any host that takes a .component.wasm AND provides the world's interfaces

Cloudflare Workers and browsers do not run components natively; they stay on --target wasm-gc. --preset cloudflare is a wasm-gc preset and stays that way.

Why no preview-1 adapter

The preview-1 component adapter is the right tool for migrating existing preview-1 wasm modules into the Component Model — it preserves their original ABI and translates calls at the boundary. Aver does not need that. Aver effects are typed and declared in source (! [Console.print, Time.unixMs]); there is no preview-1 ABI to preserve. Routing through the adapter would just replicate one compatibility shim with another. Direct WIT lowering is the natural shape for a language whose effects already declare the host capabilities the component imports.

Architecture

Aver effect call site
  └─► Aver-side glue
        Aver value (GC string / list / record / variant / option / result)
        is marshalled into the canonical ABI's core boundary representation:
        ptr+len, retptr, handle (i32), tag+payload, …

core wasm import / export
  └─► plain core wasm types and signatures
        i32 / i64 / f32 / f64 / refs as applicable
        canonical-ABI-compatible shapes

component-type metadata
  └─► describes which WIT world / interface those core signatures
        correspond to. Embedded as a `component-type:<world>` custom
        section in the core module via `wit-encoder`.

ComponentEncoder
  └─► builds the actual component boundary from core module + metadata.
        Produces `.component.wasm`. The host sees the WIT view; it never
        sees Aver's runtime object layout.

The wasm-gc emitter does not implement the Component Model boundary. It emits core imports/exports in canonical-ABI-compatible shapes and inserts Aver-side glue at effect call sites. Component-type metadata says what those core signatures mean in WIT. wit_component::ComponentEncoder does the actual lifting/lowering at component build time.

Component contract

Eight properties every --target wasip2 build must satisfy:

  1. Imports are source-declared boundaries only. Every WIT import in the component is justified by at least one declared Aver effect (! [...]) or a required operation of a loaded capability contract. Every declared standard effect either lowers to one or more WIT imports in the selected world or is rejected at compile time. A single Aver call may translate into several WIT calls in the generated glue (e.g., Console.print cache + stream write); a single WIT interface may serve many Aver effects (e.g., wasi:io/streams for both stdout writes and stdin reads). No silent capability creep, no host hooks beyond what the source asks for.
  2. Exports are the handler shape only. A program with a main function exports wasi:cli/run; a program compiled with --world wasi:http/proxy (Phase 3 / 0.19) exports wasi:http/incoming-handler. No internal Aver functions, types, or runtime helpers leak out as public exports.
  3. All public ABI goes through WIT. Anything that crosses the component boundary uses canonical WIT types: strings, lists, records, variants, results. No Aver-specific encoding.
  4. No Aver values cross the boundary. Per-instantiation Map<K, V>, List<T>, Vector<T>, Option<T>, Result<T, E>, tuples, records, and variants stay inside the user core module. The canonical ABI for engine-GC types is still pre-proposal upstream; we do not encode anything that would break when it lands.
  5. Generated WIT is emitted next to the artifact. aver compile --target wasip2 -o out produces out/<name>.component.wasm and out/<name>.wit. The WIT is human-readable and is the source of truth for what the component imports and exports — no hidden surface in custom sections.
  6. Component validates with wasm-tools. wasm-tools validate --features component-model out/<name>.component.wasm exits zero on every artifact aver compile --target wasip2 produces. Bench scenarios and example programs are gated on this in CI.
  7. WASI resources stay implementation-internal. Stdout / stderr output-stream handles, filesystem descriptors, pollables, and similar resource handles may be cached and reused inside the per-effect glue. They are not exposed as Aver-level values. There is no Resource<T> / Handle<T> / Stream<T> type on the Aver surface in 0.18. Adding one is a deliberate language decision for 0.19+, not a side effect of WIT lowering.
  8. Filesystem access is preopen-scoped. Disk.* paths resolve only against WASI preopened directories. Absolute paths and paths that escape preopens return Result.Err("path not preopened") (a dynamic host capability gap, distinct from compile-time rejects). The Aver source-level Disk API stays unchanged; the wasip2 lowering enforces the WASI capability model at the boundary.

aver run --wasip2 file.av

Compiles the source to a wasi:cli/command component, instantiates it via embedded wasmtime, and runs the wasi:cli/run export:

  • Effects are recorded at the Aver call level, above the WIT import boundary. Recordings are interchangeable with VM, wasm-gc, and self-host traces (same recording.json shape since 0.16.1).
  • Diagnostics are Aver-shaped. Wasmtime trap messages translate through the same path that aver run --wasm-gc uses today; users see Aver source spans, not core-wasm offsets.
  • No build cache. Compile is fast enough that adding a cache layer is not worth the cache-invalidation contract.
  • --record <dir> / --replay <recording.json> are not yet wired for --wasip2 and the flags are rejected at CLI time. Recording requires a separate plumbing pass against the canonical-ABI WASI imports; until that lands, use aver run --wasm-gc --record (recordings are interchangeable across backends). The earlier sentence about effects being recorded "at the Aver call level" describes the cross-backend recording shape, not what --wasip2 itself accepts.

External hosts: wasmtime run for command components is the canonical path; wasmtime serve is the canonical local runner for the HTTP/proxy world.

Host compatibility matrix for the HTTP/proxy world

The component the wasm-gc backend emits uses the WebAssembly wasm-gc + tail-call proposals. WASI 0.2 itself is stable and supported across hosts, but those two engine proposals are still opt-in on most runtimes — pick a host that ships them enabled (or enables them via flag). Verified against the eight tests in tests/wasip2_http_server_stress.rs (echo / large body / routing / method dispatch / headers / JSON / concurrent / sequential).

HostStatusNotes
wasmtime serve 43.x✅ worksPass -W gc=y -W tail-call=y. The address binds via --addr=ip:port (e.g. --addr=127.0.0.1:8080). Bound port surfaces on stderr as Serving HTTP on http://...:N/ — useful for --addr=:0 ephemeral binds in test harnesses.
Embedded wasmtime via wasmtime-wasi-http✅ worksEnable Config::wasm_gc(true) + Config::wasm_tail_call(true) on the engine, plumb a wasmtime_wasi_http::WasiHttpCtx. Same engine as wasmtime serve under the hood.
jco serve (Bytecode Alliance) on Node ≥ 22✅ worksnpx @bytecodealliance/jco serve component.wasm --host 127.0.0.1 --port N. Transpiles the component to JS + core wasm modules and runs them on V8, which has wasm-gc + tail-call enabled since 22.0. Different engine entirely from wasmtime, so a passing run here confirms the component's portability across engine implementations — not just wasmtime variants. Node 20 rejects with Unknown type code 0x4e, enable with --experimental-wasm-gc; the flag can't be set via NODE_OPTIONS, so use Node 22+ rather than working around it.
Spin 3.5.x❌ rejected at loadBundled wasmtime does not enable the wasm-gc proposal (rec group usage requires 'gc' proposal to be enabled). No user-facing flag to override; the runtime-config TOML has no [wasmtime] table. Tracks Spin upstream — once their bundled wasmtime turns the proposal on (or exposes a flag), the same .component.wasm will run unchanged.
NGINX Unit 1.34.x (wasm-wasi-component)❌ rejected at loadSame root cause as Spin — Unit's wasm_wasi_component.unit.so module embeds wasmtime with the wasm-gc proposal off; tested via the unit:wasm Docker image. Identical error: rec group usage requires 'gc' proposal to be enabled. Lands automatically once Unit upgrades its bundled wasmtime build.
WasmEdge 0.16.x❌ component model experimental--enable-component exists but the validator is still under construction; rejects our component with Alias export: Export index 0 exceeds available component instance index 0 before the wasm-gc / tail-call question even comes up. Re-test once their component-model validator stabilises.
Wasmer 7.x❌ no component modelerror: ... encoded as a component but the WebAssembly component model feature is not enabled. Component support is not on Wasmer's near-term roadmap.
wasmCloud wash 2.x⚠️ different shapewash dev is a mesh-deployment daemon expecting a full wasmCloud project + manifest, not a standalone serve. No quick equivalent of wasmtime serve component.wasm. A wasmCloud project around our component would presumably work (their host is wasmtime-based with GC enabled in recent versions), but takes a project scaffold to verify — out of scope for this round.
Fermyon Cloud, Fastly Compute⚠️ untestedCloud-only deployments — would need an account + push. Spec-compatible against wasi:http/proxy; whether each enables wasm-gc + tail-call depends on their bundled runtime build.

The component itself is portable: the only host requirement is "WASI 0.2 wasi:http/proxy host with wasm-gc + tail-call proposals on". Future Aver work to widen host coverage waits on host updates, not codegen changes.

Running Tcp.* programs (Phase 4.2.x in flight, 0.20)

Tcp.* programs compile to the same wasi:cli/command world as the other CLI effects, but the runtime additionally needs wasi-sockets imports enabled. With wasmtime run:

wasmtime run \
    -W gc=y -W tail-call=y \
    -S inherit-network=y \
    -S allow-ip-name-lookup=y \
    -S tcp=y \
    component.wasm
FlagWhy
-W gc=y -W tail-call=yEngine proposals — same requirement as the HTTP/proxy world.
-S inherit-network=yGrants the guest access to the host's network stack (otherwise every wasi-sockets call returns the default-deny error).
-S allow-ip-name-lookup=yEnables wasi:sockets/ip-name-lookup. Required even for IP-literal hosts like "127.0.0.1" — without it resolve-addresses rejects every input.
-S tcp=yEnables wasi:sockets/tcp. Without it create-tcp-socket traps before the connect can start.

-S udp=y is intentionally not needed: Aver's Tcp.* does not touch wasi:sockets/udp. Embedded wasmtime hosts get the same capability via WasiCtxBuilder::inherit_network() + allow_ip_name_lookup(true) + socket_addr_check(...).

aver compile --target wasip2 -o out

Produces:

out/
  <name>.component.wasm    -- the component
  <name>.wit               -- generated WIT, human-readable

Flags:

  • --world <world> — which WIT world the component targets. Two values: wasi:cli/command (default — long-running process exporting wasi:cli/run.run) and wasi:http/proxy (HTTP server, exporting wasi:http/incoming-handler.handle; shipped in 0.19). The proxy world pairs with --handler <fn> (same flag the wasm-gc + Cloudflare path uses) — names the user fn with signature Fn(HttpRequest) -> HttpResponse that becomes the proxy handler. The compile path is purely flag-driven; main's body can stay portable (HttpServer.listen(port, handler) runs the same source under aver run on the VM, lowers to a no-op when wasip2 proxy codegen takes over). Programs whose effects do not fit the chosen world fail at compile time with target-effect-unsupported pointing at the offending call.
  • --optimize {size,speed}rejected on --target wasip2. Upstream wasm-opt does not yet handle wasm-gc + Component Model bytes cleanly, so the flag is refused at the CLI rather than silently dropped. Use --target wasm-gc if you need post-pass size/speed optimization; we will wire it for wasip2 once the toolchain catches up.

The compiler does not shell out. WIT emission goes through wit-encoder; component-type metadata is encoded via wit-component::metadata and embedded as a custom section in the core module; the actual component wrap goes through wit_component::ComponentEncoder. Single binary, no toolchain to install on the user's machine.

Custom capability imports (phase 3a)

Calling one operation of a program-defined capability selects its complete contract. If every declared operation parameter and result is Unit, Bool, Float, or String, aver compile --target wasip2 emits one generated WIT interface and imports it into the selected world. Pure and effectful capability modules follow the same transport path; semantics, Oracle, replay, and hostile profiles keep their existing language/proof meaning and do not change the ABI.

The boundary mapping is deliberately small and exact:

AverWIT / canonical ABI
Unitno value slot
Boolbool / flat i32
Floatf64
Stringstring; copied between Aver GC storage and canonical linear memory

Int is not narrowed to s64: Aver integers are arbitrary precision. Result, Option, tuples, lists/vectors/maps, represented records/sums, and opaque resources are also outside phase 3a. One unsupported type makes the entire contract unsupported[wit-boundary-type-unsupported]; there is no partial interface. The diagnostic names the capability, operation, exact parameter/result position, offending Aver type, contract_hash, and model_hash.

Generated interface identity contains an injective encoding of the module name and the full contract_hash. Operations are sorted and encoded deterministically; parameters are positional (p0, p1, …), so source parameter renames and declaration order do not alter WIT bytes. Both hashes appear in interface docs, but model_hash does not alter transport identity: it audits source semantics, not ABI layout. If none of a capability's operations is used, no interface is emitted. If any is used, the provider binds the full contract.

Compilation intentionally leaves this import unresolved: the artifact is host-bound[component-import-required], not provided. Install the interface implementation in a Component Model host/linker, then instantiate the component. For local execution, aver run app.av --wasip2 --providers uses the explicit schema-1 [providers] manifest and cached Rust host. It validates the full contract through the ordinary native ProviderRegistry, dynamically installs the generated WIT functions in the embedded wasmtime linker, and converts the phase-3a values to/from the same transport-neutral ProviderValue tree used by the VM and generated Rust. Provider faults, panics, and wrong return shapes keep their provider-boundary diagnostics instead of surfacing as canonical-ABI traps.

Plain aver run --wasip2 remains inert and reports error[capability-provider-missing] before linking. Aver never discovers or downloads a package implicitly. aver compile --target wasip2 also remains an unresolved, portable component; the cached Rust composition is a run shortcut, not a mutation of the artifact contract.

Effect mapping

Aver effects lower directly to WASI 0.2 imports. The mapping is fixed per effect; a single Aver call at the source level may translate into one or several WIT calls in the generated glue (e.g., a Console.print may cache the stdout output-stream resource handle once and call wasi:io/streams.[method]write per print).

Aver effectWIT import (the glue calls into)
Args.getwasi:cli/environment.get-arguments
Env.getwasi:cli/environment.get-environment
Env.setCompile-rejected — WASI 0.2 environment is read-only by design (no host can ever satisfy a write). Same "cannot-ever-support" category as Terminal.*.
Console.print / error / warnwasi:cli/stdout.get-stdout / wasi:cli/stderr.get-stderr (cached) + wasi:io/streams.output-stream.[method]blocking-write-and-flush. 0.18 uses blocking write-and-flush for command-component semantics and simple replayability — one Console.* call ⇒ at most one host-side flush, easy to record/replay deterministically. WASI output-streams are fundamentally non-blocking with a polling model; blocking-write-and-flush is a binding-level convenience helper that bundles check-write + write + flush + subscribe/poll into one call. Buffered stdout/stderr could land later as an optimisation, but the semantic unit stays the Aver Console call.
Console.readLinewasi:cli/stdin.get-stdin (cached) + wasi:io/streams.input-stream.[method]blocking-read
Disk.readText / writeText / appendText / readBytes / readBytesAt / writeBytes / appendBytes / size / exists / delete / deleteDir / listDir / makeDirwasi:filesystem/preopens.get-directories (cached) + wasi:filesystem/types.[method]*. Binary calls use raw stream octets without UTF-8 conversion. readBytesAt passes its offset to read-via-stream, grows a bounded buffer only as data arrives, and treats EOF as a successful short read; size reads descriptor metadata. Paths outside preopens return Result.Err("path not preopened") — capability model, contract point 8.
Time.now / unixMswasi:clocks/wall-clock.now (Time.now formats RFC3339 guest-side via Howard Hinnant's civil_from_days)
Time.sleepwasi:clocks/monotonic-clock.subscribe-duration + wasi:io/poll.poll + [resource-drop]pollable (per-call pollable, real wait — not busy-loop)
Random.int / floatwasi:random/random.get-random-u64 + Aver-side range scaling. This is the secure wasi:random/random interface (same contract as get-random-bytes, just returning 8 cryptographically-secure bytes packed into a u64); we deliberately do NOT use wasi:random/insecure.get-insecure-random-u64. If we later need finer byte-level control (e.g. for Random.bytes(n)), the switch to get-random-bytes is mechanical.
Http.{get, head, delete, post, put, patch}wasi:http/outgoing-handler.handle + the future-incoming-response / incoming-response choreography (Phase 2 / 0.19 shipped). Method tag selects outgoing-request.set-method. Body-bearing verbs marshal a request body via request.body + outgoing-body.write + chunked blocking-write-and-flush + outgoing-body.finish. Headers (request and response) lower as Map<String, List<String>>; multi-valued field names preserve server emit order. error-code variant discriminants surface as per-variant http: <name> Err messages (39 cases).
HttpServer.listenwasi:http/incoming-handler.handle export (Phase 3 / 0.19 shipped). Requires --world wasi:http/proxy --handler <fn>. The handler wrapper decodes the host-supplied incoming-request into an Aver HttpRequest (method via the 10-case variant, path-with-query split into path/query, headers iteration as Map<String, List<String>>, body via incoming-body.stream + drained input-stream.blocking-read), runs the user's fn(HttpRequest) -> HttpResponse, marshals the result into an outgoing-response (outgoing-response constructor + set-status-code + body via outgoing-body.write + chunked blocking-write-and-flush + outgoing-body.finish), and calls response-outparam.set. Content-Length is synthesised from the response body byte count. The port argument to HttpServer.listen in source is honoured by the VM but ignored by wasip2 codegen — the host's listener flag (wasmtime serve --addr=:N etc.) binds the socket.
HttpServer.listenWithCompile-rejected — deferred one iteration; requires per-instance wasm-global context plumbing.
Tcp.{connect, close, writeLine, writeBytes, readLine, readBytes, send, sendBytes, ping}wasi:sockets/{instance-network, ip-name-lookup, tcp-create-socket, tcp} (Phase 4 / 0.20 "Pulse" shipped, hardened through five peer-review passes). __rt_tcp_connect walks lazy-network init → resolve-addresses → async pollable loop → first-IPv4 → create-tcp-socket → start/finish-connect → pool-slot allocation via first-free scan → Tcp.Connection materialise. The 256-slot pool refuses the 257th simultaneous connect with Err("tcp: connection limit reached (256 max)") (matches aver-rt::tcp::connect's HashMap-len gate); a closed slot is reusable immediately. Tcp.close drops streams + shutdown + drops socket; subsequent close on the same handle surfaces Err("tcp: unknown connection"). writeLine/readLine thread text through chunked blocking-write / 1-byte blocking-read against the pooled streams; writeLine appends \r\n and readLine strips the trailing \r only when terminated by \n (intra-payload CR preserved). writeBytes writes nominal Bytes through the same persistent out-stream without framing or encoding, while readBytes loops blocking-read until the exact requested count and returns nominal Bytes; short reads and invalid counts are catchable errors. Any real persistent read/write failure drops both streams and the socket and marks the slot stale; argument validation does not. Stale-handle paths on the persistent methods (null pool, slot-scan miss, in_use == 0) surface Err("tcp: unknown connection") so callers can tell a closed handle apart from a real wasi-side I/O failure or peer EOF. Tcp.send is fully ephemeral — inline DNS + socket + connect (no pool slot), raw write on the wire (no \r\n appended) + shutdown(send), read-until-EOF capped at 10 MiB; stream errors split as stream-error.last-operation-failed → Err("tcp: stream error") vs stream-error.closed → Ok(buf). Tcp.sendBytes is the byte-clean ephemeral sibling — same inline dial + shutdown(send) + read-to-EOF shape, but both payload and response are nominal Bytes; invalid raw integers are rejected by Bytes.fromList before TCP is called, while the backend retains a fail-closed check for malformed internal carriers. No UTF-8 conversion happens in either direction. Tcp.ping is also ephemeral — same inline dial as send minus the read/write phase, drop streams + socket on success, return Result.Ok(()); no pool slot, so a program holding 256 live Tcp.connect handles can still ping. Tcp.Connection is a capability resource (the type checker rejects construction and field reads). IPv6 yields from the resolver are skipped (first-IPv4-wins); no in-line subscribe-duration connect timeout in v1.
Tcp.poll / Tcp.readSomepoll subscribes every candidate input stream, adds one duration pollable for timeout, maps ready dense indices back to caller-owned Map<Int, Tcp.Connection> keys, sorts them with Aver's arbitrary-precision Int order, and drops every pollable. readSome performs one bounded input-stream.blocking-read, returning empty Bytes only for clean EOF. Actual read errors poison the pool slot; argument validation and polling do not.
Terminal.* (12 methods)Compile-rejected — WASI 0.2 has no raw/cooked-mode operations

Header maps grow with the headers

Request and response headers are handed to Aver as a Map<String, List<String>>, accumulated one header at a time through the same Map.set helper every Aver map uses — so they inherit its growth: the map starts at 16 buckets and doubles as the header names arrive. A request carrying an ordinary handful of headers allocates a table sized for a handful, and no number of distinct header names is a cliff. The table used to be fixed at 16384 buckets, so a peer sending more distinct header names than that could stop the guest; that surface is gone. Whatever cap the host places on header count still applies first; nothing in the guest imposes one. See the Map size note in docs/cli.md.

Why Terminal.* / Env.set are rejected, not stubbed

The axis is static target capability vs dynamic host capability. Result.Err stubs are reserved for dynamic host capability gaps: missing preopen (Disk.readText("/etc/passwd") on a host that didn't preopen /), missing env var, denied permission. A target that cannot ever support an effect is a different category and gets a different shape — a compile-time target-effect-unsupported error.

In 0.18:

  • Terminal.* — WASI 0.2 has wasi:cli/terminal-input and terminal-output as TTY signals, but no standardised raw/cooked-mode operations (set-raw-mode, set-echo, get-window-size). The capability is structurally absent.
  • Env.set — WASI 0.2 environment is read-only. There is no host implementation that could ever satisfy a write. Silent no-op would be a trap: source declares "I set X" and the program runs as if it succeeded while the environment is unchanged.
  • HttpServer.listenWith — deferred one iteration; requires per-instance wasm-global context plumbing.

(Earlier 0.18 betas grouped Time.sleep with the structural rejects on the assumption that the pollable model was out of scope. That was a scoping mistake — pollables can be wrapped inside a single helper without leaking to source. Phase 1.4c shipped __rt_time_sleep doing exactly that, so Time.sleep lowers natively now.)

Compile output for any of these:

error[target-effect-unsupported]:
  Terminal.readKey requires raw terminal input.
  --target wasip2 does not provide Terminal effects.
  Use:
    --target wasm-gc for browser/interactive terminal hosts
    aver run on VM for local terminal programs
  Or replace Terminal.* with Console.* / Args / stdin-compatible APIs.

Phasing inside 0.18

PhaseScopeStatus
0Audit legacy coupling, wire wit-component/wit-encoder deps, prove the wrap pipeline✅ shipped
1.0 / 1.1--target wasip2 CLI plumbing, end-to-end pipeline for no-effect programs✅ shipped
1.2wasi:cli/stdout + wasi:io/streams glue. Console.print / error / warn → stream write end-to-end✅ shipped
1.3wasi:cli/stdin + wasi:cli/environment. Console.readLine / Args.get / Env.get✅ shipped
1.4wasi:clocks/wall-clock.now for Time.now / Time.unixMs; wasi:random for Random.*; wasi:clocks/monotonic-clock.subscribe-duration + wasi:io/poll.poll for Time.sleep.✅ shipped
1.5wasi:filesystem. All seven Disk.* methods (exists / readText / writeText / appendText / delete / deleteDir / makeDir / listDir). Paths resolve relative to the cached preopen.✅ shipped
1.6Reject Terminal.* / Env.set at compile time as permanent (WASI 0.2 has no terminal interface; environment is read-only). Http.* / Tcp.* / HttpServer.* deferred to 0.19+.✅ shipped
1.7aver run --wasip2 (embedded wasmtime + wasmtime-wasi) with CWD preopened as .✅ shipped
1.8Drop the legacy --target wasm backend (src/codegen/wasm/, wasm-legacy feature, --bridge flag, wasm-runtime subcommand, legacy bundling in src/main/commands.rs)✅ shipped

Out of scope for 0.18

  • Outgoing HTTP (wasi:http/outgoing-handler) — Phase 2 / 0.19. Direct WIT lowering, same mechanism as Phase 1; just more types to marshal.
  • HTTP server (wasi:http/incoming-handler / wasi:http/proxy world) — Phase 3 / 0.19 or 0.20. Different export shape (handler exposes WIT export, host calls in).
  • TCP sockets (wasi:sockets/tcp) — Phase 2 / 0.19. Open question whether Aver wants long-lived socket handles as a language concept.
  • Resources / streams / pollables on the Aver surface — implementation only in 0.18. If Aver grows a Resource<T> type, that is a deliberate language decision for 0.19+.
  • WASI 0.3 — async ABI / future<T> / stream<T> are real but not finalised. 0.2 hosts will be virtualised by 0.3 hosts per upstream commitment, so we lose nothing by waiting.
  • wasi:keyvalue, wasi:logging, wasi:config, wasi:tls, wasi:blobstore, wasi:nn — none.
  • Cross-component shared runtime — requires GC types in the canonical ABI; that proposal is upstream pre-proposal. Per-instantiation helpers stay inline.
  • jco transpile as a derived target for browsers / Node — possible 0.19+ if there is concrete demand.

References