Repository navigation
spawn-guard: post-update/boot 3-spawn guard over the committed generation (t_7b0df4cf) - #127176
smurilosrocha wants to merge 1 commit into
Conversation
…tion (t_7b0df4cf) Upstream main already carries the pin fix itself (committed_venv site restore #f57d235748 plus the worker PM boot #308072df89) — this change adds only the durable net that catches the next wipe: - hermes_cli/post_update.py: new step_spawn_guard in HOME_STEPS (hence also BOOT_HOME_STEPS). Each leg builds its env with the same builders production uses and spawns the real argv in a subprocess, never an in-process import. L1 kanban worker --help (rc 0), L2 DM channel --help on the bare stripped env (rc 0), L3 cron external worker with a missing payload (PASS = exit 1 with NO stderr traceback; any ModuleNotFoundError/No module named/traceback fails naming the leg). - Generation-gated, not revision-gated: the step owns a per-profile spawn-guard/ record with the facts.json fingerprint and skips when unchanged (boot keys by revision; a PM sync can move the generation without a revision change, and must not rerun every boot step). Recorded skips: wheel-install, no-committed-site, generation-unchanged. - hermes_cli/update_cmd_maint.py: _run_post_update_maintenance runs the step best-effort with a one-line report, loud on red. - tests/hermes_cli/test_post_update_spawn_guard.py: registry, skips, verdicts, root-only mutation shape, tail reporting. Autor: tech-lead
P1 — the guard's own entry gate returns
probe = pin_hermes_tree_on_pythonpath({}, root)
if "PYTHONPATH" not in probe:
return {"ok": True, "skipped": "wheel-install"}
if probe["PYTHONPATH"] == str(root):
return {"ok": True, "skipped": "no-committed-site"}But That state isn't hypothetical: I drove the real head
Row 1 is the positive control; rows 2-4 report The root-only fallback conflates never committed (dev checkout) with committed and now gone.
|
What
The durable net over upstream's own t_7b0df4cf/#122222 fix. Upstream
mainalready carries the pin fix itself (committed-generation site restoref57d235748, boot through the committed selection9ec6b94eca, worker PM boot308072df89) — this PR adds only the guard that catches the next wipe. Purely additive (+407/−0).hermes_cli/post_update.py: newstep_spawn_guardinHOME_STEPS(hence alsoBOOT_HOME_STEPS, which excludes onlysync_skills). Each leg builds its env with the same builders production uses and spawns the real argv in a subprocess — never an in-process import ofcron/hermes_cli. Legs: L1 kanban worker--help(PASS = rc 0), L2 DM channel--helpon the bare stripped env (PASS = rc 0), L3 cron external worker with a missing payload file (PASS = exit 1 with NO stderr traceback; anyModuleNotFoundError/No module named/traceback fails naming the leg). The L3 leg setsWORKER_MARKERexactly like the production spawn (cron/scheduler.py), so the child's PM boot is exercised too — a leg without it would skip half the real path.boot_bootstrap.py), but the dependency generation (facts.json) can move without a revision change (PM sync), and extending the boot identity would re-run every boot step — including the expensive SQLite guard — on a generation-only change. The step owns a per-profilespawn-guard/record with the generation fingerprint and skips when unchanged. Recorded skips, never silent:wheel-install,no-committed-site,generation-unchanged.hermes_cli/update_cmd_maint.py:_run_post_update_maintenanceruns the step best-effort with a one-line report (_report_spawn_guard_after_update), loud on red.tests/hermes_cli/test_post_update_spawn_guard.py: registry membership (home + boot), the three skips, pass-records-generation, failure-names-the-leg, marker fidelity, root-only mutation shape, tail reporting.Verification (all in-turn, command + output)
pytest tests/hermes_cli/test_post_update_spawn_guard.py tests/hermes_cli/test_post_update.py tests/hermes_cli/test_boot_bootstrap.py tests/cron/test_restart_safe_worker.py-> 74 passed. Broader run earlier: 98 passed, 3 skipped; the only failures (test_boot_convergence.py::test_each_start_checks_pm_once_before_dispatch, 3 params) also fail on cleanmain, pre-existing and unrelated.sys.path):{"ok": true, "legs": "3/3"}.GUARDA_3_SPAWNS_MUTATION=root-only(rebuilds the pre-fix root-only pin):{"ok": false, "error": "L3-cron-externo: IMPORT-FAIL stderr: ... utils.py line 17 -> hermes_yaml.py line 10 -> ModuleNotFoundError: No module named 'ruamel'"}— L1/L2 pass, only L3 fails, with the exact production signature of the incident.worker_bootstrap(cron/worker_bootstrap.py:39-45), so a pin-only regression heals in-child while a full wipe still fails L3 — the guard stays red iff the composed production path (pin + boot) fails, which is exactly "the next boot's worker would die".Scope
Guard + wiring + tests only. No touch to the fix itself or to
hermes_cli/kanban_pr_acceptance.py.Supersedes #127167 (same content, pre-rebase head).
Autor: tech-lead