Report node hypervisor, prereq and guest CLIs

Task 10054 · Done · Coder · created · updated

## 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: node-side platform report (Architecture §2.3 unmarked probe, §4.2 CLI attestation node half, §9.1 Linux/Windows columns) Requirements this child serves (PRD §4, verbatim): - R1. Mark. An Apple Silicon macOS node and an x64 Windows node (self-hosted, or cloud-launched for Windows) can be marked MicroVM through the existing Node detail toggle. Conditions: the org's plan allows MicroVM, the node reports a usable hypervisor, and it has no held static worktrees. When the node does not qualify, the refusal is a readable sentence naming the missing prerequisite. The Linux-only `NO_KVM` sentence must not be shown to a Mac or Windows node for a reason that no longer applies. - R4. Providers. Inside a VM, a Mac or Windows node runs every provider an Ubuntu MicroVM node runs. Placement and the account picker must not refuse such a provider because of the host OS when the session lands in a VM. The host-session rules stay as the ADR-0145 table states for unmarked nodes. - R7. Linux unchanged. Linux MicroVM behaviour, the static pool and the unmarked Mac/Windows behaviour do not regress. Pinned platform-report seam (Architecture §9.1, verbatim): | Field | Vocabulary | Linux | Windows | macOS | Null means | |---|---|---|---|---|---| | `kvm` (existing) | bool | `/dev/kvm` exists (unchanged) | the WSL KVM probe (§2.3) | `jaah-vz probe` `usable` | unknown, which is unusable | | `hypervisor` (new) | `kvm` \| `kvm-wsl2` \| `vz` | `kvm` | `kvm-wsl2` | `vz` | unknown (an old node). The console shows "Hypervisor unknown" | | `microvm_prereq` (new) | `windows_build` \| `wsl_missing` \| `wsl_no_kvm` \| `vz_unsupported` \| `vz_helper` | never sent (Linux keeps `NO_KVM`) | the first unmet one, in the order listed | `vz_unsupported` (probe `error: "unsupported"`) or `vz_helper` (helper missing, or probe `error: "entitlement"` or a non-zero exit) | nothing unmet | **Precedence.** `kvm is True` passes the hypervisor check, and a node sends no `microvm_prereq` with it. If one arrives anyway it is ignored. `kvm is not True` refuses with the `microvm_prereq` sentence, or with the "update this node" sentence when the code is null or unknown. An unknown code is dropped at ingest, like every other platform field. 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) 1. **Linux** `apps/jaahd/host_platform/linux.py`: report `hypervisor: "kvm"`; never send `microvm_prereq`. `kvm()` unchanged. 2. **Windows (unmarked)** `apps/jaahd/host_platform/windows.py` `kvm()` (today returns `None`, `:116`): run `wsl -d jaah-engine -u root --exec sh -c 'test -c /dev/kvm && exec 3<>/dev/kvm'` with a 10 s bound, cached for one pass (the utility VM and `/dev/kvm` are shared by all distros, so the existing engine distro answers before `jaah-microvm` exists). Report `hypervisor: "kvm-wsl2"` and `microvm_prereq`, the first unmet in order: `windows_build` (build < 22000 on a client, < 26100 on a server — the server arm is probe-gated; the Windows-backend child may change it to "no server build"), `wsl_missing` (the existing bootstrap check, `node_bootstrap.py:2189-2197`), `wsl_no_kvm` (probe fails). `kvm is True` → no prereq. 3. Thread `hypervisor`/`microvm_prereq` through the observation's platform block the CP ingests (`fleet_observe.py:729-780` is the reader, built by the CP child — an old CP ignores the new keys). 4. **Guest CLI attestation (node half, shared Linux code):** `microvm_images` records the provider binaries installed in each built **base** image in its marker (runner CLIs live in the base; project layers add toolchains, never CLIs — confirm and keep true); one helper `microvm_images.guest_clis()`; jaahd reports `runner_clis` as that guest set while `microvm_mark.is_marked()`, the host set otherwise; **before the base is ready it reports `runner_clis` as absent (`None`, "unattested"), never `[]`**. The Windows (bridge `facts` frame) and macOS wiring of this helper belong to their runtime children. macOS reporting (`vz`) is NOT here — it comes from the `jaah-vz` helper child. Acceptance criteria this child serves (PRD §5, verbatim): - AC2 — A non-qualifying node gets a readable, specific refusal (R1). Every state is covered: hypervisor prerequisite unmet, plan off, held worktrees, live VMs while turning off. Verify: (a) On Win with the A2 prerequisite disabled (and on a Mac where the Architect's prerequisite is unmet), turn on → observe a sentence naming that prerequisite; it must not say "needs a Linux node". (b) In an org whose plan and override deny MicroVM → observe "Your plan doesn't include MicroVM nodes." (c) With a user-held worktree → observe the held-worktrees sentence. (d) With a live VM, turn off → observe "Wait for N MicroVM… to end." - AC8 — The provider set is the Linux guest's (R4). Verify: On Mac (host supports only Claude Code), run a task with a Codex account (and one other non-macOS provider an Ubuntu MicroVM node runs) → it is placed in a VM and runs. On an UNmarked Mac, the same account is still refused with the ADR-0145 sentence. - 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. Ownership note (Planner): this child delivers the node facts those oracles read; it has no live oracle (the console display needs the CP child) — the live reads of these facts on Win/Mac belong to the Windows-backend and Mac-verification children. Credentials & reach (Architect §8, verbatim rows): - AC1–AC12, AC14 on Win | Node `win-t8703` (Windows · x64, online, 1.2.4277) on dev | **verified**: read on its Node detail page. AD3 confirms it is the PC. Its Windows edition/build is not shown in the console, so C2's first step is the P2 probe. - 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; boot-latency optimisation (pre-booted snapshots). Never rename the `kvm_usable`/`kvm_error` wire keys (kept on purpose, §3.4). Also do NOT touch: the CP (`apps/api/`), the `jaah-microvm` distro / bridge (Windows-backend child), `host_platform/macos.py` (jaah-vz child). Done means: - jaahd unit tests: Linux reports `hypervisor="kvm"` and no `microvm_prereq`; Windows `kvm()` maps (probe ok → `kvm=True`, no prereq), (probe fails → `wsl_no_kvm`), (WSL absent → `wsl_missing`), (client build 19045 → `windows_build`), (server build 20348 → `windows_build`), with the 10 s bound and once-per-pass cache asserted; ordering is first-unmet. - `guest_clis()` unit tests: marked + base ready → base marker's set; marked + no base → `None` (not `[]`); unmarked → host set; a project layer never changes the set. - AC15 — `./dc check full` green (cite GATE_RESULT); existing Linux MicroVM facts tests unchanged and green.

Outcome

Node reports hypervisor (kvm / kvm-wsl2) and the first unmet microvm_prereq (WSL2 KVM probe, 10 s cap, once per pass), plus microvm_images.guest_clis(). Quick gate green; PR #6917 queued, lane runs full. Deferred: wsl_no_kvm on an unregistered engine distro. Memory: none applied.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)