|
| 1 | +## Phase 3 Day 2 — substrate-agnostic prefix port + stub retirement |
| 2 | + |
| 3 | +> Outcome log. Status: complete. Day 3 picks up |
| 4 | +> `CapsuleBackend::VzVm` and supervisor `RunningCapsule` |
| 5 | +> registration (the last AG row in the |
| 6 | +> [Day-1 audit table](PHASE_3_DAY_1_PORT_PLAN.md#audit-table)). |
| 7 | +
|
| 8 | +### Goal (recap) |
| 9 | + |
| 10 | +Day 1 shipped the bare supervisor → `VzProvider` seam: |
| 11 | +`elastos start <microvm>` on macOS reached |
| 12 | +`VzProvider::load_with_vm_config` instead of `bail!()`, but with |
| 13 | +a **minimal** `VmConfig` — no session token, no command payload, |
| 14 | +no capsule args, no Carrier bridge, no rootfs overlay. The Day-1 |
| 15 | +audit (`PHASE_3_DAY_1_PORT_PLAN.md`) classified each missing |
| 16 | +operation as **AG** (substrate-agnostic), meaning the Linux code |
| 17 | +could be ported into the Mac arm with no semantic change. |
| 18 | + |
| 19 | +Day 2 ports every AG row. |
| 20 | + |
| 21 | +### What landed |
| 22 | + |
| 23 | +1. **`Supervisor::build_vm_config_for_mac`** (new, Mac-gated, |
| 24 | + `elastos-server/src/supervisor.rs`). Factored helper that |
| 25 | + replicates the substrate-agnostic prefix of |
| 26 | + `launch_capsule` L997-1144 against `elastos_vz::VmConfig`: |
| 27 | + |
| 28 | + - vsock CID alloc + handle (advisory CID on Mac per Phase 0 §D #5). |
| 29 | + - `_elastos_interactive` / `_elastos_capsule_args` extraction + |
| 30 | + launch_config sanitisation. |
| 31 | + - `VzVmConfig::from_manifest` + `vsock_cid` + `elastos.data_dir=/opt/elastos`. |
| 32 | + - Provider-wide initramfs default if `vm_config.initramfs_path` is `None`. |
| 33 | + - Interactive TERM/winsize injection (`TIOCGWINSZ` + `TERM` env; |
| 34 | + both POSIX, work on Darwin). |
| 35 | + - Session token injection — shell capsule gets `self.shell_token`, |
| 36 | + all others get a fresh `SessionType::Capsule` token from |
| 37 | + `self.session_registry`. Mac always follows the no-TAP branch: |
| 38 | + `boot_args += " elastos.token=<t>"` only, no `elastos.api=` |
| 39 | + (the capsule uses the microVM Carrier bridge, not HTTP). |
| 40 | + - Command payload base64 (`elastos.command=`). |
| 41 | + - Capsule args base64 (`elastos.capsule_args=`). |
| 42 | + - `elastos.provider_port=<VM_PROVIDER_PORT>` when the manifest |
| 43 | + advertises `provides`. |
| 44 | + - Carrier socket path under `self.crosvm_config.socket_dir` |
| 45 | + (OS-agnostic location reused from the Linux path). |
| 46 | + - `vm_config.boot_args += " elastos.carrier_path=/dev/hvc1"` |
| 47 | + — Mac-specific delta from the Linux flow (`/dev/hvc0` is the |
| 48 | + Vz kernel console). |
| 49 | + - Rootfs overlay under `self.crosvm_config.rootfs_cache_dir/overlays/` |
| 50 | + with bytes copied verbatim from `<capsule_dir>/rootfs.ext4`, |
| 51 | + rewiring `vm_config.rootfs_path` to the overlay. |
| 52 | + |
| 53 | +2. **`Supervisor::start_capsule_vm_macos`** signature change: |
| 54 | + gained `config: serde_json::Value` so it receives the same |
| 55 | + launch payload the Linux arm does. Body now: |
| 56 | + |
| 57 | + - Fails closed before any VM work if |
| 58 | + `manifest.permissions.guest_network == true`, with a typed |
| 59 | + message naming `com.apple.vm.networking` and Phase 3 Day 4+. |
| 60 | + No silent NAT downgrade. |
| 61 | + - Builds the full `VmConfig` via `build_vm_config_for_mac`. |
| 62 | + - Spawns the microVM Carrier bridge on the returned socket |
| 63 | + path (same call shape as Linux L1148-1174). The listener |
| 64 | + exists; the guest does not yet receive bytes through it |
| 65 | + because the Vz console attachment is still a placeholder |
| 66 | + (Phase 3 Day 4+). |
| 67 | + - Calls `VzProvider::load_with_vm_config + start`. |
| 68 | + - Returns the Day-3-pending typed error after a clean |
| 69 | + `provider` drop (VM stopped, no leak). |
| 70 | + |
| 71 | +3. **`VzProvider::set_session_for_vm`** + **`VzProvider::set_network_for_vm`** |
| 72 | + (`elastos-vz/src/provider.rs`) retired. They previously |
| 73 | + carried `PHASE_1_STUB_MESSAGE`; they now return a typed, |
| 74 | + named migration error in the same style as |
| 75 | + `append_boot_args_for_vm` did on Day 1. The bodies explicitly |
| 76 | + point operators at `VmConfig::with_session` / |
| 77 | + `load_with_vm_config` and at the entitlement requirement for |
| 78 | + bridged-mode networking. |
| 79 | + |
| 80 | +4. **Tests:** |
| 81 | + |
| 82 | + | Crate | New / updated test | Purpose | |
| 83 | + |---|---|---| |
| 84 | + | `elastos-server` | `start_capsule_vm_macos_reaches_vz_provider_after_phase3_day1` (existing) | Confirms the seam is still reached after the Day-2 changes. | |
| 85 | + | `elastos-server` | `build_vm_config_for_mac_bakes_full_phase3_day2_prefix` (new) | Asserts every Day-2 boot arg (`data_dir`, `command`, `capsule_args`, `carrier_path=/dev/hvc1`) is baked; carrier-socket path embeds the handle; the wrong hvc0 path is never used. | |
| 86 | + | `elastos-server` | `start_capsule_vm_macos_fails_closed_when_manifest_requests_tap_network` (new) | Asserts TAP is rejected with a typed message naming the entitlement and the capsule name; no silent downgrade. | |
| 87 | + | `elastos-server` | `build_vm_config_for_mac_creates_rootfs_overlay_when_source_present` (new) | Asserts the overlay file is created under `rootfs_cache_dir/overlays/`, bytes round-trip, source rootfs untouched. | |
| 88 | + | `elastos-vz` | `vz_provider_set_session_for_vm_returns_typed_migration_error_after_phase3_day2` (new) | Confirms the retired stub no longer carries `PHASE_1_STUB_MESSAGE` and points at the correct new API. | |
| 89 | + | `elastos-vz` | `vz_provider_set_network_for_vm_returns_typed_migration_error_after_phase3_day2` (new) | Same shape — confirms the entitlement requirement is named for bridged-mode. | |
| 90 | + | `elastos-vz` | `vz_provider_session_and_network_still_fail_closed_with_stub` (retired) | Replaced by the two tests above; the old assertion (`contains(PHASE_1_STUB_MESSAGE)`) is now the *opposite* of correct behaviour. | |
| 91 | + |
| 92 | +5. **Docs:** this file + the status header on |
| 93 | + [`PHASE_3_DAY_1_PORT_PLAN.md`](PHASE_3_DAY_1_PORT_PLAN.md); |
| 94 | + `PLAN.md` Phase 3 line; `MAC.md` capability matrix. |
| 95 | + |
| 96 | +### Apple-API constraint that shaped Day 2 |
| 97 | + |
| 98 | +`VZVirtualMachineConfiguration` is frozen the instant |
| 99 | +`VZVirtualMachine::initWithConfiguration:queue:` is invoked. Any |
| 100 | +"mutate after load" API shape (`append_boot_args_for_vm`, |
| 101 | +`set_session_for_vm`, `set_network_for_vm`) is unsupportable on |
| 102 | +Vz. The Phase-3-Day-1 plan already named this; Day 2 finishes |
| 103 | +the cleanup by retiring the last two stubs and rerouting all |
| 104 | +boot-arg composition through `VmConfig` **before** |
| 105 | +`load_with_vm_config` runs. |
| 106 | + |
| 107 | +This is also why TAP rejection runs **before** any VM work: |
| 108 | +once `VZVirtualMachine` is initialised, no networking change is |
| 109 | +possible. Failing early keeps the error close to the cause. |
| 110 | + |
| 111 | +### What is still *not* working after Day 2 |
| 112 | + |
| 113 | +- `elastos ps` does not list the Mac capsule. The VM boots and |
| 114 | + then stops cleanly because the supervisor's `running` map |
| 115 | + cannot hold a Vz handle yet — `CapsuleBackend::Vm` wraps |
| 116 | + `Box<elastos_crosvm::vm::RunningVm>`. Day 3 adds a `VzVm` |
| 117 | + variant and the matching insertion. |
| 118 | +- `elastos stop <handle>` therefore cannot stop a Mac capsule |
| 119 | + either (Day 3). |
| 120 | +- The Carrier bridge listener exists on `<socket_dir>/<handle>-carrier.sock`, |
| 121 | + but the Vz console attachment in `ffi/console.rs::build_carrier_console_slot` |
| 122 | + is still a placeholder (does not yet forward bytes between |
| 123 | + the Unix socket and the guest's `/dev/hvc1`). Day 4 wires the |
| 124 | + real socketpair. |
| 125 | +- vsock from host → guest does not work yet. Day 5. |
| 126 | +- TAP networking (guest reachable from host LAN) is rejected |
| 127 | + with a typed error. Phase 3 Day 4+ work — needs Apple |
| 128 | + entitlement. |
| 129 | +- Booting any real first-party capsule end-to-end. Day 5 of |
| 130 | + Phase 2 already proved a synthetic Linux guest boots; Day 2 |
| 131 | + does not re-prove that, and the placeholder Carrier |
| 132 | + attachment means a real capsule launched via |
| 133 | + `elastos start <name>` would boot but not be reachable. |
| 134 | + |
| 135 | +### Linux-untouched evidence |
| 136 | + |
| 137 | +- `scripts/check-linux-untouched.sh bcf5a0a`: green. |
| 138 | +- The Linux `start_capsule_vm` flow (`launch_capsule` L957-1213) |
| 139 | + is byte-identical to the pre-Day-1 commit. The macOS arm |
| 140 | + early-returns through `start_capsule_vm_macos`; the Linux arm |
| 141 | + is compiled but unreachable on Mac. |
| 142 | +- All `elastos-crosvm` tests pass on Linux CI without |
| 143 | + modification. |
| 144 | + |
| 145 | +### Day 3 handoff |
| 146 | + |
| 147 | +The next slice ([Day-1 audit table row L1197-1210](PHASE_3_DAY_1_PORT_PLAN.md#audit-table)): |
| 148 | + |
| 149 | +- Add `CapsuleBackend::VzVm(Box<elastos_vz::vm::RunningVm>)` to |
| 150 | + the supervisor's `CapsuleBackend` enum (or an erasure trait). |
| 151 | +- In `start_capsule_vm_macos`, replace the Day-2 fail-closed |
| 152 | + exit with a real `RunningCapsule` insertion into `self.running` |
| 153 | + — same map the Linux arm uses, same `handle` key. |
| 154 | +- Wire `CapsuleStatus`, `WaitCapsule`, `StopCapsule` to dispatch |
| 155 | + through the new variant. |
| 156 | +- `elastos ps` / `elastos stop` start working on Mac for the |
| 157 | + first time. |
| 158 | + |
| 159 | +After Day 3, the supervisor seam is complete. Days 4-5 then |
| 160 | +attach real bytes to the Carrier console + vsock paths. |
0 commit comments