Skip to content

Contract map

A contract here is a shared value shape or port that another source module interprets: domain enums, execution requests and results, and transport interfaces. Opaque service handles and simple re-exports are not included in the detailed inventory. The declaration boundaries are the domain, protocol, and application layers. crates/microvms-domain/src/error.rs:167, crates/protocol/src/exec.rs:219, crates/microvms-app/src/control/transport.rs:386.

Sections are ordered by distinct directly consuming production source paths found in the current tree, with related wire shapes grouped together. Test fixtures, fuzz harnesses, and re-export-only references do not contribute to ordering. Consumer citations identify imports or use sites; assumptions cite the code that interprets the shape. The source paths identify the owning layer even when adapters import the public re-exports. crates/microvms-core/src/lib.rs:97-100, crates/microvms-app/src/session/mod.rs:51-62.

Producer: crates/microvms-domain/src/error.rs:166-201

Consumer(s):

Shape:

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ErrorKind {
/// No handler claimed this — a bug in this crate, not the platform.
Unexpected,
/// Refused locally, before any AWS call. Every trap closure lands here.
InvalidArg,
/// Transient. Run the identical request again.
Retryable,
/// An identity is wrong or absent; waiting will not fix it.
Credentials,
/// The daemon rejected the request on its merits.
Protocol,
/// The image build was never scheduled — the `clientToken` replay signature.
BuildWedged,
/// The MicroVM reached a terminal state before RUNNING; read `stateReason`.
LaunchDied,
/// The MicroVM ended before the resume (the suspended window, the maximum duration, or a
/// terminate), and it can't be resumed; launch a new one. The service refused the resume,
/// and `GetMicrovm` reports the VM TERMINATING or TERMINATED.
WindowClosed,
/// A control-plane failure with no more specific class.
Platform,
/// A client-side deadline elapsed. The VM and the exec are untouched.
Timeout,
/// Interrupted after launch; teardown ran and any leak is named in the payload.
Interrupted,
/// A prerequisite is missing.
Precondition,
/// The sandbox worked and the command in it exited non-zero.
///
/// Its own class because it is the one failure that means nothing is wrong with
/// the platform, the credentials, or this client — a CI caller needs to tell
/// "your tests failed" from "we never got a VM", and one shared class cannot say
/// both.
ExecFailed,
}

Assumptions consumers make:

Drift risk: A variant change affects exhaustive adapters, while a catalog change affects enumeration independently. crates/microvms-cli/src/exit.rs:172-187, bindings/microvms-js/src/errors.rs:198-202. Mitigation: review the enum, ALL, code strings, and adapter mappings together. crates/microvms-domain/src/error.rs:203-253.

Producer: crates/microvms-domain/src/region.rs:56-151

Consumer(s):

Shape:

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Region {
#[deprecated(since = "1.3.0", note = "use `Region::US_EAST_1`; ...")]
UsEast1,
// UsEast2, UsWest2, EuWest1 and ApNortheast1, each deprecated the same way
/// Any region outside the five above, by name.
#[deprecated(since = "1.3.0", note = "parse the name ... and read it with `as_str`; ...")]
Unlisted(String),
}
impl Region {
pub const US_EAST_1: Region = Region::UsEast1;
// US_EAST_2, US_WEST_2, EU_WEST_1 and AP_NORTHEAST_1
}
/// The regions AWS was measured running MicroVMs in: the 14 the Pricing API priced on
/// 2026-10-24, in the order AWS launched them.
pub const MEASURED_REGIONS: [&str; 14] = [/* "us-east-1", ... "eu-south-2" */];

Assumptions consumers make:

Drift risk: MEASURED_REGIONS, the drift script’s PINNED_REGIONS, and the live-rates script’s MICROVM_REGIONS change together when AWS launches a region. A stale list refuses no region. It makes a null-message denial there cite the region as a cause, and it puts the wrong regions in the live-rates script’s empty-catalog message. crates/microvms-domain/src/region.rs:121-136, tools/check-model-drift.py:201-221, tools/check-live-rates.py:87-102. Mitigation: tools/check-model-drift.py holds the emitted list equal to its pinned literal, and holds the deprecated MICROVM_REGIONS to the five. No check compares the list with AWS, so growing it follows a fresh Pricing API measurement recorded in docs/PLATFORM.md. tools/check-model-drift.py:977-997.

