refactor(gateway): build the rescanned owner once; fix docstrings the new outcome contradicted

Review follow-ups on the standalone-owner fix:
- _request_serve_profile built the HostGateway twice three lines apart; the standalone
  branch also copied the probe's stale profiles instead of the rescan answer's served set.
  One construction from the answer, standalone= from its multiplex key.
- START no longer carries an owner nobody reads.
- Module doc said 'exactly one gateway per host' / 'Four outcomes' / 'never a second gateway',
  all contradicted by the START-beside-standalone outcome; scoped to 'beside a multiplexer'
  and pointed at #109417 for the forced migration that removes the standalone branch.
This commit is contained in:
kshitijk4poor
2026-09-21 20:02:44 +05:30
committed by Teknium
parent 804cc3c021
commit d39ce8f455

View File

@@ -1,10 +1,11 @@
"""Is there ONE live host gateway, and does it already serve this profile?
Multiplex-only (Teknium ruling): exactly one ``hermes gateway run`` per host, multiplexing every
profile. The lifecycle verbs therefore answer a different question than they used to — not "does
THIS home hold a ``gateway.pid``?" but "is the host process live, and is this profile in its served
set?" — and when it is not, they ask that process to serve the profile instead of starting a second
one. Four outcomes, in order:
Multiplex-only (Teknium ruling): exactly one MULTIPLEXING ``hermes gateway run`` per host, serving
every profile; standalone per-profile gateways coexist until that migration is forced (#109417).
The lifecycle verbs therefore answer a different question than they used to — not "does THIS home
hold a ``gateway.pid``?" but "is the host process live, and is this profile in its served set?" —
and when it is not, they ask that process to serve the profile instead of starting a second one.
Five outcomes, in order:
* ``ATTACH`` — a live host gateway already serves this profile. Nothing to start; exit 0.
* ``RESCAN``→ATTACH — it does not serve it yet: ask it to reconcile ``profiles/`` now (control
@@ -79,8 +80,8 @@ class HostGateway:
#: False when the owner has not answered ``identify`` yet: an owner exists, but which profiles
#: it serves is UNKNOWN. Never conflate that with "serves nothing" — see the module doc.
served_known: bool = True
#: True once the owner has said ``multiplex: False``: it is a per-profile gateway (the documented
#: one-process-per-profile topology), not a multiplexer that can be asked to serve anyone else.
#: True once the owner has said ``multiplex: False``: a per-profile gateway that cannot be asked
#: to serve anyone else — see ``START`` in the module doc.
standalone: bool = False
def serves(self, profile: str) -> bool:
@@ -222,13 +223,12 @@ def request_serve_profile(profile: str, *, timeout: float = 8.0,
return None
if not isinstance(answer, dict):
return None
if answer.get("multiplex") is False:
return HostGateway(gateway.pid, gateway.home, gateway.profiles, standalone=True)
served = answer.get("served_profiles")
rescanned = HostGateway(
gateway.pid, gateway.home,
tuple(str(p) for p in served) if isinstance(served, list) else ())
return rescanned if rescanned.serves(profile) else None
tuple(str(p) for p in served) if isinstance(served, list) else (),
standalone=answer.get("multiplex") is False)
return rescanned if rescanned.standalone or rescanned.serves(profile) else None
@dataclass(frozen=True)
@@ -273,7 +273,7 @@ def _refuse_message(gateway: HostGateway, profile: str) -> str:
def decide(our_home: Path, *, replace: bool = False) -> HostAttachDecision:
"""Attach, rescan-then-attach, replace or refuse — never a second gateway.
"""Attach, rescan-then-attach, replace or refuse — never a second gateway beside a multiplexer.
Never raises: a broken probe degrades to ``START``, i.e. exactly the pre-rendezvous behaviour.
"""
@@ -312,7 +312,7 @@ def decide(our_home: Path, *, replace: bool = False) -> HostAttachDecision:
# here exits 78, which every supervisor treats as permanent — on a launchd fleet that parked
# every unit but the first to claim the host lock. Start beside it; the host-lock claim logs
# the topology and the `gateway migrate --multiplex` path stays the way to converge.
return HostAttachDecision(START, "", attached)
return HostAttachDecision(START, "")
if not gateway.served_known:
# The owner never answered, so we know only that it exists. ATTACH here (on the record's
# word) parked a supervised unit against a served set nobody had committed to yet.