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.