fix(gateway): scope pairing platform discovery to the profile dir - #60564
fix(gateway): scope pairing platform discovery to the profile dir#60564briandevans wants to merge 2 commits into
Conversation
The per-profile pairing isolation added self._dir and scoped every per-file path helper (_pending_path, _approved_path) to it, but _all_platforms still enumerated the module-global PAIRING_DIR. For a profile-scoped PairingStore, list_approved/list_pending/clear_pending therefore operated on the GLOBAL platform set while loading each platform's file from the PROFILE dir — so list_approved() returned [] for a user that is_approved() confirmed as approved, a silent divergence between the authz surface and the list/inspect/clear surface. Route discovery through self._dir. Byte-identical for the global store (self._dir == PAIRING_DIR when no profile is set); only the buggy profile-scoped case changes. self._dir is guaranteed to exist (__init__ mkdirs it).
There was a problem hiding this comment.
Pull request overview
Fixes a regression in the gateway pairing system where profile-scoped PairingStore instances discovered platforms from the global pairing directory, causing list_approved()/list_pending()/clear_pending() to disagree with is_approved() for the same profile.
Changes:
- Update
_all_platforms()to iterateself._dir(profile-scoped directory) instead of the module-globalPAIRING_DIR. - Add a regression test asserting
is_approved()andlist_approved()agree for a profile-scoped store when the global pairing dir is distinct/empty.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
gateway/pairing.py |
Scope platform discovery to the store’s resolved directory (self._dir) to keep listing/clearing consistent with approval checks under profiles. |
tests/gateway/test_pairing.py |
Adds a profile-scoped regression test for platform discovery behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| with patch("gateway.pairing.PAIRING_DIR", global_dir), patch( | ||
| "gateway.pairing.get_hermes_home", return_value=home | ||
| ): |
…e scopes to the mocked home
|
@copilot Addressed in 2708fa8 — you're exactly right. Fix: patch |
|
Thanks for the focused regression fix. Current main still has the mismatch: The one-line change makes enumeration use the same resolved directory as the per-file helpers. The added regression test also correctly patches Automated hermes-sweeper review. |
|
Good catch — One update to your test: it pinned |
What does this PR do?
The per-profile pairing isolation introduced
self._dirand scoped every per-file path helper to it (_pending_path→self._dir / f"{platform}-pending.json",_approved_path→self._dir / f"{platform}-approved.json"). But_all_platforms(suffix)— the helper that enumerates which platforms have data files — still iterates the module-globalPAIRING_DIR:For a
PairingStore(profile="<name>"),self._diris<HERMES_HOME>/profiles/<name>/pairing/, which is notPAIRING_DIR. So every caller that passesplatform=None—list_approved,list_pending,clear_pending— enumerates the platform set from the global dir but then loads each platform's file from the profile dir. The result is a silent divergence between the authz surface and the list/inspect/clear surface:is_approved("telegram", uid)readsself._dirand returnsTrue, whilelist_approved()scans the (empty, for that profile) global dir and returns[]for that same user. Operators inspecting or clearing a profile's whitelist see the wrong platform set.The one-line fix routes discovery through
self._dir. This is byte-identical for the global store, whereself._dir == PAIRING_DIR(set in__init__when no profile is given), so the existinghermes pairingCLI / dashboard path is unchanged; only the profile-scoped case is corrected.self._diris guaranteed to exist because__init__doesself._dir.mkdir(parents=True, exist_ok=True).Related Issue
Type of Change
Changes Made
gateway/pairing.py:_all_platformsnow iteratesself._dirinstead of the module-globalPAIRING_DIR, so platform discovery is scoped to the same directory the per-file path helpers already use.tests/gateway/test_pairing.py: addTestProfileScopedDiscovery— a profile-scoped store approves a user, then assertsis_approved()andlist_approved()agree.How to Test
PairingStore(profile="alice")withPAIRING_DIRpatched to a distinct empty directory (so the global dir provably isn't the profile dir).is_approved("telegram", "tg-456")isTruebutlist_approved()returns[](it scanned the empty global dir). After the fix both agree.uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/gateway/test_pairing.py -v→ 53 passed. The new test fails before the one-line change (assert [] == ['tg-456']) and passes after.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A