WireKind: specific daemon and transport outcomes

Section titled “WireKind: specific daemon and transport outcomes”

Producer: crates/microvms-domain/src/error.rs:269-326

Consumer(s):

Shape:

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WireKind {
/// 401: the presented bearer token is not the installed one. Fatal.
Unauthorized,
/// 400: malformed body, missing query key, bad mode, refused tar member.
///
/// Never 404. A 404 here reads as a missing file, which is exactly how one
/// defect hid for a review round.
ProtocolError,
/// 404: a genuinely absent exec id, file, or directory.
NotFound,
/// 409: well-formed, but the target is in the wrong state.
Conflict,
/// 410: stdin already saw EOF, or the child stopped reading.
///
/// Distinct from [`WireKind::Conflict`], which is "you did not ask for stdin"
/// and is fixed at start time. This one is a lifecycle fact.
StdinClosed,
/// 413: over a configured cap (body, tar members or bytes, stdin write).
TooLarge,
/// 408: the child did not drain its stdin pipe within the write timeout.
///
/// Retryable, and the daemon deliberately keeps its stdin handle open so a
/// retry can succeed. Some bytes may already have landed; reconciling that is
/// the caller's problem, which is why it is a distinct variant.
RequestTimeout,
/// 503: the run hook has not landed, so the control API is closed.
///
/// Not 404 and not a dropped connection. Retry: the platform is about to
/// deliver the token.
NotBootstrapped,
/// 5xx other than 503 and 507: spawn failure, io failure, a panicking task.
ServerError,
/// 507: a write would take the target filesystem under the daemon's disk reserve.
///
/// Not retryable, and that's why the daemon answers 507 rather than 500
/// (`crates/agentd/src/fs.rs`): an identical retry against a full disk fails the
/// same way, and a retry loop keyed on a 5xx would repeat it. The body names the
/// free and reserved bytes. The remedy is freeing space, then retrying.
InsufficientStorage,
/// The request never produced a status: connection refused, reset, timeout.
///
/// Retryable because it says nothing about the daemon's state. A VM that has
/// just reached RUNNING commonly refuses a connection or two before the proxy
/// path is wired up.
Transport,
/// `CreateMicrovmAuthToken` failed.
///
/// Retryable, and load-bearing rather than optimistic: proxy tokens expire at
/// 60 minutes, so a long run *will* mint mid-flight, and a throttle at that
/// moment must not kill a trial that is otherwise healthy.
AuthTokenMint,
/// A client-side wait or stream deadline elapsed. The exec is untouched.
ExecTimeout,
/// Output bytes are gone for good — the replay ring evicted them, or this
/// subscriber lagged the live channel.
OutputGap,
}

Assumptions consumers make:

Drift risk: Changing a status mapping can alter absence handling and retry decisions without changing request signatures. crates/microvms-app/src/session/files.rs:119-124, crates/microvms-app/src/session/complete.rs:239-256. Mitigation: review consumers that branch on the affected variant alongside its mapping. crates/microvms-domain/src/error.rs:373-438.

Producer: crates/microvms-domain/src/sizing.rs:118-125

Consumer(s):

Shape:

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SizeClass {
Mib512,
Mib1024,
Mib2048,
Mib4096,
Mib8192,
}

Assumptions consumers make:

Drift risk: Changing the baseline meaning would change pricing and image identity for existing variants. crates/microvms-domain/src/cost.rs:1586-1605, crates/microvms-app/src/control/ensure.rs:166-184. Mitigation: keep the resource table, conversion methods, and adapter mappings consistent. crates/microvms-domain/src/sizing.rs:139-155, crates/microvms-cli/src/cli.rs:463-479.

