feat(microvm): Claude Code 2.1.276 rootfs pins, and a VM run that says what it ran
Every rootfs on the fleet had sat on Claude Code 2.1.223–2.1.226 since August while the container tier moved to 2.1.276, and nothing recorded either. GLM and Kimi exist only as microVM backends, so "have we upgraded GLM and Kimi" is this change and the rebuild it drives. Pins. All four agent-* images pin 2.1.276 — as separate ARGs, since Docker has no include and each file has to stay reproducible alone — and scripts/fc-build-rootfs.sh refuses to build if they disagree, naming the odd one out. They had already drifted (claude 226, the rest 223) under comments saying "same version on purpose". Between 2.1.226 and 2.1.276, 2.1.265 and 2.1.275 each broke every turn on ANTHROPIC_BASE_URL endpoints, which is how glm and kimi reach `claude` inside a VM; the container-tier verification never exercised that path, so the VM runs on those backends are the real test. Provenance. `VmOutcome` carries the rootfs the node reported booting and the guest's own `claude --version`; `launch_microvm_phase` persists both as `checkpoint.vm` beside `records` (the two readers parse only `records`) and names them in its log line. "Which image and CLI did this mission run on" is a query now. Independence. `evaluator` derived the implementer family from a constant `"anthropic"`, true while every backend was Claude on Anthropic. With glm and kimi rootfs it made a glm mission judged by glm:glm-5.3 read as `independent = true` — the one claim that path exists to make honestly. `implementer_family(missions.backend)` mirrors `microvm_credential_for`; the subscription judge is now independent exactly when the agent did NOT run on Anthropic. Harness. `verify-mission-delivery.sh glm|kimi` run the microvm scenario on each backend and add the proof the mission itself cannot give: the placed node's journal must show the VM dialling that provider's host, never being denied it, and dialling nothing else but the forge — a model's self-report is measured worthless here. `assert_cli_version` reads checkpoint.vm. The stale scratch-repo default (dead since the 09-14 wipe) is the re-synced id. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
This commit is contained in:
co-authored by
Claude Opus 5
parent
3755699b41
commit
794f2124bc
@@ -1781,6 +1781,18 @@ async fn launch_microvm_phase(
|
||||
if let Ok(o) = &outcome {
|
||||
record_vm_tools(&pool2, mission_id, phase_id, run_id, &o.tools, &[]).await;
|
||||
}
|
||||
// What actually ran: the rootfs the node booted and the CLI the guest
|
||||
// reported. Persisted on the run so "which image and version did this
|
||||
// mission use" is a query, not an inference from file mtimes — on
|
||||
// 2026-09-18 every fleet rootfs had sat on 2.1.223–2.1.226 for a month
|
||||
// while the container tier moved on, and nothing had recorded either.
|
||||
let vm = serde_json::json!({
|
||||
"vm_id": crate::microvm_executor::vm_id_for(phase_id, iteration, None),
|
||||
"node_id": target_node_id,
|
||||
"backend": backend,
|
||||
"rootfs": outcome.as_ref().ok().and_then(|o| o.rootfs.clone()),
|
||||
"cli_version": outcome.as_ref().ok().and_then(|o| o.cli_version.clone()),
|
||||
});
|
||||
let (status, note) = match outcome {
|
||||
// The gate gave up. It is the ONLY thing that runs a
|
||||
// `done_when_check`, so a release at the cap means the phase's own
|
||||
@@ -1813,7 +1825,9 @@ async fn launch_microvm_phase(
|
||||
eprintln!(
|
||||
"phase_runner: microvm phase {phase_id} of mission {mission_id} → {status} \
|
||||
(subagents: {subagents}, teammates: {teammates}, stop-gate blocks: \
|
||||
{blocked}) — {}",
|
||||
{blocked}; rootfs: {}, cli: {}) — {}",
|
||||
vm["rootfs"].as_str().unwrap_or("?"),
|
||||
vm["cli_version"].as_str().unwrap_or("?"),
|
||||
note.chars().take(300).collect::<String>()
|
||||
);
|
||||
// Never overwrite a cancellation. The operator asking to stop is a decision;
|
||||
@@ -1843,7 +1857,9 @@ async fn launch_microvm_phase(
|
||||
"output": note,
|
||||
"tokens": 0,
|
||||
"gated": [],
|
||||
}]
|
||||
}],
|
||||
// Beside `records`, not inside: the two readers parse only `records`.
|
||||
"vm": vm,
|
||||
});
|
||||
if let Err(e) = sqlx::query(
|
||||
"UPDATE topology_runs
|
||||
|
||||
Reference in New Issue
Block a user