- Status: done —
sudo -E make test-cloud-qemuPASSES all three scenarios (unseeded → seeded → frozen) on a clean VM - Date: 2026-06-22
- Specs touched: none (a test-harness fix; realizes the "VM-boot acceptance pending" gap left by hosted-caddy-image-bake.md)
Closes the VM-boot acceptance gap that hosted-caddy-image-bake.md (#236) left open ("make test-cloud-qemu … has not been run in this slice's environment"). Running it for the first time on top of the post-C3a/C3b/C4 stack failed on the unseeded /setup gate (got 422, want 503). Tracing it surfaced two stacked build-staleness bugs and one stale harness assumption; this slice fixes all three so the lane is a real no-regression gate for the hosted on-ramp, not a never-run one.
- Stale brain image (the visible
422).dev/cloud/test/bootstrap.shreuses the shared.dev/control-plane/*.tarbundle unlessMALMO_REBUILD_CP=1. The cachedmalmo-brain.tarpredated C3a's/setupgate + the C1a profile resolution, so the brain booting in QEMU resolvedapplianceand/setupfell through to the appliance-era request-validation path (422on an empty/partial body), never the hosted503. Confirmed by running the freshly-built brain image locally with ahostedmarker mounted → correct503. - Canary masked every rebuild. The
.cloud-boot-ready/CANARY_VERSIONidempotency gate skips the entire bootstrap when the version matches and the image exists — so a fresh brain tarball (or any staged-tree edit) never re-entered the image. The canary keys only on its own version + image existence; it has no knowledge of the control-plane bundle changing underneath it. Bumping the canary is the documented way to force a clean rebuild after staged content changes. - Provisioned dashboard host moved (the follow-on
404). Once the fresh brain booted, the seeded/frozen scenarios failed the dashboard-SPA //apichecks with Caddy's catch-all404: after C3b (#207) a provisioned hosted box serves the dashboard,/api, and/setupunder its wildcard apex<box-id>.malmo.network, butcloud-assertions.shhardcodedDASH_HOST=malmo.localfor all boots. Unprovisioned still usesmalmo.local(no box-id yet), so only the post-seed boots were wrong.
dev/cloud/cloud-assertions.sh— resolveDASH_HOSTper scenario just before the HTTP checks (afterjson_stris defined):malmo.localforunseeded,<box-id>.malmo.networkforseeded(box-id read from the just-materialized seed) andfrozen(box-id from the persisted identity carried inMODE, since the brain ignores the re-delivered seed). Added two failure-path diagnostics todiag()— the brain's resolved-profile / seed-ingest log lines (grep'd, nottail'd, so they aren't scrolled off) and the brain container's bind-mount list (to confirm/etc/malmo/profileis mounted) — which pinpoint this exact class of failure next time.dev/cloud/test/bootstrap.sh—CANARY_VERSIONv13 → v14 to force the clean rebuild that picks up the fresh brain bundle + the assertion change.
sudo -E make test-cloud-qemugreen on a clean VM: boot 1 unseeded →/setup503(gate armed); boot 2 seeded → wrong secret401, correct200+box_id=cindy-fox; boot 3 frozen → identity held across reboot, re-delivered seed B (rusty-hawk) ignored,box_idstillcindy-fox. Verdict:cloud gate proof: PASS.- The fresh brain image's
/setupbehavior was first reproduced locally (hosted marker →503, appliance → proceeds past the gate) to isolate the cause before the QEMU rebuild.
- Bundle-cache staleness is mitigated, not eliminated. A future brain/host-agent code change still won't refresh the cached
.dev/control-plane/*.tarlocally unless the dev runsMALMO_REBUILD_CP=1(or deletes the bundle); the canary forces an image rebuild but not a bundle rebuild — they are orthogonal caches. CI is unaffected (no cache; builds fresh). Left as-is to preserve the deliberate ~13 GB-rebuild-avoidance for the inner loop; the failure mode is now self-diagnosing via the addeddiag()profile/mount dump. - Still air-gapped. As with the prior cloud-lane entries, this proves the control plane comes up and the hosted gate works end-to-end over the seed contract — not real ACME issuance, which remains the cloud-side CL6 live run (
malmoos/cloud#18,docs/ops/e2e-onramp.md).