diff --git a/scripts/ci/classify_changes.py b/scripts/ci/classify_changes.py index 9a61337799..e1fc084f9d 100644 --- a/scripts/ci/classify_changes.py +++ b/scripts/ci/classify_changes.py @@ -290,12 +290,13 @@ _UPDATE_DEPENDENCIES = ( "hermes_cli/_subprocess_compat.py", # update git env, process-tree kill, PM git exposure "hermes_cli/local_runtime/processes.py", # bounded probes' spawn_server/job custody "agent/deadline.py", # bounded probes' process-tree timeout cleanup - # migrate_all_homes' second-hop provider/profile/install decisions. + # migrate_all_homes' second-hop provider/profile decisions, run on every update. Its + # plugin-install branch (plugins_cmd, plugins_cmd_install) runs only for a home whose + # configured memory provider left core; no update journey's home has one, so those + # modules stay with the unit lane (tests/ci/test_update_transitive_routing.py). "agent/memory_provider.py", - "hermes_cli/plugins_cmd_install.py", - "hermes_cli/plugins_cmd.py", "pm/plugins_state.py", - "pm/install.py", + "pm/install.py", # also sealed()/lazy_installs_allowed(): every update's default-tool install "hermes_cli/desktop_build_lock.py", "hermes_cli/memory_provider_migration.py", "hermes_cli/left_core_migration.py", # source_build migrates plugins that left core diff --git a/tests/ci/test_update_transitive_routing.py b/tests/ci/test_update_transitive_routing.py index 22bfec4f5f..4cea2a3bd8 100644 --- a/tests/ci/test_update_transitive_routing.py +++ b/tests/ci/test_update_transitive_routing.py @@ -4,9 +4,12 @@ These are scoped call edges, not a claim that a recursive static import walk identifies the executed dependency closure of every lazy branch. """ +import sys +from pathlib import Path + import pytest -from tests.ci.test_update_ci_routing import _ci_run, _consumers_reached, _real_classifier +from tests.ci.test_update_ci_routing import _REPO, _ci_run, _consumers_reached, _real_classifier # bounded_probe_run -> spawn_server, kill_process_tree -> deadline; the @@ -16,8 +19,6 @@ from tests.ci.test_update_ci_routing import _ci_run, _consumers_reached, _real_c "hermes_cli/local_runtime/processes.py", "agent/deadline.py", "agent/memory_provider.py", - "hermes_cli/plugins_cmd_install.py", - "hermes_cli/plugins_cmd.py", "pm/plugins_state.py", "pm/install.py", "hermes_cli/backup.py", @@ -29,3 +30,30 @@ def test_second_hop_update_change_dispatches_real_update_consumers(path): run = _ci_run(lanes) for lane in ("e2e_upgrade", "e2e_desktop_update"): assert all(_consumers_reached(run, lane).values()), f"{path}: {lane} never reaches its consumers" + + +# Off the update lanes (review K132346-ci-cost: 4 of 7 replayed main commits that newly started +# both update journeys touched only these): the journeys cannot observe code their update never runs. +_PLUGIN_INSTALL = ("hermes_cli/plugins_cmd.py", "hermes_cli/plugins_cmd_install.py") + + +def test_update_migration_on_a_journey_home_runs_no_plugin_install_code(): + """The journeys' homes configure no memory provider that left core. If the migration ever runs + plugin-install code there, the journeys exercise it: route these modules again.""" + from hermes_cli.memory_provider_migration import migrate_all_homes + + ran: set[tuple[str, str]] = set() + + def profile(frame, event, _arg): + if event == "call": + ran.add((Path(frame.f_code.co_filename).resolve().as_posix(), frame.f_code.co_name)) + + sys.setprofile(profile) + try: + assert migrate_all_homes() == [] + finally: + sys.setprofile(None) + files = {f for f, name in ran if name != ""} + assert (_REPO / "hermes_cli/memory_provider_migration.py").resolve().as_posix() in files + hit = [rel for rel in _PLUGIN_INSTALL if (_REPO / rel).resolve().as_posix() in files] + assert hit == [], f"the update runs {hit} on a journey home"