fix(messaging): dual-live and cross-profile invariants for the served-profile card

Widen the #112765 regression (from #112784, @liuhao1024) to the two cases the three
community fixes split on:

- a sibling served profile with no ``<profile>:telegram`` entry in the shared record
  must not inherit another profile's connected verdict (from #112786, @kvnloo);
- the transitional dual-live case — a profile running its own standalone gateway while
  the live multiplexer still lists it in ``served_profiles`` — keeps the own record, the
  same rung order ``resolve_gateway_liveness`` uses, so liveness and platform state never
  come from two different gateways (from #112790, @kokhlo). Red on the "multiplexer always
  wins" shape, green on the precedence-mirroring one.

Also: every ``write_text`` in the file carries ``encoding="utf-8"`` (windows-footgun gate).
This commit is contained in:
teknium1
2026-09-16 09:26:28 -07:00
committed by Teknium
parent d4ff9e852e
commit a09195b1e8

View File

@@ -18,8 +18,8 @@ import pytest
def served_root(tmp_path, monkeypatch):
root = tmp_path / "hermes"
(root / "profiles" / "alpha").mkdir(parents=True)
(root / "config.yaml").write_text("gateway: {multiplex_profiles: true}\n")
(root / "gateway.pid").write_text(json.dumps({"pid": os.getpid(), "hermes_home": str(root)}))
(root / "config.yaml").write_text("gateway: {multiplex_profiles: true}\n", encoding="utf-8")
(root / "gateway.pid").write_text(json.dumps({"pid": os.getpid(), "hermes_home": str(root)}), encoding="utf-8")
(root / "gateway_state.json").write_text(json.dumps({
"pid": os.getpid(), "hermes_home": str(root), "gateway_state": "running",
"served_profiles": ["default", "alpha", "beta"],
@@ -27,7 +27,7 @@ def served_root(tmp_path, monkeypatch):
"api_server": {"state": "connected", "listener_base": "http://127.0.0.1:45719"},
"webhook": {"state": "fatal", "error_code": "port_in_use"},
"alpha:telegram": {"state": "connected"},
}}))
}}), encoding="utf-8")
monkeypatch.setenv("HERMES_HOME", str(root))
monkeypatch.delenv("GATEWAY_MULTIPLEX_PROFILES", raising=False)
import hermes_constants
@@ -77,7 +77,41 @@ def test_messaging_card_ignores_a_served_profiles_stale_own_runtime_record(serve
"required_env": []}
alpha = served_root / "profiles" / "alpha"
(alpha / "gateway_state.json").write_text(json.dumps(
{"gateway_state": "stopped", "platforms": {}}))
{"gateway_state": "stopped", "platforms": {}}), encoding="utf-8")
[payload] = messaging._platform_payloads(alpha, [entry])
assert payload["gateway_running"] is True
assert payload["state"] == "connected", payload
# The shared record is scoped per profile: beta has no ``beta:telegram`` entry, so alpha's
# connected verdict must not bleed into beta's card.
beta = served_root / "profiles" / "beta"
beta.mkdir()
(beta / "gateway_state.json").write_text(json.dumps({"gateway_state": "stopped", "platforms": {}}), encoding="utf-8")
[beta_payload] = messaging._platform_payloads(beta, [entry])
assert beta_payload["state"] == "pending_restart", beta_payload
def test_messaging_card_keeps_a_live_own_gateway_record_over_the_multiplexer(served_root, monkeypatch):
"""Transitional dual-live case: a profile running its own standalone gateway while the live
multiplexer still lists it in ``served_profiles``. ``resolve_gateway_liveness`` answers from the
own record (rung 3) before the multiplexer (rung 4); the card must read the same record, or
liveness and platform state come from two different gateways."""
import gateway.status as status
from hermes_cli.web_routers import messaging
monkeypatch.setattr(messaging, "_platform_enablement", lambda *a, **k: (True, True, None))
alpha = served_root / "profiles" / "alpha"
# Two live gateways: this process is the default multiplexer; a second (fake, never signalled)
# PID wears alpha's argv. Only the live guard sees a foreign PID, so existence is stubbed.
own_pid = 2 ** 22 - 1
(alpha / "gateway_state.json").write_text(json.dumps({
"pid": own_pid, "hermes_home": str(alpha), "gateway_state": "running",
"platforms": {"telegram": {"state": "retrying", "error_code": "network"}}}), encoding="utf-8")
real_pid_exists = status._pid_exists
monkeypatch.setattr(status, "_pid_exists", lambda pid: pid == own_pid or real_pid_exists(pid))
monkeypatch.setattr(status, "_read_process_cmdline",
lambda pid: "hermes -p alpha gateway run" if pid == own_pid else "hermes gateway run")
assert status.multiplexer_liveness_for_profile(alpha) is not None
entry = {"id": "telegram", "name": "Telegram", "description": "", "docs_url": "", "env_vars": [],
"required_env": []}
[payload] = messaging._platform_payloads(alpha, [entry])
assert payload["gateway_running"] is True
assert payload["state"] == "retrying", payload