fix(update): don't roll back a good update over uninstalled deps

Phase 2 review caught a false-rollback I introduced: on the git path the
import guard runs at the post-pull syntax check, which is BEFORE the
dependency sync. A release that adds a new third-party requirement would
fail the probe and trigger `git reset --hard` on a perfectly good update.

Rather than reorder the git path (the guard belongs with the rollback it
feeds), make the probe ignore a missing module that isn't ours. A missing
third-party package means deps aren't installed yet; a missing first-party
module means the update dropped a file, which IS the skew we're hunting.

This also makes the ZIP path's ordering non-load-bearing.

Verified: third-party absent -> (True, None, None); first-party absent ->
flagged; and the original TODO_INJECTION_HEADER skew is still caught.
This commit is contained in:
kshitij 2026-08-01 15:58:53 +05:30
parent aa5d4fd6ee
commit 822571fa8e
2 changed files with 34 additions and 0 deletions

View File

@ -200,6 +200,16 @@ def _validate_critical_modules_import(root) -> tuple[bool, str | None, str | Non
"for name in %r:\n"
" try:\n"
" importlib.import_module(name)\n"
" except ModuleNotFoundError as exc:\n"
# A missing *third-party* module means dependencies aren't installed
# yet (this guard can run before the dependency sync on the git path),
# not a skewed checkout. Only treat it as breakage when the missing
# module is one of ours.
" missing = (getattr(exc, 'name', '') or '').split('.')[0]\n"
" if missing in ('tools', 'agent', 'gateway', 'plugins', 'providers') \\\n"
" or missing.startswith('hermes'):\n"
" sys.stdout.write(name + '\\n' + str(exc))\n"
" raise SystemExit(3)\n"
" except ImportError as exc:\n"
" sys.stdout.write(name + '\\n' + str(exc))\n"
" raise SystemExit(3)\n"

View File

@ -147,3 +147,27 @@ def test_import_guard_prefers_the_project_venv_interpreter(monkeypatch, tmp_path
update_cmd._validate_critical_modules_import(tmp_path)
assert seen["interpreter"] == str(venv_python)
def test_import_guard_ignores_missing_third_party_dependency(monkeypatch, tmp_path):
"""A new third-party requirement is not a partially-updated tree.
On the git path this guard runs BEFORE the dependency sync, so a release
that adds a dependency would otherwise look like breakage and trigger a
spurious `git reset --hard` rollback of a perfectly good update.
"""
(tmp_path / "consumer.py").write_text("import totally_not_installed_pkg\n")
monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",))
assert update_cmd._validate_critical_modules_import(tmp_path) == (True, None, None)
def test_import_guard_flags_missing_first_party_module(monkeypatch, tmp_path):
"""A missing *first-party* module IS skew — the update dropped a file."""
(tmp_path / "consumer.py").write_text("import tools.nonexistent_module\n")
monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",))
ok, module, error = update_cmd._validate_critical_modules_import(tmp_path)
assert ok is False
assert module == "consumer"
assert error is not None and "tools.nonexistent_module" in error