diff --git a/hermes_cli/container_boot.py b/hermes_cli/container_boot.py index 327e6a5ce7f0..6cf241af6482 100644 --- a/hermes_cli/container_boot.py +++ b/hermes_cli/container_boot.py @@ -125,6 +125,16 @@ def reconcile_profile_gateways( """ actions: list[ReconcileAction] = [] + # A multiplexing root/default gateway owns inbound platform connections + # for every profile. Named slots must still be registered (so explicit + # lifecycle management remains available), but booting them from their + # persisted run intent would create additional multiplex owners. + from utils import is_truthy_value + + multiplex_profiles = is_truthy_value( + os.environ.get("GATEWAY_MULTIPLEX_PROFILES"), + ) + # Default profile — always register, even if nothing has ever # populated the root profile dir. The slot exists so # ``hermes gateway start`` (no ``-p``) has somewhere to land; @@ -173,7 +183,9 @@ def reconcile_profile_gateways( continue prior_state = _read_desired_state(entry) - should_start = prior_state in _AUTOSTART_STATES + should_start = ( + not multiplex_profiles and prior_state in _AUTOSTART_STATES + ) if not dry_run: _cleanup_stale_runtime_files(entry) diff --git a/tests/hermes_cli/test_container_boot.py b/tests/hermes_cli/test_container_boot.py index 2bfea1b0c5d4..69f7efd0ea75 100644 --- a/tests/hermes_cli/test_container_boot.py +++ b/tests/hermes_cli/test_container_boot.py @@ -195,6 +195,39 @@ def test_desired_state_running_autostarts_even_if_runtime_failed(tmp_path: Path) assert not (scandir / "gateway-resilient" / "down").exists() +def test_multiplex_boot_keeps_named_running_profile_registered_down( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Only the root/default s6 slot may own a multiplex gateway process.""" + monkeypatch.setenv("GATEWAY_MULTIPLEX_PROFILES", "true") + scandir = tmp_path / "run-service"; scandir.mkdir() + _seed_default_root(tmp_path, state="running") + profile = _make_profile( + tmp_path, + "resilient", + state="running", + desired_state="running", + ) + persisted_state = (profile / "gateway_state.json").read_text() + + actions = reconcile_profile_gateways( + hermes_home=tmp_path, scandir=scandir, dry_run=False, + ) + + assert actions == [ + ReconcileAction( + profile="default", prior_state="running", action="started", + ), + ReconcileAction( + profile="resilient", prior_state="running", action="registered", + ), + ] + assert not (scandir / "gateway-default" / "down").exists() + assert (scandir / "gateway-resilient" / "down").exists() + assert (profile / "gateway_state.json").read_text() == persisted_state + + def test_desired_state_stopped_blocks_legacy_running_runtime(tmp_path: Path) -> None: """Explicit stop must survive a stale legacy runtime state of running.""" scandir = tmp_path / "run-service"; scandir.mkdir()