Files
hermes-agent/tests
ang-fleet-workers[bot] b6ab666b6d fix(gateway): a service definition is written only by the home it pins
Two homes can resolve to one gateway service name: `<root>/profiles/<name>`
and `~/.hermes/profiles/<name>` both take `hermes-gateway-<name>`
(`ai.hermes.gateway-<name>` on launchd). Every gateway boot, start and
restart calls `refresh_systemd_unit_if_needed` / `refresh_launchd_plist_if_needed`,
which rewrote whatever definition sat at that name with no check of the
HERMES_HOME it pins. A scratch/E2E gateway started from a custom root
therefore repointed the real install's unit at the scratch directory; the
real gateway failed on its next restart (200/CHDIR once the scratch dir was
cleaned up). `systemd_install --force` and `launchd_install --force` did
the same. Uninstall already refused a unit pinning another home
(`_systemd_unit_belongs_to_current_home`); the writers did not.

- hermes_cli/gateway_service_owner.py (new sibling): `pinned_home()` reads
  the HERMES_HOME a unit or plist pins; `definition_belongs_to_home()`
  refuses a write from any other home; `refuse_foreign_home_install()`
  gates `gateway install` for a home outside the platform-default tree.
- Writers refuse a definition pinned to another home: the systemd and
  launchd refreshes (boot/start/restart) and both installers, `--force`
  included. The system-scope check runs on the caller's own home, before
  `systemd_unit_is_current` adopts the unit's home into os.environ (else
  the check compares the unit with itself); a sudo-stripped caller with no
  HERMES_HOME still acts for the unit's pinned home, as before.
- `gateway install` refuses a HERMES_HOME outside the account's Hermes
  tree that owns no bare service name and has no service installed yet: a
  service registered for a scratch home outlives the run.
- New `gateway install --force-unit-path` overrides both refusals (an
  explicit repoint of the definition at this home).
- Refused installs exit 1, so `gateway install` no longer goes on to start
  the other home's service; the setup wizard reports the refusal and
  continues, as ensure_gateway_service already did.
- Existing tests whose sandbox HERMES_HOME is a tmp dir opt in to the
  admission check (5 files, one monkeypatch each).

Tests: tests/hermes_cli/test_gateway_service_owner.py, 18 tests. On
e36a818033 (sources reverted, tests kept): 11 failed, 7 passed (foreign
unit/plist rewritten on refresh and --force, refused install starts the
service, scratch install proceeds, no --force-unit-path). With this change:
18 passed. test_colliding_profile_names_share_a_unit pins the premise on
the real name resolution.
2026-10-08 17:06:02 -07:00
..
2026-10-08 18:56:50 -04:00
2026-10-08 21:35:58 +02:00
2026-10-08 21:35:58 +02:00
2026-10-08 21:35:58 +02:00
2026-10-08 21:35:58 +02:00
2026-10-08 21:35:58 +02:00
2026-10-08 21:29:48 +02:00
2026-10-08 19:42:29 +02:00
2026-10-08 18:56:50 -04:00
2026-10-08 21:29:48 +02:00
2026-10-08 21:35:58 +02:00
2026-10-08 21:35:58 +02:00
…