Commit Graph

8 Commits

Author SHA1 Message Date
Teknium
c18755d69e fix(update): a launch repairs a move that tore the repair's own code (D1)
The launch-time repair (hermes_bootstrap -> _early_recovery under the checkout lock) lives in
the tree a killed git rewrites. A merge SIGKILLed while writing hermes_bootstrap.py,
_early_recovery.py, a prefix of hermes_cli/__init__.py or a custody module left every launch
exiting 1 with the marker kept and the tree never restored.

arm_tree_move now publishes the recovery/lock/custody modules, as committed at the move's
pre commit (git cat-file, never the working tree), to <git dir>/hermes-update-recovery/<pre>/
by an atomic directory rename before it writes the marker. The minted launcher catches a
failing checkout import; with the marker present and its pre's closure published it purges
checkout modules, runs restore_interrupted_pull from that copy (same claim and checkout lock:
a live writer still refuses) and relaunches from the restored tree.

(cherry picked from commit 2bc12d9283d90a54f970a354a52f0a95d41bbfb0)
(cherry picked from commit 924b2a9a2d4dde6bc2a9074ff3ee0cd8f87c8684)
2026-10-06 06:20:32 -07:00
Teknium
47dcc975ce fix(update): the tree-move marker records the updater's absolute git for the launch repair
A merge killed while writing hermes_constants.py now reaches the repair (the launcher
imports hermes_bootstrap first), but on a Windows install whose only git is PM's store
copy the repair looked it up through pm, which imports hermes_constants: the lookup
failed, a bare 'git' died with WinError 2 and the tree stayed torn (native mid_git,
run 37198020181). arm_tree_move records the absolute git this run resolved (PATH or
expose_pm_git) and _git_executable prefers it.

(cherry picked from commit 59a744696d3805fdc9fb3f0bf5d5f3b20161cbce)
(cherry picked from commit 8bc8502e336d847a88fc4fd7c408f18d6950377f)
2026-10-06 06:20:31 -07:00
Teknium
b054f32fc7 fix(update): the commit point's git calls go through the one custody runner
update_cmd_commit's diff/show/rev-parse/status probes run through
update_custody.run_git like every other updater git call: -c gc.autoDetach=false
-c maintenance.auto=false, no lock fd for readers, the owner's kill-on-close job
on Windows (R2).

(cherry picked from commit ee8726ef171b9dd355aed80014c5feb3f0ac3256)
(cherry picked from commit cd2ca1f31296f9de2b3f14f2196e8fe67653bd5a)
2026-10-06 06:20:31 -07:00
Teknium
2cfa57f70b fix(update): a killed syntax rollback keeps its recovery record until the tree is whole
R1. The launch-time replay of a syntax rollback ran `git reset -q pre` before any
dead-lock reclaim and ignored its result; HEAD still on the broken release then
read as "git finished" and the marker was deleted, leaving the broken tree and a
stale index.lock with nothing left to restore it. In-process, the same path made
settle_failed_tree_move report the rollback complete.

Now: the killed step's index.lock is reclaimed first and only once its git is
proven gone (/proc fds + live git with cwd in the checkout on Linux, lsof on
macOS/BSD, the unlink itself on Windows; unknowable = keep); update-ref, reset
and the HEAD readback are each checked; a rollback marker is removed only when
HEAD == pre, no index.lock and no tracked change. The in-process rollback checks
drop_added_files and the same whole-tree oracle before removing the marker, and
says the next launch finishes it when it could not.

Was (reviewer probe, real git SIGKILLed at index.lock): restore returned False,
HEAD broken, lock left, marker gone. Now: restore returns True, HEAD pre, clean,
lock and marker gone; a live lock holder keeps the marker and the lock.

(cherry picked from commit 087ae72d38be89892a70e97a935db532a9e8284f)
(cherry picked from commit 5984c98726cb29b1123552f875f99d5020f01b71)
2026-10-06 06:20:30 -07:00
Teknium
98649fd74f fix(update): a historical updater's hand-off no longer dies on a missing ruamel
The takeover's new fleet-obligation arm imported `update_cmd_fleet`'s writer,
which imports `update_cmd` -> `hermes_cli.config` -> ruamel. `prepare()` runs
in the HISTORICAL interpreter before PM syncs the new dependencies, so every
update from a release whose venv has no ruamel (the installer-script and
e2e-upgrade core/handoff/pm legs) exited 1 with
`Update preparation failed: No module named 'ruamel'`. The arm now writes the
host obligation through the stdlib-only `update_host_obligation`, with the same
per-home breadcrumb fallback.

Also:
- The syntax rollback's mixed reset un-tracked the files the broken commit
  ADDED, and `reset --hard` never touches untracked files, so they stayed in
  the tree (`?? hermes_cli/`). They are now removed by name after the reset.
- A git that EXITED on another git's `index.lock` no longer has that lock
  deleted by the in-process settle: the lock is foreign by construction, the
  scan only reads, and a tree with writes to put back keeps its marker.
