From 4ae3c988b51e9aef0433d4dbaee974c759b2cbcb Mon Sep 17 00:00:00 2001 From: AhmetArif0 <147827411+AhmetArif0@users.noreply.github.com> Date: Sun, 31 May 2026 16:05:37 +0300 Subject: [PATCH] fix(gateway): bridge shared-key loop to nested platform config blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared-key bridging loop (allow_from, require_mention, free_response_channels, …) read only the top-level yaml platform block (yaml_cfg.get(plat.value)). When a user configured a platform solely under ``platforms:`` or ``gateway.platforms:`` with no top-level block, the loop skipped that platform entirely and all bridged keys were silently dropped into PlatformConfig.extra — making allow_from, require_mention, etc. ineffective for nested-only configs. The apply_yaml_config_fn dispatch already received this same fallback in 44f3e51 to handle plugin adapters (e.g. Discord allow_from). The shared-key loop now mirrors it: if yaml_cfg.get(plat.value) is absent, fall back to gateway.platforms. then platforms.. The enabled field is deliberately excluded from the nested fallback (guarded by _cfg_toplevel): _merge_platform_map already merged it with the correct precedence, so re-applying it from a single nested source would overwrite the correctly-merged value. Two new regression tests assert that allow_from and require_mention configured under platforms.telegram and gateway.platforms.telegram are bridged into PlatformConfig.extra. All 54 existing config tests pass. --- gateway/config.py | 21 +++++++++++- tests/gateway/test_config.py | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/gateway/config.py b/gateway/config.py index a1b61fed5..f130fa7da 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -843,6 +843,25 @@ def load_gateway_config() -> GatewayConfig: if plat == Platform.LOCAL: continue platform_cfg = yaml_cfg.get(plat.value) + _cfg_toplevel = isinstance(platform_cfg, dict) + # Fall back to the platform's block under ``platforms`` / + # ``gateway.platforms`` so shared-key bridging (allow_from, + # require_mention, free_response_channels, …) still runs when + # the user configured the platform only under those nested paths + # and not via a top-level block. Mirrors the identical fallback + # already applied to the apply_yaml_config_fn dispatch below + # (#44f3e51). + # Note: ``enabled`` is only written to plat_data from a + # top-level block (``_cfg_toplevel``); for nested-only configs + # ``_merge_platform_map`` already merged it with the correct + # precedence, so re-applying it here would overwrite that. + if not _cfg_toplevel: + for _src in (gateway_platforms, yaml_cfg.get("platforms")): + if isinstance(_src, dict): + _candidate = _src.get(plat.value) + if isinstance(_candidate, dict): + platform_cfg = _candidate + break if not isinstance(platform_cfg, dict): continue # Collect bridgeable keys from this platform section @@ -903,7 +922,7 @@ def load_gateway_config() -> GatewayConfig: bridged["channel_prompts"] = channel_prompts if "gateway_restart_notification" in platform_cfg: bridged["gateway_restart_notification"] = platform_cfg["gateway_restart_notification"] - enabled_was_explicit = "enabled" in platform_cfg + enabled_was_explicit = _cfg_toplevel and "enabled" in platform_cfg if not bridged and not enabled_was_explicit: continue plat_data, extra = _ensure_platform_extra_dict(platforms_data, plat.value) diff --git a/tests/gateway/test_config.py b/tests/gateway/test_config.py index 9950f967f..4f2756dba 100644 --- a/tests/gateway/test_config.py +++ b/tests/gateway/test_config.py @@ -477,6 +477,69 @@ class TestLoadGatewayConfig: assert telegram.token == "top-token" assert telegram.extra["reply_prefix"] == "top" + def test_shared_key_loop_bridges_allow_from_from_nested_platforms(self, tmp_path, monkeypatch): + """Regression: shared-key loop must bridge allow_from / require_mention + into PlatformConfig.extra even when the platform is configured only + under ``platforms:`` (no top-level ``telegram:`` block). + + Before the fix, ``platform_cfg = yaml_cfg.get('telegram')`` returned + None for nested-only configs, so the loop skipped the platform entirely + and allow_from was silently ignored. The apply_yaml_config_fn dispatch + received the same fix in #44f3e51; the shared-key loop now mirrors it. + """ + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + config_path = hermes_home / "config.yaml" + config_path.write_text( + "platforms:\n" + " telegram:\n" + " allow_from:\n" + " - \"111222333\"\n" + " - \"444555666\"\n" + " require_mention: true\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + telegram = config.platforms[Platform.TELEGRAM] + assert telegram.extra.get("allow_from") == ["111222333", "444555666"], ( + "allow_from configured under platforms.telegram must be bridged " + "into PlatformConfig.extra by the shared-key loop" + ) + assert telegram.extra.get("require_mention") is True, ( + "require_mention configured under platforms.telegram must be " + "bridged into PlatformConfig.extra by the shared-key loop" + ) + + def test_shared_key_loop_bridges_allow_from_from_nested_gateway_platforms(self, tmp_path, monkeypatch): + """Same regression check for ``gateway.platforms:`` path.""" + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + config_path = hermes_home / "config.yaml" + config_path.write_text( + "gateway:\n" + " platforms:\n" + " telegram:\n" + " allow_from:\n" + " - \"777888999\"\n" + " require_mention: false\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + telegram = config.platforms[Platform.TELEGRAM] + assert telegram.extra.get("allow_from") == ["777888999"], ( + "allow_from configured under gateway.platforms.telegram must be " + "bridged into PlatformConfig.extra by the shared-key loop" + ) + assert telegram.extra.get("require_mention") is False + def test_bridges_quoted_false_session_notify_from_config_yaml(self, tmp_path, monkeypatch): hermes_home = tmp_path / ".hermes" hermes_home.mkdir()