63ff4b87b6
`hermes desktop` still failed with EBADENGINE demanding Node >=26 after #76562, on a machine whose `apps/desktop/package.json` already said `^20.19.0 || >=22.12.0`. #76562 fixed the manifest but not its mirror in `package-lock.json`, and `npm ci` reads engines from the lockfile: package.json apps/desktop -> {'node': '^20.19.0 || >=22.12.0'} package-lock apps/desktop -> {'node': '>=26.0.0'} <- what gated Chasing that exposed a second, pre-existing problem: the floor #76562 declared was too generous. Running the real `npm ci` against the whole workspace on Node 22.21.1 fails on a transitive dependency — npm error notsup Not compatible with your version of node/npm: react-router@8.3.0 npm error notsup Required: {"node":">=22.22.0"} react-router 8.3.0 (a direct dependency of both `apps/desktop` and `web`) declares `>=22.22.0`, which is tighter than Vite's `^20.19 || >=22.12` and excludes all of Node 20. So `>=20.0.0` promised support the tree cannot deliver: an install on Node 20 or early 22 passed the installer's gate and then died inside `npm ci` on someone else's package. All four engine declarations now state the floor the dependency tree actually has, `>=22.22.0`: root `package.json`, `apps/desktop/package.json`, and both of their `package-lock.json` mirrors. The installer gates move with them (`node_satisfies_build` in install.sh, `Test-NodeVersionOk` in install.ps1) so a too-old system Node is replaced with the managed one *before* npm runs, and the failure a user does see names hermes-agent rather than a transitive package. NODE_VERSION stays 22 — latest-v22.x is 22.23.2, comfortably above the floor. The invariant test gains the case that would have caught the mirror drift on its own: the desktop assertion now pins the tightest floor a dependency actually declares, and the managed-runtime check compares majors, since install.sh fetches latest-v{major}.x rather than {major}.0.0. Verified with real `npm ci --dry-run` over the full workspace: - node 22.23.2 (what install.sh provisions) -> 1258 packages - node 26.5.1 -> 1189 packages - node 22.21.1 (below the floor) -> EBADENGINE naming hermes-agent, i.e. our own manifest, not react-router