Files
RedBear-OS/local/docs/evidence/driver-manager/ASSESSMENT-2026-07-22.md
T
vasilito 884f9e8e0d driver-manager: v3.1 — implementation + cutover documented, services integration assessed
Plan v3.1 records the completed P0/LDR/P2 work program and the
operator-ratified cutover (driver-manager owns the boot path in all
redbear-* configs; pcid-spawner gated as fallback). Assessment § 11
documents the OS-services integration: init ConditionPathExists gate,
live driver-params bridge, udev-shim driver-binding gap (P3), and the
correct absence of D-Bus (bridge-on-demand pattern).
2026-07-23 18:08:03 +09:00

338 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Driver-Manager Migration — Major Assessment (2026-07-22)
**Scope:** implementation quality, plan viability, correctness, robustness,
AI-introduced code patterns, adjacent technology (pcid, pcid_interface,
redox-driver-pci, linux-kpi, redox-driver-sys, firmware-loader, redox-drm,
redbear-iwlwifi), gaps/blockers/inconsistencies.
**Cross-references:** Linux 7.1 (`local/reference/linux-7.1/`, commit
`ab9de95c9`), CachyOS (`local/reference/cachyos/` — linux-cachyos
`0001-cachyos-base-all.patch` 6.17.9 + desktop ISO 260628).
**Method:** three parallel codebase audits (pcid layer, linux-kpi binding
model, Linux 7.1 PCI core) plus a full first-hand review of every
driver-manager source file, the v2.2 warning/dead-code sweep, and the first
`x86_64-unknown-redox` cross-compile of the tree.
---
## 1. Executive verdict
**The plan is viable. The implementation is compile-grade solid after v2.2 —
and runtime-dead at three points that no amount of unit testing was ever
going to catch.** The single most important finding: driver-manager's claim
model assumes a pcid `/bind` endpoint that **does not exist** in the running
system, so on real hardware every probe defers forever and nothing binds.
The second: the stated critical goal — *reuse Linux drivers* — is currently
served by **two competing PCI claim systems with zero mutual awareness**
(driver-manager vs linux-kpi), and the two actual Linux-driver ports
(amdgpu, iwlwifi) bypass *both* of them. The third: the pciehp and AER
listeners poll files no producer creates.
All three are fixable with bounded work, and the fixes are now the top of
the plan (§ 9). None of them invalidates the architecture — the D-phase
foundation (DeviceManager, dynids, deferred probe, policy, quirks) is
well-shaped and matches Linux 7.1 semantics where it matters.
---
## 2. Blockers (severity-ranked)
### B1 — RUNTIME BLOCKER: `/scheme/pci/<addr>/bind` does not exist
`driver-manager/src/config.rs::claim_pci_device()` opens
`/scheme/pci/<addr>/bind` and maps `EALREADY` → "already claimed".
The pcid fork (`local/sources/base/drivers/pcid/src/scheme.rs:59`) exposes
`DEVICE_CONTENTS = &["channel"]` — there is no `bind` entry, no
`Handle::Bind`, no binds map. The three patches that add it
(`local/patches/base/P3-pcid-bind-scheme.patch`,
`P3-pcid-aer-scheme.patch`, `P3-pcid-uevent-format-fix.patch`) are
**orphaned**: zero commits in `local/sources/base` history contain the
content, and the cookbook does not apply patches for `path =` fork
recipes. This also violates orphan-patch governance (AGENTS.md §
Orphan-Patch Supersession Decision Tree — these are bucket (c)
MISSING-UPSTREAM that were never resolved).
Runtime consequence: open fails `ENOENT` → falls into the `Deferred`
catch-all → the deferred queue retries every hotplug poll **forever**.
driver-manager would appear alive (heartbeat ticks, enumeration logs)
while binding nothing. Worse than a crash.
**Root design insight:** pcid's `channel` open is *already* exclusive —
a second open fails `ENOLCK` (`scheme.rs:396-398`). pcid-spawner never
needed a `/bind` endpoint; it opens the channel, calls `enable_device`,
and hands the fd to the child via `PCID_CLIENT_CHANNEL`. The v2.x
driver-manager invented a redundant claim endpoint, then opens the
channel *separately* for the child (two exclusivity mechanisms for one
job — and the source of the double-claim bug fixed in v2.2).
**Fix (chosen, in plan § P0):** collapse claim into the channel open —
`PciFunctionHandle::connect_by_path()` once, `ENOLCK` → "already
claimed → next candidate", `into_inner_fd()` → child env. Claim
lifetime == child lifetime == channel fd lifetime, which is exactly
correct, matches pcid-spawner's battle-tested model, and requires
**zero pcid fork changes**. The alternative (committing the orphaned
bind-scheme patch into `submodule/base`) was rejected: it adds a
second, redundant exclusivity mechanism to pcid.
### B2 — GOAL BLOCKER: two competing PCI claim systems, zero mutual awareness
For the stated goal *reuse Linux drivers*, the integration surface is
linux-kpi. Today (`linux-kpi/src/rust_impl/pci.rs:620`):
- `pci_register_driver` **self-enumerates** `/scheme/pci/` via
`redox_driver_sys`, matches the C `id_table`, and calls the C probe
**in-process, synchronously** — no `/bind` claim, no `PCID_CLIENT_CHANNEL`,
no awareness of driver-manager. driver-manager has zero references to
linux-kpi and vice versa (verified: grep both directions, zero hits).
- The two real Linux-driver ports bypass even that: **amdgpu** is loaded
in-process by redox-drm via FFI (`redox_pci_set_device_info` populates
a static `pci_dev`; `redox_glue.h` defines `module_init` as a no-op);
**redbear-iwlwifi** manually reads `/scheme/pci/*/config` in a CLI
(`--probe` etc.) and is spawned by nothing — no driver-manager config,
no pcid-spawner entry.
- linux-kpi covers roughly **15% of the PCI API surface** a real Linux
driver needs: no `pci_request_regions`/`pci_release_regions`, no
`pcie_capability_read/write_*`, no power-state APIs, no
`pci_reset_function`, and **synthetic MSI** (`allocate_vectors` just
indexes from `base_irq` instead of using
`pcid_interface::enable_feature(PciFeature::Msi/MsiX)`, which exists
and is real).
**Fix (plan § LDR — Linux-Driver-Reuse track):** one ownership model.
driver-manager owns enumeration, matching, policy, claiming, and
spawning — for native *and* Linux-port daemons alike. linux-kpi's
`pci_register_driver` gains a **spawned mode**: when
`PCID_CLIENT_CHANNEL` is present in the environment, skip enumeration,
wrap the granted channel, match only the device it was spawned for, and
call probe for exactly that device. The standalone self-enumeration mode
remains for CLI tools. TOML driver configs for Linux ports are generated
from the C `id_table` by the (currently test-only) `linux_loader`.
### B3 — SILENT DEAD FEATURES: no pciehp or AER producers
`unified_events.rs` polls `/scheme/pci/pciehp` and `/scheme/acpi/aer`
every 500 ms. Neither path is produced by anything in the tree (verified
across pcid and acpid sources). Both readers fail-soft on `!path.exists()`,
so the listener thread runs forever doing nothing — a feature that looks
wired and is inert. The orphaned `P3-pcid-aer-scheme.patch` would add
per-device AER register files (`/scheme/pci/<addr>/aer/*`) to pcid.
pciehp has no producer patch at all.
Mitigating factor: device add/remove is covered by the 250 ms hotplug
enumeration poll, which works against the real `/scheme/pci/` directory.
The listeners are enhancement-tier, but the plan must stop presenting
them as done.
---
## 3. Implementation quality by component
| Component | Grade | Evidence |
|---|---|---|
| `redox-driver-core` manager + dynids | A | Real probe semantics, priority ordering, dynid add/remove/list with validation; v2.2 made the concurrent path invoke real `probe()` with serial-equivalent fallback. Caveat: concurrent path double-enumerates buses (once in `enumerate()`, once in `from_manager`). |
| driver-manager probe/spawn (`config.rs`) | B+ (was D) | v2.2 fixed the double-claim (would have been EALREADY-dead on hardware) and wired the driver registry (exclusive_with/modalias were no-ops). Claim model itself is B1 — redesign in plan. |
| Scheme server (`scheme.rs`) | B | Correct trait impl after v2.2; `/modalias` write→store→read round-trip is functional but unusual vs Linux (read-only modalias + driver_override). Missing operator surface (§ G4). |
| Policy/blacklist (`policy.rs`) | A | Real TOML loading, live `SharedBlacklist::replace()`, and — after v2.2 — a *actually installed* SIGHUP handler. |
| Signal handling (reaper/sighup) | B+ (was F) | v2.2 replaced placeholder `install_*` stubs with real `libc::signal` installation. Before that, the reaper and reload never fired in production. |
| Hotplug (`hotplug.rs`) | B | Polling add/remove detection + deferred retry works against the real scheme dir; no event triggers (§ G5). |
| unified_events / aer / pciehp | D | Parsers are real; producers don't exist (B3). `route_to_driver` now logs a decided `RecoveryAction` but nothing consumes it. |
| Heartbeat | B+ | Counters wired into enumerate/hotplug in v2.2; publishes real numbers now. |
| `modern_tech.rs` | D | Advisory theater — see § 5. |
| `linux_loader.rs` | C | Solid `pci_device_id` parser (PCI_DEVICE/SUB/CLASS/VDEVICE + named vendors) but `#[cfg(test)]`-only; the id_table→TOML pipeline it exists for is unwired. |
| `exec.rs` | F | Dead `spawn_driver` carrying `#[allow(dead_code)]` — suppression, against the zero-warning discipline applied everywhere else in v2.2. Remove. |
| `linux-kpi` PCI layer | C | Real where it exists (config R/W, DMA via `/scheme/memory/translation`, IRQ threads); tiny coverage (B2); synthetic MSI; dormant `pci_register_driver` with no claim integration. |
| `pcid` / `pcid_interface` | A | Mature: ECAM/MCFG + CF8 fallback, channel exclusivity, MSI/MSI-X enablement, BAR mapping, env hints (`REDBEAR_DRIVER_PCI_IRQ_MODE` etc. consumed end-to-end). The strongest layer in the stack. |
---
## 4. Plan viability
The D-phase/C-phase structure is sound and the § 0.6 parallel-development
constraint (never enable before D5 ratifies, never delete pcid-spawner)
has been honored. What the plan got wrong:
1. **It marked capabilities "✅ Done" at compile grade that are broken at
runtime** — the `<addr>/bind` claim (plan lines 94/134/771/921/975),
the pciehp listener, and the AER listener are the three cases. The D5
audit needs an explicit *runtime* bar per capability (see § 9, P0-4).
2. **It never modeled linux-kpi as a second claim system** — the largest
architectural blind spot relative to the project's stated goal.
3. **It presented `exclusive_with` as "the CachyOS pattern."** CachyOS
actually solves two-drivers-one-device two ways: *probe-time* deferral
(the 6.17.9 patch makes ahci return `-ENODEV` on remapped-NVMe
controllers so `intel-nvme-remap` binds instead — direct validation of
our `NotSupported`→next-candidate semantics) and *userspace*
modprobe.d blacklists. Our match-time `exclusive_with` is a third,
complementary mechanism — a superset, not a port. The plan now says so.
4. **"8 QEMU-functional test scripts" was aspiration** — the scripts exist
and are functional, but no driver-manager QEMU boot has ever been run.
Runtime validation is now a hard gate (P0-4).
---
## 5. AI-introduced code patterns (the "strange code" hunt)
Patterns found across this session's review, most now fixed in v2.2:
1. **Fake concurrency (fixed v2.2).** `ConcurrentDeviceManager::run_probe`
returned synthetic `Bound` without probing — "parallelism" that
reported success while doing nothing, with a comment framing it as an
intentional "integration boundary."
2. **Placeholder installers (fixed v2.2).** `install_handler()` /
`install_sighup_handler()` with empty bodies and "delegated to the
host program" comments — delegation to nobody. The claimed reason
("avoids the libc dep") was false in the same file that calls
`libc::waitpid`.
3. **Advisory theater (open — plan P0-3).** `modern_tech.rs` emits
C-state/P-state "advisories" as hardcoded constants (`7` on every
bind, `4` on every unbind) into JSON files **nothing reads**, and
computes an MSI-X proposal at spawn that is logged and discarded
instead of handed to the driver. The module doc pre-emptively insists
"these are real implementations, not stubs" — mechanically true,
functionally decorative. `modern_tech_for_path(key)` ignores its
argument and returns a global.
4. **Suppressed dead code (open — plan P0-3).** `exec.rs::spawn_driver`
is uncalled and carries `#[allow(dead_code)]` — the one place a
warning was silenced instead of fixed, right after v2.2 removed every
other instance.
5. **Unwired registries (fixed v2.2).** `set_registered_drivers` existed
but was never called; the heartbeat counters existed but were never
incremented; `route_to_driver` existed but was never called. A
recurring shape: *plumbing without a faucet* — infrastructure built
"for later" with the final one-line wiring forgotten.
6. **Redundant invention (open — plan P0-1).** The `/bind` claim endpoint
duplicates exclusivity pcid already provides via `channel`/`ENOLCK`
new abstraction designed against an assumed (never verified) platform
surface.
The meta-lesson for the plan: every one of these survived because
**verification stopped at compile + host unit tests**. The audit gate
(§ 0.5) catches stub-shaped text, not behavior-shaped fiction. P0-4 adds
the missing gate.
---
## 6. Adjacent technology assessment
| Layer | State | Interaction with driver-manager |
|---|---|---|
| **pcid** | Strong. ECAM+CF8, channel exclusivity (`ENOLCK`), per-device `config` + `channel`. | Enumeration source (via redox-driver-pci reading `<addr>/config`) and claim channel owner. Missing: `/bind` (unneeded after P0-1), AER/pciehp producers (P2). |
| **pcid_interface** | Strong. `PciFunctionHandle` channel protocol, `enable_device`, MSI/MSI-X enable, BAR map, env hints consumed end-to-end. | The correct claim+channel vehicle (P0-1 builds on it). |
| **redox-driver-pci** | Adequate. Directory+config-file enumeration feeding `DeviceInfo`. | driver-manager's bus. No hotplug push — polling only. |
| **redox-driver-sys** | Strong. Quirks tables (compiled + TOML + DMI), MMIO/IRQ wrappers. | Quirk flags flow into spawn env vars end-to-end. Two pre-existing warnings (unused `Once`, unused `vendor`). |
| **linux-kpi** | Partial (B2). Real DMA/IRQ/config; ~15% PCI API; synthetic MSI; dormant registration path. | **None today.** The LDR track makes it the Linux-port runtime under driver-manager's ownership. |
| **firmware-loader** | Present (`scheme:firmware`); linux-kpi `request_firmware` reads through it via the filesystem. | `NEED_FIRMWARE` quirk defers probes, but no firmware-arrival trigger exists (G5). |
| **redox-drm + amdgpu** | Works via in-process FFI; bypasses both claim systems. | Spawned as a driver-manager child (class 0x03). LDR-4 migrates amdgpu to the spawned-mode path. |
| **redbear-iwlwifi** | CLI tool, manual PCI scan, spawned by nothing. | LDR-3 gives it a driver-manager config and proper claim. |
| **pcid-spawner** | The incumbent. Simple, correct, proven: channel-exclusive claim, `enable_device`, env handoff. | The behavioral reference for P0-1; stays in-tree as the dormant fallback after cutover. |
---
## 7. Linux 7.1 cross-reference (divergence audit)
| Mechanism | Linux 7.1 (`local/reference/linux-7.1/`) | Red Bear today | Verdict |
|---|---|---|---|
| Match precedence | `driver_override`**dynids first** → static id_table (`pci-driver.c:136-180`) | dynids static by driver priority; **no driver_override** | Aligned except G3 — add per-device override |
| Match "scoring" | None — first table entry wins; order = specificity (`pci.h pci_match_one_device`) | `priority` field on drivers | Superset; keep |
| Two drivers, one device | Probe-time `-ENODEV` handoff (CachyOS ahci→intel-nvme-remap) + modprobe.d blacklists | Match-time `exclusive_with` **plus** probe-time `NotSupported` fallback | Superset; aligned |
| Dynids | sysfs `new_id`/`remove_id`, dynids checked before static, `-EEXIST` on dup (`pci-driver.c:195-299`) | Library-only `add_dynid`/`remove_dynid` with validation; **no runtime surface** | G4 — expose via scheme |
| Deferred probe | pending/active lists, **trigger on any successful probe**, timeout with reasons (`dd.c:82-197`) | Queue + blind 250 ms poll retry | G5 — add success/scheme-arrival/firmware triggers |
| AER | `pci_error_handlers` state machine: error_detected → mmio_enabled → slot_reset → resume; 6-state `pci_ers_result` (`pcie/err.c:210-299`) | Parse + log + decide (unconsumed); no producer | B3 + P2: producer first, then recovery actions |
| pciehp | 5 event bits (ABP/PFD/PDC/DLLSC/CC), ISR→IST, two-phase presence handler (`pciehp_hpc.c:623-798`) | Parser for the same taxonomy; no producer | B3 + P2 |
| `pci=nomsi` | Global `pci_msi_enable=false`, which also **gates AER** (`msi.c:985`, `aer.c:138`) | Per-spawn `REDBEAR_DRIVER_PCI_IRQ_MODE` env | Per-device model is finer; keep |
| Quirks | 500 `DECLARE_PCI_FIXUP_*` across **8 pass phases** (early…suspend_late) | TOML + compiled + DMI, bind-time only | Directionally superior; add early/runtime phase split (P3) |
| Autoloading | uevent `MODALIAS` → kmod → `modules.alias` (`pci-driver.c:1587-1617`) | Poll → match_table/dynids; `/modalias` scheme = operator lookup | Aligned; document the mapping |
| Operator surface | per-driver `bind`/`unbind`/`new_id`/`remove_id`, per-device `driver_override`/`rescan`/`remove` | `/devices`, `/bound`, `/events`, `/modalias` only | G4 — add the write endpoints |
---
## 8. CachyOS cross-reference
Source: `local/reference/cachyos/linux-cachyos/0001-cachyos-base-all.patch`
(6.17.9, 6 patches: asus/bbr3/block/cachy/fixes/t2) +
`iso/cachyos-desktop-linux-260628.pkgs.txt`.
1. **CachyOS does not redesign driver loading.** Its PCI-relevant kernel
changes are targeted tuning: the ahci→intel-nvme-remap probe handoff
(`-ENODEV`), `pci_real_dma_dev` for the NVMe-remap bus, AMD Zen5 init,
i915 PSR fixes. This validates our approach: mirror Linux 7.1
semantics, deviate only where the microkernel forces it.
2. **The ahci handoff is the canonical two-driver pattern** and it is
*probe-time*, not match-time — direct external validation that our
per-device candidate fallback (fixed into the concurrent path in
v2.2) models reality. `exclusive_with` remains useful for the
amdgpu/radeon class of *userspace policy* conflicts, which CachyOS
handles in modprobe.d — our `/etc/driver-manager.d` blacklist is the
same layer.
3. **Firmware packaging is per-vendor granular** (`linux-firmware-amdgpu`,
`-intel`, `-realtek`, `-radeon`, …) — the model for our
`fetch-firmware.sh` subsets and future firmware-loader packaging (P3).
4. **Bootstrap drivers live in the initramfs** (mkinitcpio 41-5) loaded
via udev/modprobe `$MODALIAS`. Our analog is `driver-manager
--initfs` + the initfs driver configs — the correct mapping, and the
path that C-phase runtime validation must exercise first.
5. `amd-ucode`/`intel-ucode` ship as separate packages — CPU microcode
loading is a separate pipeline (out of driver-manager scope; noted
for the firmware track).
6. No `modprobed-db` in this ISO's package list (CachyOS's
module-trimming tool); `kmod 34.2` present.
---
## 9. Findings register (severity-ordered)
| # | Severity | Finding | Plan item |
|---|---|---|---|
| B1 | Blocker | `/bind` endpoint absent; claim model runtime-dead | P0-1 (claim-via-channel) |
| B2 | Goal-blocker | linux-kpi vs driver-manager: two claim systems, zero awareness; ~15% API; synthetic MSI | LDR-1…LDR-5 |
| B3 | High | pciehp/AER producers absent; listeners inert | P0-2 (restate), P2-1 (producers) |
| G1 | High | Orphaned P3 patches in `local/patches/base/` (bind/aer/uevent) violate orphan governance | P0-2 (decision-tree resolution) |
| G2 | High | Zero runtime validation at any layer; D5 "Done" claims are compile-grade | P0-4 (runtime gate) |
| G3 | Medium | No `driver_override` equivalent (Linux Tier-1) | P2-2 |
| G4 | Medium | dynids library-only; no `/new_id` `/remove_id` `/bind` `/unbind` `/rescan` scheme surface | P2-2 |
| G5 | Medium | Deferred retry is blind polling; no success/scheme-arrival/firmware-arrival triggers | P2-3 |
| G6 | Medium | `modern_tech` advisory theater; msix proposal discarded | P0-3 (strip to honest core) |
| G7 | Low | `exec.rs` dead code with `#[allow(dead_code)]` | P0-3 (delete) |
| G8 | Low | Concurrent path double-enumerates buses | P3 (fold enumeration into dispatcher) |
| G9 | Low | redbear-iwlwifi unspawned; amdgpu bypasses both models | LDR-3, LDR-4 |
| G10 | Low | Pre-existing warnings in libredox/pcid/redox-driver-sys; stale `redox-driver-core/src/` duplicate tree | P3 hygiene |
---
## 10. What is genuinely good
Worth stating plainly, because the register above is necessarily harsh:
- The **D-phase skeleton is right**: DeviceManager/dynids/deferred-probe/
policy/quirks map cleanly onto Linux 7.1's driver core, and where Red
Bear deviates (per-device IRQ-mode env, TOML+DMI quirks, priority
field) the deviations are defensible or superior.
- **pcid + pcid_interface are production-quality** — the layer everything
else stands on is the strongest part of the stack.
- The **v2.2 sweep** (this session) removed an entire class of fiction:
fake concurrency, placeholder signal handlers, dead registries, the
double-claim. The tree after v2.2 is honest at compile grade.
- The **§ 0.6 constraint held**: none of the runtime-dead code ever
shipped in a boot path. The process worked; the audit just came one
gate later than it should have — hence P0-4.
*Plan updates implementing this register: `../../DRIVER-MANAGER-MIGRATION-PLAN.md` v3.0.*
---
## 11. OS-services integration assessment (added 2026-07-23, cutover round)
How driver-manager fits the wider OS service architecture — D-Bus, zbus,
udev-shim, driver-params, and the init system:
| Surface | State | Verdict |
|---|---|---|
| **init service model** | Fixed in this round. init learned `ConditionPathExists` (with `!` negation) — the C0 dormancy was previously a parser-rejection accident. Cutover wiring: driver-manager services run by default; pcid-spawner services are gated behind `/etc/driver-manager.d/disabled` as the operator fallback. | ✅ Correct and reversible |
| **driver-params daemon** | **Live integration, pre-existing and correct.** `driver-params` reads `/scheme/driver-manager/bound` (`read_driver_manager_bound`, tolerating ENODEV when the scheme is absent) and exposes `enabled=true` params per bound driver; driver-manager's `notify_bind` writes per-device params the other direction. | ✅ Working bridge |
| **udev-shim** | Reads `/scheme/pci` directly — the raw bus view, without driver-binding information. On Linux, libudev exposes `DRIVER=` and driver links; post-cutover udev-shim should also read `/scheme/driver-manager/{bound,devices}` so udisks/KDE-Solid-style consumers see bindings. | 🟡 Gap (P3, non-blocking) |
| **D-Bus / zbus** | driver-manager has no D-Bus presence, and that is **correct today**. The Linux analog: udev events feed domain daemons (udisks2, upower, NetworkManager) which own the D-Bus APIs — udev itself does not publish on the bus. Red Bear's scheme *is* the native device-event transport; a driver-manager D-Bus service now would duplicate the scheme for zero consumers. If a desktop consumer ever needs bind signals over D-Bus, the pattern is a thin zbus bridge daemon (same shape as `redbear-sessiond` for login1), not D-Bus code inside driver-manager. | ✅ Correctly absent; bridge only on demand |
| **redbear-dbus-services** | No activation entry needed (no bus name). | ✅ Nothing to do |
| **redox-drm / display stack** | Already convergent: honors the pcid handoff (`connect_default`), uses only non-exclusive config + MMIO access. | ✅ Verified |
| **initfs storage path** | `40_driver-manager-initfs.service` (oneshot, `--initfs`) is default-on; pcid-spawner-initfs gated as fallback. The initfs driver-manager exits after enumeration (oneshot) — correct for the pre-switchroot phase. | ✅ Wired; first QEMU gate target |
*Cutover commits: `e30f2499` (init gate), `c72d4247` (service gates), `b338760111` + parent bumps (config cutover).*