Producer: crates/protocol/src/exec.rs:217-299

Consumer(s):

Shape: Source documentation comments omitted.

#[derive(Debug, Deserialize, JsonSchema, Serialize)]
#[non_exhaustive]
pub struct StartRequest {
pub exec_id: String,
pub command: Vec<String>,
#[serde(default)]
pub shell: Shell,
#[serde(default)]
pub cwd: Option<String>,
#[serde(default)]
pub env: HashMap<String, String>,
#[serde(default)]
pub user: Option<NameOrId>,
#[serde(default)]
pub group: Option<NameOrId>,
#[serde(default)]
pub timeout_sec: Option<f64>,
#[serde(default)]
pub stdin: bool,
#[serde(default)]
pub reap_group_on_exit: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub poweroff_on_exit: bool,
#[serde(default)]
pub inherit_image_env: bool,
}

Assumptions consumers make:

Drift risk: A new field needs consistent constructor defaults, adapter builders, and daemon interpretation. crates/protocol/src/exec.rs:505-551, bindings/microvms-py/src/session.rs:807-832. Mitigation: review the request declaration and all construction paths together, then regenerate the wire schema. crates/agentd/src/routes.rs:601.

Producer: crates/microvms-app/src/session/exec.rs:72-83

Consumer(s):

Shape:

#[derive(Debug)]
pub struct ExecResult {
pub exec_id: String,
pub phase: protocol::exec::Phase,
/// `None` while running. Present once the child has exited.
pub outcome: Option<protocol::exec::Outcome>,
/// What the client did when its own deadline expired, for a result
/// [`super::complete::CompletionPlan::drive`] returned after one (BIND-9, BIND-10).
/// `None` for every other result: a poll, a wait, an ack, and a run that finished in
/// time.
pub client_deadline: Option<super::complete::ClientDeadline>,
}

Assumptions consumers make:

Drift risk: A new completion condition can be lost if an adapter renders only raw exit status. crates/microvms-cli/src/commands/attached.rs:706-737. Mitigation: preserve both the outcome and application-derived status accessors in each adapter. bindings/microvms-js/src/exec.rs:102-126.

Producer: crates/microvms-app/src/session/sse.rs:240-257

Consumer(s):

Shape:

#[derive(Debug)]
pub enum ExecEvent {
/// Output bytes, with the offset they start at.
Output {
stream: protocol::exec::StreamKind,
offset: u64,
data: Vec<u8>,
},
/// A byte range that is gone for good.
///
/// Surfaced as a typed event rather than logged, because the alternative is reading
/// a truncated log as a complete one. `from` is inclusive and `to` exclusive, so
/// `to` is where a cursor resumes.
Gap { from: u64, to: u64 },
/// The terminal event. Its absence is what distinguishes a cut connection from a
/// finished command — the byte sequences are otherwise identical.
Exit(protocol::exec::ExitEvent),
}

Assumptions consumers make:

Drift risk: A new variant requires every exhaustive stream consumer to decide how it affects delivery and completion. crates/microvms-app/src/session/split.rs:196-240, bindings/microvms-js/src/exec.rs:192-224. Mitigation: review cursor advancement, gap handling, and adapter conversion together. crates/microvms-app/src/session/exec.rs:655-687.

PollResponse and Outcome: retained execution output

Section titled “PollResponse and Outcome: retained execution output”

Producer: crates/protocol/src/exec.rs:431-438, crates/protocol/src/exec.rs:57-78

Consumer(s):

Shape:

