Pin arm64 guest images and raw disks
## Epic requirement (verbatim)
"Implement microVM on Windows and MacOS" (Task 10021 PRD ask #1, `in_scope`).
Operator decisions that bind every child (verbatim from the PRD/Architecture):
- D1 **Full parity** with Linux. Each session gets its own Linux guest kernel. The CP egress allowlist is enforced on the node. Control calls are vsock-bound (or an equivalent per-VM channel). No NodeLink inside the VM. No root in the guest. The disk is retained on `system_failed`. Console, sizing and placement UX are the same.
- D2 **All three:** fleet runners, advisor chats and human console sessions. D3 **The Linux guest's set** of providers. D4 macOS = Apple Silicon, Windows = x64. D5 cloud Windows too. D7 same plan feature (`billing_plans.microvm_allowed` + `orgs.microvm_allowed_override`).
- AD1 Windows = WSL2 + the existing Cloud Hypervisor/KVM runtime (Windows 10 refused). AD2 Mac guests arm64 only, no Rosetta. AD3 Windows test PC = `win-t8703`. AD5 probe m8i.large exactly; metal (m7i.metal-24xl) is the fallback. AD6 Mac oracles deferred until the Mac is enrolled on dev. AD7 AC2(a) live is dropped; unit tests map every `microvm_prereq` code to its sentence.
**Quality bar (verbatim).** Every refusal shown to an operator is one plain sentence naming what is missing and how to fix it (`docs/standards/ui-copy-standards.md`). No generic 400, no stack trace, no Linux-specific wording on a Mac or Windows node. Boot-to-runner latency on Mac and Win is reported in the build's evidence. It is not gated, but it must not be degraded by more than 2× against a Linux MicroVM node of similar size without the Architect recording why.
## This child: arm64 pins, raw conversion, APFS-clone disk backend (Architecture §3.3)
Requirements this child serves (PRD §4, verbatim):
- R6. Install and update. The macOS installer and the Windows service install and converge everything the VM runtime needs: binaries, images and helpers. The A2 prerequisite is the only manual step. Updating a node keeps its VMs' contract.
- R7. Linux unchanged. Linux MicroVM behaviour, the static pool and the unmarked Mac/Windows behaviour do not regress.
The full settled Architecture (Task 10022) and PRD (Task 10021) are the epic's prerequisite artifacts; read them with `task_read`/`artifact_read` when this body is shorter than you need. Section numbers (§) refer to the Architecture.
### Approach (settled, realize it)
- `packaging/microvm-pins.toml` gains arm64 artifacts with `arch = "aarch64"` beside the x86_64 ones, under the same licence gate: `ubuntu-noble-cloudimg-arm64` (a dated serial, `fetch = "node"`) and `nodejs-linux-arm64` (`fetch = "node"`). No CH, firmware or virtiofsd for arm64 (VZ provides VMM, EFI and virtio-fs). Rewrite the header comment "every pin is x86_64" (`microvm-pins.toml:27`) to describe the present. `tools/microvm_pins.py` selects pins by `(arch, os)`; x86_64 Linux selection unchanged; `(x86_64, windows)` selects the same CH/CLOUDHV.fd/virtiofsd/noble-image/node set as Linux (the Windows bundle's `microvm/` payload runs inside the WSL distro).
- `apps/jaahd/microvm_qcow2.py` gains `to_raw(src, dst)`: a pure-Python qcow2 reader that inflates zlib-compressed clusters (Ubuntu's `.img` is compressed qcow2), output a sparse raw file. Keep the no-GPL-tooling rule (no `qemu-img`).
- **Layering by APFS clone, not a backing chain** on macOS: base `base-<pin>.raw` → project layer `layer-<project>-<gen>.raw` → per-session disk, each a `clonefile(2)` (via `ctypes` from `libSystem`) of the one before. Retention/destroy unchanged (the VM dir holds its disk). `microvm_images` asks ONE function `disk_backend()` → `qcow2-chain` on Linux/WSL, `apfs-clone` on macOS; no other module branches. Boot growth: the raw root is truncated to the effective disk size; cloud-init `growpart` runs as on Linux.
Acceptance criteria this child owns (PRD §5, verbatim):
- AC17 — Pins are licence-gated per architecture (A3). Verify: `python3 tools/microvm_pins.py` (the licence gate) passes with the arm64 guest images/binaries pinned beside the x86_64 ones.
- AC15 — Linux and static behaviour unchanged (R7). Verify: `./dc check full` is green. On dev, a Linux MicroVM node still passes AC3. An unmarked Mac and Win still run a static task as before.
Credentials & reach (Architect §8, verbatim rows):
- AC15 gate, AC16, AC17 | Worktree only (`./dc check full`, `tools/check_context.py`, `tools/microvm_pins.py`) | **verified**: nothing beyond the worktree.
Out of scope (BA, verbatim — do NOT touch):
- Do **not** change Linux MicroVM behaviour, its pins, its egress default list (`apps/api/app/microvm_egress.py`) or its sizing defaults, beyond what is needed to add the OS dimension.
- Do **not** change the static worktree pool or dynamic worktrees on any OS.
- Do **not** add a new plan term or billing price (D7).
- Do **not** mount NodeLinks inside a VM on any OS.
- Do **not** add Multi-Node Hosts / co-located nodes (`vnode_host`), GitHub Actions runners or SDK-agent support to macOS or Windows hosts.
- Do **not** support Intel Macs, Windows on ARM, or cloud-launched macOS nodes.
- Do **not** relax the host-session (unmarked) capability table of ADR-0145.
- Do **not** describe the boundary as stronger than each hypervisor gives ("strong isolation", "defence in depth" — ADR-0129 anti-pattern).
- No new console page. Existing surfaces only (A4).
- Operate and verify Jaah only through the console. No CLI, SQL or cloud-console workaround for live state (CLAUDE.md hard rule).
Architect boundaries (declined, do NOT build): a Windows native Hyper-V runtime; Rosetta in Mac guests (no x86_64 binfmt); boot-latency optimisation (pre-booted snapshots).
Also do NOT touch: the x86_64 pins' values or the Linux qcow2 chain path; microvmd / seed (macOS-runtime child).
Done means:
- AC17 — `python3 tools/microvm_pins.py` exits 0 with both arm64 pins present and licence-checked (output cited); selection unit tests: `(x86_64, linux)` returns exactly today's set, `(x86_64, windows)` returns the same set, `(aarch64, macos)` returns the arm64 image + node and no CH/firmware/virtiofsd.
- `to_raw` unit tests: a committed small compressed-qcow2 fixture (generated by a test-side pure-Python writer, no `qemu-img`) converts to raw whose bytes match the expected image, unallocated clusters stay sparse (allocated blocks < virtual size), a corrupt header raises a readable error.
- `disk_backend()` returns `qcow2-chain` on Linux and `apfs-clone` on macOS (platform patched); the APFS clone path is exercised on the `macos-15` CI job (a clone shares blocks; a write to the clone diverges only the written range) — job log cited.
- AC15 — `./dc check full` green (cite GATE_RESULT); existing Linux `microvm_images`/`microvm_qcow2` tests unchanged and green.
Outcome
Added arm64 pins with (arch, os) selection, a pure-Python qcow2-to-raw converter (byte-identical to qemu-img on the real image) and disk_backend() with APFS clones. Quick gate green; merge lane merged #6918 (90c3b90). Deploys superseded, so the macos-15 APFS job is not yet observed. No UI surface affected. Memory: none applied.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)