Round 4 of helix4u's review — the durable fallback is now real:
- Result protocol gains `manual`: an ok result the user still must act
on (reopen the app, reinstall the GUI package, fix the sandbox helper).
Both orchestrators set it on every DONE_NOTE/downgrade path; the Desktop
consumer surfaces manual results in a real dialog on next boot instead
of a log line — the browserless-Linux disappearance now ends at a
visible dialog, worst case one boot later. Older result files without
the field parse as manual:false (covered).
- notify ladder verifies EXECUTION, not existence: zenity/kdialog must
survive their first second (an instant death means no display and falls
through); the no-surface case is an explicit best-effort contract whose
guaranteed channel is the result dialog.
- mac DONE_NOTE + failed relaunch of the kept/rolled-back bundle is no
longer swallowed (`|| true` dropped): the durable message carries both
facts.
- launch/gate matrices assert `manual` in the result JSON; consumer
round-trip tested in handoff-result.test.ts.
gille's round 3:
- cd into the install root FAILS CLOSED (set -u without set -e let a
failed cd continue hermes update in the caller's tree -- the exact
wrong-tree class the correction exists to kill). Honest result, exit 3.
- A supplied mac relaunch target that is missing is a REJECTED launch ->
manual downgrade; the launch matrix asserts the downgrade instead of
codifying the old false success. A mac swap-failure DONE_NOTE now still
relaunches the kept/rolled-back bundle before publishing manual.
- notify_fallback: every rung falls through on EXECUTION failure (a
notify-send that can't reach D-Bus no longer eats the message), mac
gets osascript (present on every macOS -- Safari-only machines have no
chromium shim), and the no-surface terminal case is an explicit logged
contract: the result file carries the outcome to the next boot.
- update:repro:fresh passes --non-interactive explicitly (prompt_yes_no
falls back to /dev/tty, so </dev/null was not equivalent).
gille's round-2 review: the terminal lifecycle claimed outcomes the
launch hadn't delivered yet.
- posix finish() reorders: outcome -> durable result+marker -> LAUNCH
WITH ACCEPTANCE -> terminal event. mac acceptance is open's exit code
(launchd rejects broken bundles loudly); linux verifies the setsid
child is still alive 1.5s after spawn, so an instant exec failure
downgrades to a held 'manual' state + truthful result instead of a
vanished 'done'. Gated skew/manual outcomes publish a real 'manual'
event (new third shim state -- still zero logic in the page).
- Renderer-free linux recovery: when no chromium-family browser exists,
manual/error outcomes fire notify-send/zenity/kdialog best-effort so a
gated non-relaunch is never a silent disappearance.
- windows.ps1 mirrors the contract: Start-DesktopRelaunch returns
verified acceptance (WMI pid alive / fallback process alive; dying
before the window appears counts as failure), and the finally block
downgrades to Show-ManualFinale + rewritten result when the launch
didn't land. Error path still relaunches after showing itself.
- repro.sh launch / npm run update:repro:launch: real-orchestrator
matrix for instant-exit relaunch downgrade and skew-message surfacing.
- posix.sh cds into the install root before hermes update (found by the
sandboxed behind-repro: parts of the update resolve the mutated tree
from cwd, which is the Desktop's cwd -- it updated the DEVELOPER'S
checkout while reporting success against the sandbox).
The posix orchestrator inherited the Desktop's cwd, and parts of the
update pipeline resolve the tree they mutate from the working directory
-- the sandboxed behind-repro caught it updating the DEVELOPER'S primary
checkout (cwd at spawn time) while reporting success against the
sandbox. cd "$INSTALL_ROOT" before running hermes update, matching the
cwd:updateRoot contract of the deleted in-app path. Verified: rerun
leaves the outside checkout untouched (reflog clean).
repro.sh fresh used a --no-interactive flag install.sh doesn't have;
non-TTY stdin (</dev/null) + --skip-setup is the real non-interactive
contract.
Address helix4u's review:
- finish() now delivers the outcome BEFORE publishing it: mac bundle swap
and the linux relaunch gate run first, then the result file, marker
removal, and the shim event -- the app launch itself goes last so it
can't race the result write. A gated/skewed linux install (AppImage/
deb/rpm, broken sandbox helper) surfaces its message in the result file
AND holds the shim window open with it instead of closing on a false
'Opening Hermes...'.
- mac swap is transactional with a checked rollback; a failed install
restores the previous bundle and the result says so (exit 7 when even
rollback fails). Failed 'open' rewrites the result truthfully.
- linux gate is an exact port of the deleted update-relaunch.ts logic:
anchored path-segment match on <root>/apps/desktop/release/linux-unpacked,
chrome-sandbox absent = namespace build = fine, present = root+setuid
required, with the real opt-outs (ELECTRON_DISABLE_SANDBOX, --no-sandbox
among replayed args, or the Desktop vouching) instead of the invented
HERMES_DESKTOP_NO_SANDBOX. collectRelaunchArgs/sandboxFallbackFromEnv
live in updater-process.ts again; the Desktop passes filtered launch
args (after --) and --relaunch-cwd so a deep-link or --no-sandbox
launch survives the update.
- result/status JSON strings are escaped (git permits '"' in branch
names) and the result write is atomic (tmp + rename).
- coverage: resolvePosixScriptHandoff + ported helpers in
updater-process.test.ts (19 pass); repro.sh gate / npm run
update:repro:gate asserts the whole gate matrix and round-trips a
hostile branch name through the result JSON.
scripts/desktop-update/repro.sh drives the real code paths against a
disposable HERMES_HOME under /tmp: shim/shim-fail (UI dry runs), fresh
(literal install.sh), behind N (rewound checkout driven forward by the
orchestrator), error (broken venv -> abort + result file). Exposed as
npm run update:shim / update:shim:fail / update:repro:* from
apps/desktop.
scripts/desktop-update/posix.sh is the mac/linux twin of windows.ps1:
the Desktop spawns it detached and QUITS; it waits the app out, runs
plain hermes update (retry-once across the update boundary, truthful
desktop-rebuild completion), swaps/relaunches the .app bundle (mac) or
the release/*-unpacked binary when its sandbox helper is launchable
(linux), writes .hermes-update-result.json, and drives the same shim.
Repo-owned, so every update refreshes the code that drives the next one.
resolvePosixScriptHandoff mirrors the Windows resolver (with the
flat-path fallback covering the scripts/ reorg skew).
scripts/desktop-update.ps1 moves to scripts/desktop-update/windows.ps1 (a
compat forwarder stays at the old path for one asar/checkout skew cycle)
and gains the shim: scripts/desktop-update/ui.html rendered in a
chromeless Edge app window, fed done|error over a loopback /progress
endpoint. The page is #75895's hand-off screen ported verbatim (Fourier
Flow loader, one title, one line, OS light/dark, charcoal dark seeds);
failure is the terse card pointing at hermes debug share. The WinForms
card stays as the no-Edge fallback, same shape.
Salvaged from the web-shell spike: TcpListener runspace server, Edge
--app spawn with throwaway profile, degradation ladder, -SelfTestUi.
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>