#[derive(Debug, Deserialize, JsonSchema, Serialize)]
pub struct PollResponse {
pub exec_id: String,
pub phase: Phase,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
pub result: Option<Outcome>,
}
#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)]
pub struct Outcome {
/// Exit code, or `None` when the child died to a signal.
pub exit_code: Option<i32>,
/// Signal number that killed the child, when one did.
pub signal: Option<i32>,
/// The daemon execution deadline fired. Absent on older daemons, which cannot
/// distinguish deadline expiry from other signal deaths.
#[serde(default)]
pub timed_out: bool,
pub stdout: String,
pub stderr: String,
/// Set when either stream hit `max_output_bytes` and was cut. An explicit
/// flag rather than a sentinel string in the output: a marker inside the
/// bytes is indistinguishable from output that happens to contain it.
pub truncated: bool,
/// Set when the post-exit linger deadline expired with the pipes still open,
/// meaning some grandchild is alive and may write more that nobody will see.
/// Reported rather than hidden, because a harness that sees empty output from
/// a command it knows produced some needs to be able to tell why.
pub writers_may_be_alive: bool,
}

Assumptions consumers make:

Drift risk: Adding an outcome field changes the flattened poll payload as well as the terminal outcome shape. crates/protocol/src/exec.rs:431-438. Mitigation: preserve defaults for additive wire fields and review both schema and result conversion. crates/protocol/src/exec.rs:63-66, crates/microvms-app/src/session/exec.rs:85-93.

Producer: crates/microvms-app/src/control/transport.rs:386-415

Consumer(s):

Shape:

pub trait Transport: Send + Sync {
/// The service region, when known, for diagnostics about ambiguous denials.
/// Custom transports may leave this unknown without changing their behavior.
fn region(&self) -> Option<&Region> {
None
}
/// Performs `call`, returning whatever the far side answered.
///
/// A transport-level failure (no status at all) is an `Err`; a 4xx or 5xx is an
/// `Ok(Reply)` carrying the status, because classifying a status is
/// [`classify_failure`]'s job and a transport that classified would be a second
/// place the taxonomy lives.
fn send(
&self,
call: Call,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Reply, Error>> + Send + '_>>;
/// Resolves the credentials the next [`Transport::send`] would sign with, sending nothing
/// to the service (BIND-15's credentials check).
///
/// Resolving may reach an SSO, credential-process, or instance-metadata source, none of
/// which bills. The default answers `Ok`: a transport that does not sign (every scripted
/// one) has nothing to resolve.
fn resolve_credentials(
&self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Error>> + Send + '_>> {
Box::pin(async { Ok(()) })
}
}

Assumptions consumers make:

Drift risk: A transport that classifies HTTP failures itself can bypass the application retry policy. crates/microvms-app/src/control/transport.rs:386-402, crates/microvms-app/src/control/transport.rs:138-158. Mitigation: return raw replies for HTTP responses and reserve transport errors for failures without a response. crates/microvms-app/src/control/transport.rs:393-398.

Producer: crates/microvms-app/src/session/http.rs:156-177

Consumer(s):

Shape:

pub trait HttpBackend: Send + Sync {
/// Sends one request and collects the whole body, whatever the status.
///
/// Deliberately does not fail on a status: a conformance suite asserts on 401 and
/// 409 as expected outcomes, and a client that could only reach them through an
/// error would be a client that cannot test the protocol.
fn send(&self, request: HttpRequest) -> BoxFuture<'_, Result<HttpResponse, Error>>;
/// Opens a streaming response, reading the status before any body byte.
///
/// Status first so a 404 on an unknown exec id surfaces as `NotFound` rather than
/// as an empty stream. `idle_timeout` bounds *silence* rather than duration: an SSE
/// body is idle by design between chunks, so the useful bound is how long a gap may
/// last. Without one, a half-open connection — the failure a NAT or proxy timeout
/// produces, where no FIN ever arrives — hangs forever and the reconnect logic
/// never runs.
fn open_stream(
&self,
request: HttpRequest,
idle_timeout: Duration,
) -> BoxFuture<'_, Result<OpenStream, Error>>;
}

Assumptions consumers make:

Drift risk: Replacing idle timeouts with total-duration timeouts can cut off an active stream. crates/microvms-edges/src/session/http.rs:140-179, crates/microvms-edges/src/session/http.rs:249-277. Mitigation: retain separate collected-response and streaming implementations with the status available before stream consumption. crates/microvms-app/src/session/http.rs:153-176.

These pages cite many of the same source files as this one.