`harmos-guest`
Purpose
Section titled “Purpose”harmos-guest owns the Component Model WIT world, guest lifecycle bindings,
capability wrappers, static declaration parser, and the host-side
harmos-component builder.
The root harmos crate exposes direct loading only behind its guest feature.
Boundary
Section titled “Boundary”#[harmos::guest]embeds artifact identity, the Cargo package version, and typed config name. Description defaults to Cargo; an explicit contextual description is optional.#[harmos::requires]embeds artifact dependencies.- capability attributes embed the edges a guest declares.
Declaration::inspectreads final component bytes without compiling or instantiating Wasm.Plugin::registercontributes typed static registrations before initialization.#[harmos::guest(routes(echo, ...))]composes dispatch and its complete static route inventory from one list. An omitted list explicitly declares an empty table.harmos::guest::Hostoffers application capabilities, compiles a valid component, collects registrations, and initializes it under a fuel budget.
The guest receives no Runtime, Journal, Streams, filesystem, or socket.
Every effect crosses the WIT boundary and is revalidated by the host.
Routes
Section titled “Routes”A route is a Rust type declared once and named by both ends: the method, the protobuf payload, the protobuf reply, and the bound one call is held to. The same declaration serves a sidecar, so a tenant moves between facilities without its callers being rewritten.
A self-contained handler exports its typed descriptor and composition function:
#[harmos::route(id = "text.echo", deadline_ms = 250)]pub fn echo(request: String) -> Result<String, harmos::guest::RouteRefusal> { Ok(request)}
#[harmos::guest( id = "text", description = "Echoes text.", config = Settings, routes(echo),)]struct Text;The lifecycle remains in impl Guest for Text; no second route list belongs
there. The host calls guest.call::<echo::Route>(&request).await?.
Declaration::inspect reads a complete inventory qualified by guest and the
artifact id. It ignores other owners' lists and unowned linked route fragments;
a missing or repeated matching inventory is refused. Inspection runs no code.
These bytes are the artifact's declaration, not proof of arbitrary executable
behavior. Runtime admission may compare actual served declarations separately.
Low-level kits implement RouteTable and emit a complete manifest using
harmos::guest::inventory::{RouteEntry, encoded_len, encode}. Native SDKs use the
same helpers under harmos::sidecar::inventory, with facility sidecar and the
final executable identity. RouteEntry::of::<Descriptor>() reads the method and
deadline at compile time. Generate both dispatch and this manifest from the same
composition; a descriptor linked into a library alone claims no served route.
Every route call crosses the world's one answer export, which every guest
carries whether or not it serves routes — a world is one shape. Declaring a
route is therefore a source change in one crate rather than a world change, and
the world's hash does not move when one is added.
Handlers refuse with RouteRefusal, the envelope both facilities speak: a
numeric code a caller branches on and a message a caller shows. Methods under
harmos. belong to the facility and are refused at the site that registers one
and again at the site that calls one.
Bounds and failure
Section titled “Bounds and failure”A route call is bounded twice, and the two bounds answer different questions. Fuel bounds computation and is refilled for each call, so no guest draws one lifetime budget down across its whole life. An epoch deadline derived from the route's declared bound bounds time, which fuel cannot: a guest suspended inside a host call burns no fuel at all. No number converts one into the other.
A call that ends badly ends in exactly one named way — the bound elapsed, the fuel ran out, or the guest trapped. Each discards the instance, and every later call is refused without reaching the guest. Nothing is re-run: a route that changed application state before failing changed it once.
Build the guest as a wasm32-unknown-unknown core module, then run
harmos-component <core.wasm> <guest.wasm>. The component encoder and the
Wasmtime version that consumes it resolve from the same workspace lockfile.
Example
Section titled “Example”mise run run:minischematic:guest builds and runs Autoroute. The host inspects
metadata before compilation, offers net.rename, observes declared progress,
finalizes, and reopens the journal without the guest.
The direct host implements every WIT edge. Invoke enters the application journal;
record and recall use a durable guest-local namespace; publish collects declared
guest output; survey and subscription require the guest to have declared the
edge, which is a wiring check rather than a permission. Egress also requires an
application-owned adapter registered with Host::egress, which receives only
requests already bounded and proven to stay inside the declared endpoint
family.