use super::transport::{Call, paths, send_with_retry};
use super::{ControlPlane, RunMicrovmRequest, WaitOpts, ops, timed_out, token};
use crate::error::{Error, ErrorKind};
pub const PROXY_AUTH_HEADER: &str = "X-aws-proxy-auth";
pub const PROXY_PORT_HEADER: &str = "X-aws-proxy-port";
pub const MAX_TOKEN_MINUTES: u32 = 60;
#[derive(Clone, Eq, PartialEq)]
pub struct RunHookPayload(String);
impl std::fmt::Debug for RunHookPayload {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "RunHookPayload(<{} bytes>)", self.0.len())
}
}
impl RunHookPayload {
pub fn for_agent_token(agent_token: &str) -> Result<Self, Error> {
Self::for_launch(agent_token, &std::collections::HashMap::new())
}
pub fn for_launch(
agent_token: &str,
env: &std::collections::HashMap<String, String>,
) -> Result<Self, Error> {
let payload = if env.is_empty() {
serde_json::json!({ "agent_token": agent_token })
} else {
serde_json::json!({ "agent_token": agent_token, "env": env })
};
let text = serde_json::to_string(&payload).map_err(|error| {
Error::new(
ErrorKind::Unexpected,
format!("could not serialize the run-hook payload: {error}"),
)
})?;
let total = text.len();
Self::new(text).map_err(|error| {
if env.is_empty() {
return error;
}
Error::invalid_arg(format!(
"{error} The launch env contributed {} of those bytes across {} variable(s); \
the payload's other {} are the token and the JSON framing. A launch env is \
for small values a workload needs at startup — move credential-scale \
material to PUT /v1/fs/file after bootstrap, or to a role the workload \
assumes.",
env_contribution(agent_token, env),
env.len(),
total.saturating_sub(env_contribution(agent_token, env)),
))
})
}
pub fn new(payload: impl Into<String>) -> Result<Self, Error> {
let payload = payload.into();
let size = payload.len();
if size <= crate::constants::MAX_RUN_HOOK_PAYLOAD_BYTES {
return Ok(Self(payload));
}
Err(Error::invalid_arg(format!(
"runHookPayload is {size} bytes, over the ceiling of {} (service model {}, measured \
inclusive 2026-08-07: 4096 passes and 4097 fails). This is the only per-VM secret \
channel the platform offers — one bearer token fits, a cloud credential set does \
not. Note that docs/STRATEGY.md, docs/TRUST.md, and the service model's own \
documentation string for this member all claim 16 KB, which is wrong by 4x in the \
dangerous direction; the shape RunMicrovmRequestRunHookPayloadString is the \
authority and it states max 4096.",
crate::constants::MAX_RUN_HOOK_PAYLOAD_BYTES,
crate::constants::MODEL_API_VERSION,
)))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
fn env_contribution(agent_token: &str, env: &std::collections::HashMap<String, String>) -> usize {
let with = serde_json::json!({ "agent_token": agent_token, "env": env }).to_string();
let without = serde_json::json!({ "agent_token": agent_token }).to_string();
with.len().saturating_sub(without.len())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Microvm {
pub id: String,
pub state: String,
pub endpoint: String,
pub image_arn: String,
pub image_version: String,
pub state_reason: Option<String>,
pub idle_policy: Option<ops::IdlePolicy>,
}
impl Microvm {
fn reason(&self) -> &str {
self.state_reason.as_deref().unwrap_or("no stateReason")
}
}
impl From<ops::MicrovmResponseWire> for Microvm {
fn from(wire: ops::MicrovmResponseWire) -> Self {
Self {
id: wire.microvm_id,
state: wire.state,
endpoint: wire.endpoint,
image_arn: wire.image_arn,
image_version: wire.image_version,
state_reason: wire.state_reason,
idle_policy: wire.idle_policy,
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct ProxyToken {
value: String,
port: u16,
}
impl std::fmt::Debug for ProxyToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProxyToken")
.field("headers", &[PROXY_AUTH_HEADER, PROXY_PORT_HEADER])
.field("port", &self.port)
.finish()
}
}
impl ProxyToken {
pub fn from_map(
map: &std::collections::BTreeMap<String, String>,
port: u16,
) -> Result<Self, Error> {
let value = map.get(PROXY_AUTH_HEADER).ok_or_else(|| {
Error::wire(
crate::error::WireKind::AuthTokenMint,
format!(
"the CreateMicrovmAuthToken response carried no {PROXY_AUTH_HEADER} key. The \
authToken member is a map, not a string, and that key is the header value \
(keys present: {:?}).",
map.keys().collect::<Vec<_>>()
),
)
})?;
Ok(Self {
value: value.clone(),
port,
})
}
pub fn headers(&self) -> [(&'static str, String); 2] {
[
(PROXY_AUTH_HEADER, self.value.clone()),
(PROXY_PORT_HEADER, self.port.to_string()),
]
}
pub fn value(&self) -> &str {
&self.value
}
pub fn port(&self) -> u16 {
self.port
}
}
impl ControlPlane {
pub async fn run_microvm(&self, request: RunMicrovmRequest) -> Result<Microvm, Error> {
super::require_valid_identifier("imageIdentifier", &request.image_identifier)?;
super::require_duration_in_range(request.max_duration_sec)?;
super::require_idle_duration(request.max_idle_sec)?;
if let Some(version) = request.image_version.as_deref() {
super::require_valid_version("imageVersion", version)?;
}
if let Some(role) = request.execution_role_arn.as_deref() {
super::require_valid_role_arn("executionRoleArn", role)?;
}
let ingress: Vec<String> = request
.connectors
.iter()
.filter(|intent| matches!(intent, super::ConnectorIntent::AllIngress))
.map(|intent| intent.arn(&self.region))
.collect();
let egress: Vec<String> = request
.connectors
.iter()
.filter(|intent| matches!(intent, super::ConnectorIntent::Egress))
.map(|intent| intent.arn(&self.region))
.collect();
if ingress.len() + egress.len() > crate::constants::MAX_NETWORK_CONNECTORS {
return Err(Error::invalid_arg(format!(
"{} network connectors were requested, over the NetworkConnectorList ceiling of \
{} (service model {}).",
ingress.len() + egress.len(),
crate::constants::MAX_NETWORK_CONNECTORS,
crate::constants::MODEL_API_VERSION,
)));
}
let wire = ops::RunMicrovmWire {
image_identifier: request.image_identifier.clone(),
image_version: request.image_version.clone(),
execution_role_arn: request.execution_role_arn.clone(),
ingress_network_connectors: ingress,
egress_network_connectors: (!egress.is_empty()).then_some(egress),
idle_policy: ops::IdlePolicy {
max_idle_duration_seconds: request.max_idle_sec,
suspended_duration_seconds: request.suspended_sec,
auto_resume_enabled: request.auto_resume,
},
maximum_duration_in_seconds: request.max_duration_sec,
run_hook_payload: request.run_hook_payload.as_str().to_string(),
client_token: token::run_token(
request
.token_scope
.as_deref()
.unwrap_or(&request.image_identifier),
),
};
let call = Call::post_json("RunMicrovm", paths::microvms(), &wire)?;
let reply = send_with_retry(self.transport(), call).await?;
let launched: ops::MicrovmResponseWire = reply.json("RunMicrovm")?;
Ok(launched.into())
}
pub async fn wait_for_running(&self, id: &str, opts: WaitOpts) -> Result<Microvm, Error> {
self.wait_for_state(id, &["RUNNING"], &crate::constants::TERMINAL_STATES, opts)
.await
}
pub async fn wait_for_state(
&self,
id: &str,
wanted: &[&str],
fail_on: &[&str],
opts: WaitOpts,
) -> Result<Microvm, Error> {
let started = self.clock().elapsed();
loop {
let got = self.get_microvm(id).await?;
if wanted.contains(&got.state.as_str()) {
return Ok(got);
}
if fail_on.contains(&got.state.as_str()) {
return Err(self.reached_terminal_state(&got, wanted));
}
let elapsed = self.clock().elapsed().saturating_sub(started);
if elapsed >= opts.timeout {
return Err(timed_out(
&format!(
"microvm {id} never reached {wanted:?} (last state {})",
got.state
),
elapsed,
));
}
self.clock().sleep(opts.poll_interval).await;
}
}
fn reached_terminal_state(&self, got: &Microvm, wanted: &[&str]) -> Error {
Error::new(
ErrorKind::LaunchDied,
format!(
"microvm {} reached {} before {wanted:?}: {}. A VM that reaches a terminal state \
before RUNNING died during startup, and for a hook-serving daemon that almost \
always means a lifecycle hook failed — the stateReason above is the only evidence \
left, because the VM is gone (docs/PLATFORM.md, 'A MicroVM that dies during \
startup reports a connection error that hides the cause').",
got.id,
got.state,
got.reason(),
),
)
}
pub async fn get_microvm(&self, id: &str) -> Result<Microvm, Error> {
super::require_valid_identifier("microvmIdentifier", id)?;
let call = Call::get("GetMicrovm", paths::microvm(id));
let reply = send_with_retry(self.transport(), call).await?;
let got: ops::MicrovmResponseWire = reply.json("GetMicrovm")?;
Ok(got.into())
}
pub async fn list_microvms(&self) -> Result<Vec<ops::MicrovmItemWire>, Error> {
let mut items = Vec::new();
let mut next_token: Option<String> = None;
loop {
let call = Call::get("ListMicrovms", paths::microvms_list(next_token.as_deref()));
let reply = send_with_retry(self.transport(), call).await?;
let page: ops::ListMicrovmsResponseWire = reply.json("ListMicrovms")?;
items.extend(page.items);
match page.next_token {
Some(token) => next_token = Some(token),
None => return Ok(items),
}
}
}
pub async fn suspend(&self, id: &str) -> Result<(), Error> {
super::require_valid_identifier("microvmIdentifier", id)?;
let call = Call::post_empty("SuspendMicrovm", paths::suspend(id));
send_with_retry(self.transport(), call).await?;
Ok(())
}
pub async fn resume(&self, id: &str) -> Result<(), Error> {
super::require_valid_identifier("microvmIdentifier", id)?;
let call = Call::post_empty("ResumeMicrovm", paths::resume(id));
send_with_retry(self.transport(), call).await?;
Ok(())
}
pub async fn terminate(&self, id: &str) -> Result<(), Error> {
super::require_valid_identifier("microvmIdentifier", id)?;
let call = Call::delete("TerminateMicrovm", paths::microvm(id));
send_with_retry(self.transport(), call).await?;
Ok(())
}
pub async fn mint_auth_token(&self, id: &str) -> Result<ProxyToken, Error> {
self.mint_auth_token_for(id, &[ops::PortSpecification::port(self.port())])
.await
}
pub async fn mint_auth_token_for(
&self,
id: &str,
ports: &[ops::PortSpecification],
) -> Result<ProxyToken, Error> {
super::require_valid_identifier("microvmIdentifier", id)?;
if ports.is_empty() {
return Err(Error::new(
ErrorKind::Precondition,
"a proxy token must allow at least one port ('allowedPorts' declares min: 1). \
Name the ports this token is for — the agent port plus any workload port \
reached through the endpoint.",
));
}
for spec in ports {
match spec {
ops::PortSpecification::One { port } => {
super::require_valid_port("allowedPorts[].port", *port)?;
}
ops::PortSpecification::Range { range } => {
super::require_valid_port("allowedPorts[].range.startPort", range.start_port)?;
super::require_valid_port("allowedPorts[].range.endPort", range.end_port)?;
}
ops::PortSpecification::All { .. } => {}
}
}
let wire = ops::CreateAuthTokenWire {
expiration_in_minutes: MAX_TOKEN_MINUTES,
allowed_ports: ports.to_vec(),
};
let call = Call::post_json("CreateMicrovmAuthToken", paths::auth_token(id), &wire)?;
let reply = send_with_retry(self.transport(), call).await?;
let minted: ops::CreateAuthTokenResponseWire = reply.json("CreateMicrovmAuthToken")?;
ProxyToken::from_map(&minted.auth_token, self.port())
}
}
pub const SUSPEND_WANTED: [&str; 2] = ["SUSPENDED", "TERMINATED"];
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::super::fake::{self as fake, Answer, FakeControlPlane, TestClock};
use super::*;
use crate::region::Region;
fn planted() -> (ControlPlane, Arc<FakeControlPlane>, Arc<TestClock>) {
let fake = Arc::new(FakeControlPlane::new());
let clock = Arc::new(TestClock::new());
let plane = ControlPlane::with_transport(fake.clone(), Region::UsEast1, clock.clone());
(plane, fake, clock)
}
#[test]
fn the_payload_ceiling_is_four_thousand_ninety_six_bytes_inclusive() {
let at_ceiling = "a".repeat(4096);
let payload = RunHookPayload::new(at_ceiling.clone()).expect("4096 bytes fits");
assert_eq!(payload.len(), 4096);
assert_eq!(payload.as_str(), at_ceiling);
let over = "a".repeat(4097);
let error = RunHookPayload::new(over).expect_err("4097 bytes does not fit");
assert_eq!(error.kind(), ErrorKind::InvalidArg);
let message = error.to_string();
assert!(message.contains("4097 bytes"), "{message}");
assert!(
message.contains("ceiling of 4096"),
"the message must name the service-model ceiling: {message}"
);
assert!(message.contains("2025-09-09"), "{message}");
assert!(
message.contains("wrong by 4x"),
"the 16 KB claim has to be named as wrong: {message}"
);
}
#[tokio::test]
async fn an_over_ceiling_payload_reaches_no_control_plane_call() {
let (plane, fake, _) = planted();
let refused = RunHookPayload::new("a".repeat(4097));
assert!(refused.is_err());
assert_eq!(fake.calls().len(), 0);
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::new("a".repeat(4096)).expect("fits");
plane
.run_microvm(RunMicrovmRequest::new("arn:image", payload))
.await
.expect("launches");
assert_eq!(fake.call_count("RunMicrovm"), 1);
}
#[test]
fn the_ceiling_counts_utf8_bytes_rather_than_characters() {
let two_byte_chars = "é".repeat(2049);
assert_eq!(
two_byte_chars.chars().count(),
2049,
"well under 4096 chars"
);
assert_eq!(two_byte_chars.len(), 4098, "but over 4096 bytes");
let error = RunHookPayload::new(two_byte_chars).expect_err("bytes are what count");
assert!(error.to_string().contains("4098 bytes"), "{error}");
let exact = "é".repeat(2048);
assert_eq!(exact.len(), 4096);
RunHookPayload::new(exact).expect("exactly at the ceiling");
}
#[test]
fn the_agent_token_payload_is_checked_even_though_the_client_builds_it() {
let payload = RunHookPayload::for_agent_token("bearer-token-value").expect("fits");
assert_eq!(payload.as_str(), r#"{"agent_token":"bearer-token-value"}"#);
let huge = "x".repeat(4096);
let error = RunHookPayload::for_agent_token(&huge)
.expect_err("the JSON wrapper pushes it over the ceiling");
assert_eq!(error.kind(), ErrorKind::InvalidArg);
assert!(
error.to_string().contains("one bearer token fits"),
"{error}"
);
}
#[test]
fn an_empty_launch_env_emits_the_payload_the_token_only_constructor_did() {
let with_empty = RunHookPayload::for_launch("tok", &std::collections::HashMap::new())
.expect("an empty env fits");
let token_only = RunHookPayload::for_agent_token("tok").expect("fits");
assert_eq!(with_empty.as_str(), token_only.as_str());
assert_eq!(with_empty.as_str(), r#"{"agent_token":"tok"}"#);
assert!(
!with_empty.as_str().contains("env"),
"an empty env must not appear on the wire at all: {}",
with_empty.as_str()
);
}
#[test]
fn a_launch_env_rides_in_the_payload_the_daemon_parses() {
let mut env = std::collections::HashMap::new();
env.insert("ANTHROPIC_BASE_URL".to_string(), "https://x".to_string());
let payload = RunHookPayload::for_launch("tok", &env).expect("fits");
let value: serde_json::Value =
serde_json::from_str(payload.as_str()).expect("the payload is JSON");
assert_eq!(value["agent_token"], "tok");
assert_eq!(value["env"]["ANTHROPIC_BASE_URL"], "https://x");
}
#[test]
fn an_over_ceiling_launch_env_is_refused_locally_with_the_byte_count() {
let mut env = std::collections::HashMap::new();
env.insert("CREDENTIALS".to_string(), "c".repeat(4096));
let expected = serde_json::json!({ "agent_token": "tok", "env": &env })
.to_string()
.len();
assert!(
expected > 4096,
"the fixture must actually be over: {expected}"
);
let error = RunHookPayload::for_launch("tok", &env)
.expect_err("a credential-scale launch env does not fit");
assert_eq!(error.kind(), ErrorKind::InvalidArg);
let message = error.to_string();
assert!(
message.contains("ceiling of 4096"),
"the ceiling has to be named: {message}"
);
assert!(
message.contains(&format!("{expected} bytes")),
"the actual byte count has to be named: {message}"
);
assert!(
message.contains("launch env contributed"),
"the env's share of the budget is the actionable half: {message}"
);
assert!(
message.contains("1 variable(s)"),
"the variable count has to be named: {message}"
);
}
#[test]
fn the_launch_env_ceiling_counts_the_serialized_payload_and_not_the_raw_values() {
let mut env = std::collections::HashMap::new();
env.insert("A".to_string(), "a".repeat(2030));
env.insert("B".to_string(), "b".repeat(2030));
let raw: usize = env.values().map(String::len).sum();
assert_eq!(raw, 4060, "the raw values are under the ceiling");
assert!(
raw <= crate::constants::MAX_RUN_HOOK_PAYLOAD_BYTES,
"the fixture only means something if the raw sum fits: {raw}"
);
let error = RunHookPayload::for_launch("tok", &env)
.expect_err("the serialized payload is over even though the raw values are not");
assert!(error.to_string().contains("2 variable(s)"), "{error}");
let mut fits = std::collections::HashMap::new();
fits.insert("A".to_string(), "a".repeat(2000));
fits.insert("B".to_string(), "b".repeat(2000));
let payload = RunHookPayload::for_launch("tok", &fits).expect("4000-odd bytes fits");
assert!(payload.len() <= 4096, "{}", payload.len());
}
#[test]
fn an_empty_payload_is_legal() {
let payload = RunHookPayload::new("").expect("min is 0");
assert!(payload.is_empty());
assert_eq!(payload.len(), 0);
}
#[tokio::test]
async fn the_launch_emits_derived_connector_arns_in_the_right_members() {
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
let request = RunMicrovmRequest::new("arn:image", payload).with_egress();
plane.run_microvm(request).await.expect("launches");
let calls = fake.calls();
assert_eq!(calls[0].path, "/2025-09-09/microvms");
assert_eq!(calls[0].method, super::super::transport::Method::Post);
let body = fake.first_body("RunMicrovm");
assert_eq!(
body["ingressNetworkConnectors"],
serde_json::json!([
"arn:aws:lambda:us-east-1:aws:network-connector:aws-network-connector:ALL_INGRESS"
])
);
assert_eq!(
body["egressNetworkConnectors"],
serde_json::json!([
"arn:aws:lambda:us-east-1:aws:network-connector:aws-network-connector:INTERNET_EGRESS"
])
);
assert_eq!(body["runHookPayload"], r#"{"agent_token":"token"}"#);
assert_eq!(body["maximumDurationInSeconds"], 3600);
}
#[tokio::test]
async fn a_launch_without_egress_omits_the_member_entirely() {
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
plane
.run_microvm(RunMicrovmRequest::new("arn:image", payload))
.await
.expect("launches");
let body = fake.first_body("RunMicrovm");
assert!(
body.get("egressNetworkConnectors").is_none(),
"omitting egress is the whole mechanism: {body}"
);
assert!(body.get("ingressNetworkConnectors").is_some());
}
#[tokio::test]
async fn a_pinned_launch_version_reaches_the_wire_and_an_unpinned_one_omits_the_member() {
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
plane
.run_microvm(RunMicrovmRequest::new("arn:image", payload).with_image_version("2.0"))
.await
.expect("launches");
let body = fake.first_body("RunMicrovm");
assert_eq!(body["imageVersion"], "2.0");
assert_eq!(
body["imageIdentifier"], "arn:image",
"pinning a version does not replace the identifier; both are sent"
);
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
plane
.run_microvm(RunMicrovmRequest::new("arn:image", payload))
.await
.expect("launches");
assert!(
fake.first_body("RunMicrovm").get("imageVersion").is_none(),
"an unpinned launch must send what this client always sent: {}",
fake.first_body("RunMicrovm")
);
}
#[tokio::test]
async fn an_invalid_launch_version_reaches_no_control_plane_call() {
for bad in ["", "2.0\n", "a b"] {
let (plane, fake, _) = planted();
let payload = RunHookPayload::for_agent_token("token").expect("fits");
let error = plane
.run_microvm(RunMicrovmRequest::new("arn:image", payload).with_image_version(bad))
.await
.expect_err(&format!("{bad:?} is not a legal Version"));
assert_eq!(error.kind(), ErrorKind::InvalidArg, "{bad:?}");
assert!(
error.to_string().contains("imageVersion"),
"{bad:?}: {error}"
);
assert_eq!(fake.calls().len(), 0, "{bad:?} must not reach the wire");
}
}
#[tokio::test]
async fn no_bare_connector_name_reaches_the_wire() {
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
plane
.run_microvm(RunMicrovmRequest::new("arn:image", payload).with_egress())
.await
.expect("launches");
let body = fake.first_body("RunMicrovm");
for member in ["ingressNetworkConnectors", "egressNetworkConnectors"] {
for value in body[member].as_array().expect("a list") {
let text = value.as_str().expect("a string");
assert!(text.starts_with("arn:aws:lambda:"), "{member}: {text}");
assert_ne!(text, "ALL_INGRESS");
assert_ne!(text, "INTERNET_EGRESS");
}
}
}
#[tokio::test]
async fn an_out_of_range_duration_reaches_no_control_plane_call() {
let (plane, fake, _) = planted();
let payload = RunHookPayload::for_agent_token("token").expect("fits");
let mut request = RunMicrovmRequest::new("arn:image", payload);
request.max_duration_sec = 28_801;
let error = plane
.run_microvm(request)
.await
.expect_err("over the ceiling");
assert_eq!(error.kind(), ErrorKind::InvalidArg);
assert_eq!(fake.calls().len(), 0);
}
#[tokio::test]
async fn a_terminal_state_before_running_carries_both_the_state_and_the_reason() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response(
"TERMINATED",
Some("run hook returned 500"),
)),
);
let error = plane
.wait_for_running("mvm-abc123", WaitOpts::for_launch())
.await
.expect_err("a dead VM must not be waited out");
assert_eq!(error.kind(), ErrorKind::LaunchDied);
assert_eq!(error.code(), "ERR_LAUNCH_DIED");
let message = error.to_string();
assert!(
message.contains("TERMINATED"),
"the state must be attached: {message}"
);
assert!(
message.contains("run hook returned 500"),
"the stateReason must be attached: {message}"
);
assert!(
message.contains("lifecycle hook failed"),
"the likely cause is worth naming: {message}"
);
assert!(
fake.call_count("GetMicrovm") == 1,
"it fails fast rather than polling to the deadline"
);
}
#[tokio::test]
async fn every_terminal_state_fails_a_launch_fast() {
assert_eq!(crate::constants::TERMINAL_STATES.len(), 4);
for state in crate::constants::TERMINAL_STATES {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response(state, Some("hook timeout"))),
);
let error = plane
.wait_for_running("mvm-abc123", WaitOpts::for_launch())
.await
.expect_err(&format!("{state} before RUNNING is a death"));
assert_eq!(error.kind(), ErrorKind::LaunchDied, "{state}");
assert!(error.to_string().contains(state), "{state}: {error}");
assert!(
error.to_string().contains("hook timeout"),
"{state}: {error}"
);
assert_eq!(
fake.call_count("GetMicrovm"),
1,
"{state} must fail fast rather than polling to the deadline"
);
}
}
#[tokio::test]
async fn a_missing_state_reason_is_named_rather_than_printed_empty() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("TERMINATED", None)),
);
let error = plane
.wait_for_running("mvm-abc123", WaitOpts::for_launch())
.await
.expect_err("still a death");
let message = error.to_string();
assert!(message.contains("no stateReason"), "{message}");
assert!(
!message.contains(r#"["RUNNING"]: ."#),
"an absent reason must not render as an empty string: {message}"
);
assert_eq!(error.kind(), ErrorKind::LaunchDied);
}
#[tokio::test]
async fn the_launch_wait_polls_through_pending_and_returns_on_running() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
)
.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("RUNNING", None)),
);
let vm = plane
.wait_for_running("mvm-abc123", WaitOpts::for_launch())
.await
.expect("reaches RUNNING");
assert_eq!(vm.state, "RUNNING");
assert_eq!(
vm.endpoint,
"https://mvm-abc123.microvm.us-east-1.amazonaws.com"
);
assert_eq!(fake.call_count("GetMicrovm"), 2);
}
#[tokio::test]
async fn a_launch_that_never_comes_up_times_out_rather_than_reporting_a_death() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let error = plane
.wait_for_running("mvm-abc123", WaitOpts::for_launch())
.await
.expect_err("the deadline elapses");
assert_eq!(error.kind(), ErrorKind::Timeout);
assert_ne!(
error.kind(),
ErrorKind::LaunchDied,
"a timeout is not a death, and conflating them is what hid the cause"
);
assert!(error.to_string().contains("PENDING"), "{error}");
}
#[tokio::test]
async fn the_resume_wait_tolerates_suspended_and_fails_on_terminated() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("SUSPENDED", None)),
)
.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("RUNNING", None)),
);
let vm = plane
.wait_for_state(
"mvm-abc123",
&["RUNNING"],
&crate::constants::DEAD_STATES,
WaitOpts::for_launch(),
)
.await
.expect("SUSPENDED is a waypoint on the resume path");
assert_eq!(vm.state, "RUNNING");
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response(
"TERMINATED",
Some("suspended window elapsed"),
)),
);
let error = plane
.wait_for_state(
"mvm-abc123",
&["RUNNING"],
&crate::constants::DEAD_STATES,
WaitOpts::for_launch(),
)
.await
.expect_err("a terminated VM never reaches RUNNING");
assert_eq!(error.kind(), ErrorKind::LaunchDied);
assert!(
error.to_string().contains("suspended window elapsed"),
"{error}"
);
}
#[tokio::test]
async fn a_suspend_treats_terminated_as_a_state_to_report() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("TERMINATED", None)),
);
let vm = plane
.wait_for_state("mvm-abc123", &SUSPEND_WANTED, &[], WaitOpts::for_launch())
.await
.expect("a suspend reports the state it reached");
assert_eq!(vm.state, "TERMINATED");
}
#[tokio::test]
async fn the_lifecycle_calls_use_the_models_paths_and_methods() {
let (plane, fake, _) = planted();
fake.answer("SuspendMicrovm", Answer::ok(fake::empty_response()))
.answer("ResumeMicrovm", Answer::ok(fake::empty_response()))
.answer("TerminateMicrovm", Answer::ok(fake::empty_response()));
plane.suspend("mvm-1").await.expect("suspends");
plane.resume("mvm-1").await.expect("resumes");
plane.terminate("mvm-1").await.expect("terminates");
let calls = fake.calls();
use super::super::transport::Method;
assert_eq!(
calls
.iter()
.map(|call| (call.operation, call.method, call.path.as_str()))
.collect::<Vec<_>>(),
[
(
"SuspendMicrovm",
Method::Post,
"/2025-09-09/microvms/mvm-1/suspend"
),
(
"ResumeMicrovm",
Method::Post,
"/2025-09-09/microvms/mvm-1/resume"
),
(
"TerminateMicrovm",
Method::Delete,
"/2025-09-09/microvms/mvm-1"
),
]
);
}
#[tokio::test]
async fn the_minted_token_comes_from_the_map_and_carries_both_headers() {
let (plane, fake, _) = planted();
fake.answer(
"CreateMicrovmAuthToken",
Answer::ok(fake::auth_token_response("opaque-proxy-token")),
);
let token = plane.mint_auth_token("mvm-1").await.expect("mints");
assert_eq!(token.value(), "opaque-proxy-token");
assert_eq!(token.port(), 9000);
let headers = token.headers();
assert_eq!(
headers[0],
("X-aws-proxy-auth", "opaque-proxy-token".to_string())
);
assert_eq!(
headers[1],
("X-aws-proxy-port", "9000".to_string()),
"the auth header without the port header is rejected in a way that reads like a \
bad token"
);
let body = fake.first_body("CreateMicrovmAuthToken");
assert_eq!(body["expirationInMinutes"], 60);
assert_eq!(body["allowedPorts"][0]["port"], 9000);
}
#[test]
fn a_launch_requests_debug_does_not_print_the_agent_token() {
let payload = RunHookPayload::for_agent_token("SECRETTOKEN").expect("fits");
let rendered = format!("{payload:?}");
assert!(
!rendered.contains("SECRETTOKEN"),
"the agent token reached a Debug string: {rendered}"
);
assert!(
rendered.contains(&format!("{} bytes", payload.len())),
"the size is what a TRAP-5 diagnosis needs: {rendered}"
);
let request = RunMicrovmRequest::new("arn:image", payload);
let rendered = format!("{request:?}");
assert!(rendered.contains("arn:image"), "{rendered}");
assert!(
!rendered.contains("SECRETTOKEN"),
"the agent token reached a Debug string through the request: {rendered}"
);
}
#[test]
fn a_proxy_token_debug_does_not_print_the_credential() {
let mut map = std::collections::BTreeMap::new();
map.insert(
PROXY_AUTH_HEADER.to_string(),
"eyJhbGciOi-secret".to_string(),
);
let token = ProxyToken::from_map(&map, 9000).expect("the map has the auth key");
let rendered = format!("{token:?}");
assert!(rendered.contains(PROXY_AUTH_HEADER), "{rendered}");
assert!(rendered.contains("9000"), "{rendered}");
assert!(
!rendered.contains("secret"),
"the token value reached a Debug string: {rendered}"
);
}
#[test]
fn a_map_missing_the_auth_key_is_a_retryable_mint_failure() {
let mut map = std::collections::BTreeMap::new();
map.insert("SomeOtherKey".to_string(), "value".to_string());
let error = ProxyToken::from_map(&map, 9000).expect_err("no auth key");
assert_eq!(
error.wire_kind(),
Some(crate::error::WireKind::AuthTokenMint)
);
assert!(error.retryable(), "minting is inside the retry path");
assert!(
error.to_string().contains("is a map, not a string"),
"{error}"
);
}
#[tokio::test]
async fn the_token_is_minted_for_the_configured_port() {
let fake = Arc::new(FakeControlPlane::new());
let plane =
ControlPlane::with_transport(fake.clone(), Region::UsEast1, Arc::new(TestClock::new()))
.with_port(8080)
.expect("8080 is a legal port");
fake.answer(
"CreateMicrovmAuthToken",
Answer::ok(fake::auth_token_response("t")),
);
let token = plane.mint_auth_token("mvm-1").await.expect("mints");
assert_eq!(token.port(), 8080);
assert_eq!(
fake.first_body("CreateMicrovmAuthToken")["allowedPorts"][0]["port"],
8080
);
}
#[tokio::test]
async fn a_full_lifecycle_never_calls_shell_auth_or_requests_shell_ingress() {
let (plane, fake, _) = planted();
fake.answer(
"CreateMicrovmImage",
Answer::created(fake::create_image_response("img")),
)
.answer(
"GetMicrovmImage",
Answer::ok(fake::get_image_response("img", "CREATED")),
)
.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
)
.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("RUNNING", None)),
)
.answer(
"CreateMicrovmAuthToken",
Answer::ok(fake::auth_token_response("proxy-token")),
)
.answer("SuspendMicrovm", Answer::ok(fake::empty_response()))
.answer("ResumeMicrovm", Answer::ok(fake::empty_response()))
.answer("TerminateMicrovm", Answer::ok(fake::empty_response()))
.answer(
"ListMicrovmImageVersions",
Answer::ok(fake::list_versions_response("1")),
)
.answer(
"DeleteMicrovmImage",
Answer::ok(fake::delete_image_response()),
);
let request = super::super::CreateImageRequest::new(
"img",
b"binary".to_vec(),
"s3://bucket/img.zip",
"arn:aws:iam::123456789012:role/build",
);
let image = plane.create_image(request).await.expect("creates");
plane
.wait_for_image(&image.identifier, image.size, WaitOpts::default())
.await
.expect("becomes usable");
let payload = RunHookPayload::for_agent_token("agent-token").expect("fits");
let vm = plane
.run_microvm(
super::super::RunMicrovmRequest::new(&image.identifier, payload).with_egress(),
)
.await
.expect("launches");
plane
.wait_for_running(&vm.id, WaitOpts::for_launch())
.await
.expect("reaches RUNNING");
plane.mint_auth_token(&vm.id).await.expect("mints");
plane.suspend(&vm.id).await.expect("suspends");
plane.resume(&vm.id).await.expect("resumes");
plane.terminate(&vm.id).await.expect("terminates");
plane
.delete_image(&image.identifier, 1, std::time::Duration::from_secs(1))
.await;
let operations = fake.operations();
assert!(
operations.len() >= 10,
"the lifecycle must actually have run: {operations:?}"
);
assert!(operations.contains(&"CreateMicrovmAuthToken"));
assert_eq!(
fake.call_count("CreateMicrovmShellAuthToken"),
0,
"shell auth gates a debug console, not programmatic exec: {operations:?}"
);
for operation in &operations {
assert!(
!operation.contains("Shell"),
"no operation may be a shell one: {operation}"
);
}
for path in fake.paths() {
assert!(
!path.contains("shell"),
"no request may reach a shell route: {path}"
);
}
for body in fake.bodies_as_text() {
assert!(
!body.contains("SHELL_INGRESS"),
"no request may name the shell connector: {body}"
);
assert!(!body.contains("SHELL"), "{body}");
}
}
#[tokio::test]
async fn the_fleet_listing_follows_next_token_to_the_vms_on_page_two() {
let (plane, fake, _) = planted();
fake.answer(
"ListMicrovms",
Answer::ok(fake::list_microvms_page(
&["mvm-page1-a", "mvm-page1-b"],
Some("fleet-page-2"),
)),
)
.answer(
"ListMicrovms",
Answer::ok(fake::list_microvms_page(&["mvm-page2-a"], None)),
);
let fleet = plane.list_microvms().await.expect("lists");
assert_eq!(
fleet.len(),
3,
"a fleet spanning two pages is three VMs, not two"
);
let ids: Vec<&str> = fleet.iter().map(|vm| vm.microvm_id.as_str()).collect();
assert!(ids.contains(&"mvm-page2-a"), "page two is missing: {ids:?}");
assert!(ids.contains(&"mvm-page1-a"), "{ids:?}");
assert_eq!(fake.call_count("ListMicrovms"), 2, "both pages were read");
let paths = fake.paths();
assert!(
paths[1].contains("nextToken=fleet-page-2"),
"the second request must carry the first page's token: {}",
paths[1]
);
assert!(
!paths[0].contains('?'),
"the first request carries no cursor: {}",
paths[0]
);
}
#[tokio::test]
async fn the_reported_idle_policy_is_carried_rather_than_dropped() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("RUNNING", None)),
);
let vm = plane.get_microvm("mvm-1").await.expect("reads");
let policy = vm
.idle_policy
.expect("GetMicrovm returns an idlePolicy, measured 2026-08-15");
assert_eq!(policy.max_idle_duration_seconds, 1800);
assert_eq!(
policy.suspended_duration_seconds, 600,
"the member the comment claimed was request-only"
);
assert!(!policy.auto_resume_enabled);
}
#[tokio::test]
async fn an_absent_idle_policy_is_none_rather_than_a_zero_window() {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(
r#"{
"microvmId": "mvm-1",
"state": "RUNNING",
"endpoint": "https://e",
"imageArn": "arn:aws:lambda:us-east-1:123456789012:microvm-image:img",
"imageVersion": "1"
}"#,
),
);
let vm = plane.get_microvm("mvm-1").await.expect("parses");
assert_eq!(vm.idle_policy, None);
}
#[tokio::test]
async fn a_transport_failure_is_retried_rather_than_reported() {
let (plane, fake, _) = planted();
fake.fail_transport("GetMicrovm", 2).answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("RUNNING", None)),
);
let vm = plane
.get_microvm("mvm-1")
.await
.expect("the retry succeeds");
assert_eq!(vm.state, "RUNNING");
assert_eq!(
fake.call_count("GetMicrovm"),
3,
"two failures then a success"
);
}
#[tokio::test]
async fn an_under_minimum_idle_duration_reaches_no_control_plane_call() {
for under in [0, 1, 59] {
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
let mut request = RunMicrovmRequest::new("arn:image", payload);
request.max_idle_sec = under;
let error = plane
.run_microvm(request)
.await
.expect_err("the model's min is 60");
assert_eq!(error.kind(), ErrorKind::InvalidArg);
let message = error.to_string();
assert!(message.contains("maxIdleDurationSeconds"), "{message}");
assert!(message.contains(&under.to_string()), "{message}");
assert_eq!(
fake.calls().len(),
0,
"{under} was refused locally; before this guard it was serialized and sent"
);
}
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
let mut request = RunMicrovmRequest::new("arn:image", payload);
request.max_idle_sec = 60;
plane.run_microvm(request).await.expect("60 is the minimum");
assert_eq!(
fake.first_body("RunMicrovm")["idlePolicy"]["maxIdleDurationSeconds"],
60
);
}
#[tokio::test]
async fn a_malformed_launch_role_or_identifier_reaches_no_control_plane_call() {
type Mutate = fn(&mut RunMicrovmRequest);
let rows: [(&str, Mutate, &str); 6] = [
(
"an execution role that is a bare name",
|request| {
request.execution_role_arn = Some("execution-role".to_string());
},
"role *name*",
),
(
"an execution role with eleven account digits",
|request| {
request.execution_role_arn =
Some("arn:aws:iam::12345678901:role/exec".to_string());
},
"exactly twelve digits",
),
(
"an execution role that is a function ARN",
|request| {
request.execution_role_arn =
Some("arn:aws:lambda:us-east-1:123456789012:function:h".to_string());
},
"RoleArn pattern",
),
(
"an empty image identifier",
|request| {
request.image_identifier = String::new();
},
"collapses the path",
),
(
"a 257-character image identifier",
|request| {
request.image_identifier = "a".repeat(257);
},
"MicrovmImageArn permits 2048",
),
(
"an image version with a newline",
|request| {
request.image_version = Some("2.0\n".to_string());
},
"contains whitespace",
),
];
for (label, mutate, expected) in rows {
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
let mut request = RunMicrovmRequest::new("arn:image", payload);
mutate(&mut request);
let error = plane
.run_microvm(request)
.await
.expect_err(&format!("{label} must be refused"));
assert_eq!(error.kind(), ErrorKind::InvalidArg, "{label}");
assert!(
error.to_string().contains(expected),
"{label}: wanted {expected:?}, got {error}"
);
assert_eq!(fake.calls().len(), 0, "{label}");
}
let (plane, fake, _) = planted();
fake.answer(
"RunMicrovm",
Answer::ok(fake::microvm_response("PENDING", None)),
);
let payload = RunHookPayload::for_agent_token("token").expect("fits");
let mut request = RunMicrovmRequest::new(
"arn:aws:lambda:us-east-1:392583147479:microvm-image:agentd-conformance",
payload,
);
request.execution_role_arn =
Some("arn:aws:iam::392583147479:role/bonk-sandbox-microvm-execution".to_string());
request.image_version = Some("1.0".to_string());
plane
.run_microvm(request)
.await
.expect("a realistic pinned launch with a real execution role");
assert_eq!(fake.call_count("RunMicrovm"), 1);
}
#[tokio::test]
async fn no_lifecycle_operation_sends_an_empty_or_over_long_microvm_identifier() {
for bad in ["", &"a".repeat(257)] {
let (plane, fake, _) = planted();
fake.answer(
"GetMicrovm",
Answer::ok(fake::microvm_response("RUNNING", None)),
);
fake.answer("SuspendMicrovm", Answer::ok("{}"));
fake.answer("ResumeMicrovm", Answer::ok("{}"));
fake.answer("TerminateMicrovm", Answer::ok("{}"));
fake.answer(
"CreateMicrovmAuthToken",
Answer::ok(fake::auth_token_response("t")),
);
plane.get_microvm(bad).await.expect_err("GetMicrovm");
plane.suspend(bad).await.expect_err("SuspendMicrovm");
plane.resume(bad).await.expect_err("ResumeMicrovm");
plane.terminate(bad).await.expect_err("TerminateMicrovm");
plane
.mint_auth_token(bad)
.await
.expect_err("CreateMicrovmAuthToken");
plane
.wait_for_running(bad, WaitOpts::for_launch())
.await
.expect_err("GetMicrovm, in a loop");
assert_eq!(
fake.calls().len(),
0,
"six operations refused {bad:?} locally. An empty identifier on the terminate \
path is a DELETE addressed at the collection."
);
}
}
#[tokio::test]
async fn no_proxy_token_is_minted_for_port_zero() {
let rows: [(&str, ops::PortSpecification, &str); 3] = [
(
"a single port of 0",
ops::PortSpecification::port(0),
"allowedPorts[].port",
),
(
"a range starting at 0",
ops::PortSpecification::range(0, 9000),
"allowedPorts[].range.startPort",
),
(
"a range ending at 0",
ops::PortSpecification::range(0, 0),
"allowedPorts[].range.startPort",
),
];
for (label, spec, member) in rows {
let (plane, fake, _) = planted();
fake.answer(
"CreateMicrovmAuthToken",
Answer::ok(fake::auth_token_response("t")),
);
let error = plane
.mint_auth_token_for("mvm-1", &[spec])
.await
.expect_err(&format!("{label} must be refused"));
assert_eq!(error.kind(), ErrorKind::InvalidArg, "{label}");
let message = error.to_string();
assert!(message.contains(member), "{label}: {message}");
assert!(
message.contains("authorizes nothing"),
"{label}: the 403 consequence has to be in the message: {message}"
);
assert_eq!(fake.calls().len(), 0, "{label}");
}
let (plane, fake, _) = planted();
fake.answer(
"CreateMicrovmAuthToken",
Answer::ok(fake::auth_token_response("t")),
);
plane
.mint_auth_token_for(
"mvm-1",
&[
ops::PortSpecification::port(9000),
ops::PortSpecification::port(0),
],
)
.await
.expect_err("every entry is checked, not the first");
assert_eq!(fake.calls().len(), 0);
for legal in [
ops::PortSpecification::port(1),
ops::PortSpecification::port(65_535),
ops::PortSpecification::range(1, 65_535),
ops::PortSpecification::all(),
] {
let (plane, fake, _) = planted();
fake.answer(
"CreateMicrovmAuthToken",
Answer::ok(fake::auth_token_response("t")),
);
plane
.mint_auth_token_for("mvm-1", &[legal])
.await
.expect("a legal port specification mints");
assert_eq!(fake.call_count("CreateMicrovmAuthToken"), 1);
}
}
}