mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix: merge split gateway pairing stores
This commit is contained in:
parent
2c5762f575
commit
8a7d0790df
2 changed files with 93 additions and 1 deletions
|
|
@ -33,7 +33,7 @@ from gateway.whatsapp_identity import (
|
|||
expand_whatsapp_aliases,
|
||||
normalize_whatsapp_identifier,
|
||||
)
|
||||
from hermes_constants import get_hermes_dir
|
||||
from hermes_constants import get_hermes_dir, get_hermes_home
|
||||
from utils import atomic_replace
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -161,6 +161,51 @@ def _sync_allowlist_remove(platform: str, user_id: str) -> None:
|
|||
pass
|
||||
|
||||
|
||||
def _load_json_file(path: Path) -> dict:
|
||||
if path.exists():
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
return data if isinstance(data, dict) else {}
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return {}
|
||||
return {}
|
||||
|
||||
|
||||
def _merge_pairing_dir(active_dir: Path, alternate_dir: Path) -> None:
|
||||
"""Merge split legacy/new pairing data into the active PairingStore dir.
|
||||
|
||||
Older installs use ``{HERMES_HOME}/pairing`` while newer code/docs may
|
||||
write ``{HERMES_HOME}/platforms/pairing``. If both directories exist, the
|
||||
gateway must not silently ignore approved users sitting in the inactive
|
||||
location; otherwise already-paired Feishu users get asked for a fresh code.
|
||||
"""
|
||||
if not alternate_dir.exists() or active_dir.resolve() == alternate_dir.resolve():
|
||||
return
|
||||
active_dir.mkdir(parents=True, exist_ok=True)
|
||||
for src in alternate_dir.glob("*.json"):
|
||||
if not src.is_file():
|
||||
continue
|
||||
dest = active_dir / src.name
|
||||
merged = _load_json_file(src)
|
||||
if not merged:
|
||||
continue
|
||||
current = _load_json_file(dest)
|
||||
before = dict(current)
|
||||
# Active data wins on key conflict; otherwise union the inactive data.
|
||||
merged.update(current)
|
||||
if merged != before:
|
||||
_secure_write(dest, json.dumps(merged, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
def _migrate_split_pairing_dirs() -> None:
|
||||
home = get_hermes_home()
|
||||
old_dir = home / "pairing"
|
||||
new_dir = home / "platforms" / "pairing"
|
||||
active = PAIRING_DIR
|
||||
alternate = new_dir if active.resolve() == old_dir.resolve() else old_dir
|
||||
_merge_pairing_dir(active, alternate)
|
||||
|
||||
|
||||
def _secure_write(path: Path, data: str) -> None:
|
||||
"""Write data to file with restrictive permissions (owner read/write only).
|
||||
|
||||
|
|
@ -212,6 +257,11 @@ class PairingStore:
|
|||
else:
|
||||
self._dir = PAIRING_DIR
|
||||
self._dir.mkdir(parents=True, exist_ok=True)
|
||||
if not profile:
|
||||
# Heal installs whose global pairing data ended up split across
|
||||
# the legacy and new directories (per-profile stores never had
|
||||
# the legacy/new split).
|
||||
_migrate_split_pairing_dirs()
|
||||
# Protects all read-modify-write cycles. The gateway runs multiple
|
||||
# platform adapters concurrently in threads sharing one PairingStore.
|
||||
self._lock = threading.RLock()
|
||||
|
|
|
|||
|
|
@ -27,6 +27,48 @@ def _make_store(tmp_path):
|
|||
return PairingStore()
|
||||
|
||||
|
||||
class TestSplitPairingDirMigration:
|
||||
def test_merges_new_approved_into_active_legacy_dir(self, tmp_path):
|
||||
home = tmp_path / "home"
|
||||
legacy = home / "pairing"
|
||||
new = home / "platforms" / "pairing"
|
||||
legacy.mkdir(parents=True)
|
||||
new.mkdir(parents=True)
|
||||
(new / "feishu-approved.json").write_text(json.dumps({
|
||||
"ou_user": {"user_name": "Alice", "approved_at": 123.0}
|
||||
}))
|
||||
|
||||
with patch("gateway.pairing.PAIRING_DIR", legacy), patch("gateway.pairing.get_hermes_home", return_value=home):
|
||||
store = PairingStore()
|
||||
assert store.is_approved("feishu", "ou_user") is True
|
||||
|
||||
migrated = json.loads((legacy / "feishu-approved.json").read_text())
|
||||
assert "ou_user" in migrated
|
||||
|
||||
def test_active_entries_win_when_merging_split_dirs(self, tmp_path):
|
||||
home = tmp_path / "home"
|
||||
legacy = home / "pairing"
|
||||
new = home / "platforms" / "pairing"
|
||||
legacy.mkdir(parents=True)
|
||||
new.mkdir(parents=True)
|
||||
(legacy / "feishu-approved.json").write_text(json.dumps({
|
||||
"ou_user": {"user_name": "Active", "approved_at": 2.0}
|
||||
}))
|
||||
(new / "feishu-approved.json").write_text(json.dumps({
|
||||
"ou_user": {"user_name": "Inactive", "approved_at": 1.0},
|
||||
"ou_other": {"user_name": "Other", "approved_at": 1.0},
|
||||
}))
|
||||
|
||||
with patch("gateway.pairing.PAIRING_DIR", legacy), patch("gateway.pairing.get_hermes_home", return_value=home):
|
||||
store = PairingStore()
|
||||
assert store.is_approved("feishu", "ou_user") is True
|
||||
assert store.is_approved("feishu", "ou_other") is True
|
||||
|
||||
migrated = json.loads((legacy / "feishu-approved.json").read_text())
|
||||
assert migrated["ou_user"]["user_name"] == "Active"
|
||||
assert migrated["ou_other"]["user_name"] == "Other"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _secure_write
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue