fix(missions): a microvm mission could not be created at all

`runtime_kind='microvm'` passes the DB CHECK, is honoured by placement, and now
has an executor — but `POST /api/missions` rejected the value with 400, so the
only interface that creates missions could not produce one. And `backend`, which
selects the per-CLI rootfs, was not in the create payload at all: it existed as a
column and as a parameter to `vm_create`, with nothing able to set it.

microvm needs no target_node_id at create time, unlike local_herdr: placement
resolves a KVM-capable node at launch and fails the launch when there is none, so
an explicit target is a request rather than a requirement.

461 tests pass, clippy clean.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-05 16:21:39 -07:00
co-authored by Claude Opus 5
parent 0206be68e5
commit c9b7d8b6ca
2 changed files with 17 additions and 2 deletions
+10
View File
@@ -38,6 +38,9 @@ pub struct CreateMissionRequest {
/// Defaults to "zeroclaw". "local_herdr" requires target_node_id.
pub runtime_kind: Option<String>,
pub target_node_id: Option<Uuid>,
/// Which per-CLI rootfs a `microvm` mission boots (`missions.backend`), e.g.
/// "claude". NULL boots the node's default image.
pub backend: Option<String>,
}
fn default_schedule() -> Value {
@@ -260,6 +263,12 @@ pub async fn create(
return Err(ApiError::BadRequest);
}
}
// microvm needs no target here: placement resolves a KVM-capable node at
// launch and fails the launch when there is none, so an explicit target is
// a request rather than a requirement. Rejecting the value outright — as
// this did until B4.5 — made `runtime_kind='microvm'` unreachable through
// the only interface that creates missions.
"microvm" => {}
_ => return Err(ApiError::BadRequest),
}
@@ -275,6 +284,7 @@ pub async fn create(
config: body.config,
runtime_kind: Some(runtime_kind),
target_node_id: body.target_node_id,
backend: body.backend.as_deref(),
phases: phases_for_create(
crate::workflow_registry::get(body.template_kind.trim()),
body.phases,