- `_early_recovery`'s git runs carry `cwd` as well as `-C`.
- `_early_recovery._git_executable` gets a reasoned resolution-allowlist row
  (stdlib-only launch repair; cannot import `hermes_platform`).

Tests rewritten to the new contract:
- test_source_release_channels zip fallback: the download stub takes the
  target SHA the ZIP swap now journals, and asserts it is passed through.
- test_update_target_identity: the fault injection keys the upstream sync by
  its remote (`upstream`/`upstream/main`), since the sync now merges the
  fetched ref instead of a second `pull`; the origin-target syntax case
  expects the pre-move preflight refusal.
- test_hostile_commit ZIP cell kills right after a tracked file landed, not
  at the N-th swap (listing order made it land on unchanged entries only).

(cherry picked from commit b6adf96ca6988262fe7c408ea652d3b390c7be8d)
(cherry picked from commit 028cb933271626d21003c255bb397deb8998b7da)
2026-10-06 06:20:30 -07:00
Teknium
f562145c70 fix(update): a syntax rollback resets HEAD and the index before any file
The syntax rollback's first step was `reset --soft` (or `update-ref` for a
parked/detached checkout), which leaves the index on the broken commit. A
kill during the following `reset --hard`, after it had written a good file,
left that file staged as the broken version, so the next launch's restore
landed on the pre-update commit with a dirty index (`MM`).

The first step is now a mixed reset (`reset -q <pre>`), after `update-ref
--no-deref HEAD` for the detach mode. HEAD and the index are back on the
pre-update commit before any file is written. The marker's `rollback=` field
is `branch`/`detach`, and recovery redoes the same steps when the kill came
before them. A failed rollback step now goes to the in-process restore every
time. Before this, a failure in the first step dropped the marker, even
after `update-ref` had already moved HEAD.

Also: the old branch-switch hostile cell triggers on `checkout [--no-guess]
main`, since CP0 now passes `--no-guess`. The fork cell makes origin read as
a fork through the git shim, because the sandbox otherwise reports the
official URL and the upstream sync never runs.

(cherry picked from commit 23f9658868b9c1f639ed972e59251fe154372929)
(cherry picked from commit 5fd6de2e62152076d6703e5ff70250ec1ece71b9)
2026-10-06 06:20:30 -07:00
Teknium
26e3e2d55d fix(update): keep the commit point's obligations and copies through failed later moves
Review round 2 on the LP-COMMIT commit point:

- A failed upstream fork fast-forward after a committed origin pull no
  longer hands back the obligations the origin pull armed.
  `disarm_commit_obligations` now refuses unless HEAD is the commit the
  run STARTED from (`record_run_start`), so every caller is covered; the
  restore of the snapshot is an atomic replace.
- The ZIP swap journal counts a leftover `<entry>.hermes-update-old` as
  "existed" (`_stage_replacement` puts it back before the swap), and the
  `swapping` recovery treats any backup as proof the entry existed, so a
  kill mid-swap can no longer delete both copies. A pre-run journal the
  start-of-run settle could not clear refuses the swap instead of being
  overwritten.
- CP0 runs `git checkout --no-guess <branch>` only when the local branch
  exists; otherwise only the marked `checkout -B <branch> origin/<branch>`
  runs. git's checkout guess used to create the branch and rewrite the
  tree while the code believed the checkout could not move.
- The syntax rollback moves HEAD back to the pre-update commit first
  (`reset --soft`, or `update-ref --no-deref` for a parked/detached
  checkout), then `reset --hard`. The marker names pre = the good commit
  and `rollback=<mode>`, so a kill anywhere lands the next launch on the
  pre-update commit; recovery redoes the HEAD step if the kill came first.
- The syntax guards (preflight `git show`, ZIP compile gate, post-pull
  backstop) skip a target whose `requires-python` excludes the running
  interpreter: its syntax may be a newer Python's. Unknown specs keep
  checking.
- The no-op exit in `_verify_head_after_pull` disarms (self-verified).

(cherry picked from commit c787a8c3f9823be8639f6f38efbbdff2b78a97cd)
(cherry picked from commit d785ad1c7e919bd7d64d17f2e9d664b84a7e8982)
2026-10-06 06:20:30 -07:00
Teknium
27f197d5ce fix(update): make the git/ZIP swap a single crash-safe commit point
Arm the source-completion tail and the fleet obligation in the parent before
any tree move; cover the CP0 branch switch, the upstream fork ff and the syntax
rollback with the interrupted-pull marker; settle (never drop) the marker on a
non-kill git failure; refuse foreign-owned venvs and uncompilable targets before
HEAD moves; push the fork only after a validated update; ZIP fallback only from
an untouched checkout; journal the ZIP swap so start-of-run recovery finishes or
rolls it back and no staging/backup sibling wedges the next run.

(cherry picked from commit 584acad44f3d6c552b7c2767d12c612de0436498)
(cherry picked from commit c6567ac2d28c421f384d95e1a6fc28912cb30f86)
2026-10-06 06:20:29 -07:00