fix(s6): persist profile gateway desired state (#46292)

* fix: persist s6 gateway desired state

* chore(release): map salvaged contributor

---------

Co-authored-by: Alfred Smith <alfred@my-cloud.me>
Co-authored-by: Ben <ben@nousresearch.com>
This commit is contained in:
Teknium
2026-06-15 14:02:10 +10:00
committed by GitHub
co-authored by Alfred Smith Ben
parent 61ee2dbfdb
commit b770967263
5 changed files with 175 additions and 13 deletions
+49 -4
View File
@@ -30,6 +30,7 @@ def _make_profile(
name: str,
*,
state: str | None,
desired_state: str | None = None,
with_pid: bool = False,
config: bool = True,
) -> Path:
@@ -40,10 +41,13 @@ def _make_profile(
# SOUL.md is what the reconciler keys on — it's always seeded by
# `hermes profile create`. See container_boot._render_run_script.
(p / "SOUL.md").write_text("# fake profile\n")
if state is not None:
(p / "gateway_state.json").write_text(json.dumps({
"gateway_state": state, "timestamp": 1234567890,
}))
if state is not None or desired_state is not None:
payload: dict[str, object] = {"timestamp": 1234567890}
if state is not None:
payload["gateway_state"] = state
if desired_state is not None:
payload["desired_state"] = desired_state
(p / "gateway_state.json").write_text(json.dumps(payload))
if with_pid:
(p / "gateway.pid").write_text(json.dumps(
{"pid": 99999, "host": "old-container"},
@@ -130,6 +134,46 @@ def test_startup_failed_does_not_autostart(tmp_path: Path) -> None:
assert (scandir / "gateway-broken" / "down").exists()
def test_desired_state_running_autostarts_even_if_runtime_failed(tmp_path: Path) -> None:
"""Persisted operator intent wins over transient runtime failures."""
scandir = tmp_path / "run-service"; scandir.mkdir()
_make_profile(
tmp_path,
"resilient",
state="startup_failed",
desired_state="running",
)
actions = reconcile_profile_gateways(
hermes_home=tmp_path, scandir=scandir, dry_run=False,
)
assert _named_actions(actions) == [ReconcileAction(
profile="resilient", prior_state="running", action="started",
)]
assert not (scandir / "gateway-resilient" / "down").exists()
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()
_make_profile(
tmp_path,
"quiet",
state="running",
desired_state="stopped",
)
actions = reconcile_profile_gateways(
hermes_home=tmp_path, scandir=scandir, dry_run=False,
)
assert _named_actions(actions) == [ReconcileAction(
profile="quiet", prior_state="stopped", action="registered",
)]
assert (scandir / "gateway-quiet" / "down").exists()
def test_starting_state_does_not_autostart(tmp_path: Path) -> None:
"""`starting` means the gateway died mid-boot last time; treat as
failed, not as a candidate for auto-restart."""
@@ -513,6 +557,7 @@ def test_legacy_gateway_run_cmd_seeds_default_running_state(
assert not (scandir / "gateway-default" / "down").exists()
state = json.loads((tmp_path / "gateway_state.json").read_text())
assert state["gateway_state"] == "running"
assert state["desired_state"] == "running"
assert state["migrated_from"] == "legacy-container-cmd"
+42
View File
@@ -726,6 +726,48 @@ def test_s6_lifecycle_dispatches_to_s6_svc(
assert flags == ["-u", "-d", "-t"]
def test_s6_lifecycle_persists_named_profile_desired_state(
s6_scandir,
fake_subprocess_run,
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
import json
hermes_home = tmp_path / "hermes-home"
profile_dir = hermes_home / "profiles" / "coder"
profile_dir.mkdir(parents=True)
(s6_scandir / "gateway-coder").mkdir()
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
mgr = S6ServiceManager(scandir=s6_scandir)
mgr.start("gateway-coder")
assert json.loads((profile_dir / "gateway_state.json").read_text())["desired_state"] == "running"
mgr.stop("gateway-coder")
assert json.loads((profile_dir / "gateway_state.json").read_text())["desired_state"] == "stopped"
mgr.restart("gateway-coder")
assert json.loads((profile_dir / "gateway_state.json").read_text())["desired_state"] == "running"
def test_s6_lifecycle_persists_default_profile_desired_state(
s6_scandir,
fake_subprocess_run,
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
import json
hermes_home = tmp_path / "hermes-home"
hermes_home.mkdir()
(s6_scandir / "gateway-default").mkdir()
monkeypatch.setenv("HERMES_HOME", str(hermes_home / "profiles" / "coder"))
mgr = S6ServiceManager(scandir=s6_scandir)
mgr.start("gateway-default")
state = json.loads((hermes_home / "gateway_state.json").read_text())
assert state["desired_state"] == "running"
# ---------------------------------------------------------------------------
# Lifecycle errors — friendly messages, not raw CalledProcessError
# ---------------------------------------------------------------------------