503d863fcd
On Windows the updater renames the live `hermes*.exe` shims aside (`hermes.exe.old.<unix-ms>`) so uv can write replacements. When that quarantine succeeds but the install then fails, the recovery path could leave the install with no `hermes` on PATH at all — unrecoverable in place, because the command that would repair it IS `hermes update` (#75584). Restoring a quarantined shim happens at three sites: the updater, the early-recovery installer, and the startup sweep's orphan rescue. Each was a single un-retried rename whose OSError was swallowed in silence, while the OUTBOUND quarantine rename already retried a lock. That is backwards — a failed quarantine merely aborts an update, a failed restore removes `hermes` from PATH — and the two sites that had messages had already drifted apart. - `_early_recovery.restore_quarantined_shims()` is now the single implementation: retry ladder, one recovery message, returns the pairs it could not restore. It lives in the stdlib-only module that both `main` and `_install_repair` already import, so the layers cannot drift again. A pair is not a failure when the original reappeared or the quarantine file vanished — two processes sweeping the same orphan must not produce a spurious error. - `_cleanup_quarantined_exes` unlinked every `*.exe.old.*` on each invocation. When the original shim was already missing, that .old file was the ONLY surviving copy — deleting it converted a one-rename recovery into a full reinstall. It now rescues the orphan through the shared helper instead, and leaves anything inside a 15-minute grace window alone so it cannot destroy a concurrent update's in-flight quarantine. - Ordering is by the PARSED `.old.<unix-ms>` stamp, not the raw filename. Lexicographic ordering only tracks recency while every stamp shares a digit width; a stray `.old.999` sorts above a 13-digit epoch-ms stamp and would be the copy rescued onto the live shim name. - Names whose suffix does not parse as int-ms are not ours: never rescued, never deleted. The sweep should not destroy files whose provenance it cannot establish, and they are not produced by the quarantiner. The stamp is read from the filename rather than st_mtime because `rename` preserves the original shim's mtime, which records when uv wrote the shim — days earlier, in general — not when it was quarantined. A regression test pins that distinction. Messages go to stderr: the sweep runs on EVERY hermes invocation and `hermes acp` speaks JSON-RPC on stdout. Scope note: `_quarantine_running_hermes_exe` is deliberately byte-identical to main here. Why the outbound rename fails in the first place (the launcher holding its own image without FILE_SHARE_DELETE) is #88121's subject; this is the net underneath, covering the case where quarantine SUCCEEDS and the install dies afterwards. The two touch disjoint functions and can merge in either order. Reproduced and verified on Windows 11 (26200), Python 3.11.15: stranded the shims, confirmed a normal `hermes` invocation now rescues the orphan instead of deleting it, and confirmed an exhausted rescue prints the recovery command. 16 new tests; 35 pass across the four quarantine suites.