fix(gateway): key-presence precedence for session_reset/stt nested fallback

Follow-up for salvaged PR #59779: the session_reset and stt fallbacks
used truthiness/type checks, so a present-but-empty top-level value was
silently replaced by the nested gateway.* form — inconsistent with the
key-presence precedence every other key in the block uses. Switch both
to 'key not in yaml_cfg' gating and add precedence regression tests.
This commit is contained in:
Teknium 2026-07-20 03:19:34 -07:00
parent e9bd3b6eeb
commit 73543744bc
2 changed files with 48 additions and 2 deletions

View file

@ -1188,8 +1188,12 @@ def load_gateway_config() -> GatewayConfig:
# Map config.yaml keys → GatewayConfig.from_dict() schema.
# Each key overwrites whatever gateway.json may have set.
# Precedence contract: key-presence at the TOP LEVEL wins; the
# nested gateway.* form is consulted only when the top-level key
# is absent (not merely falsy/mistyped), so a present-but-empty
# top-level value is never silently replaced by the nested one.
sr = yaml_cfg.get("session_reset")
if not (sr and isinstance(sr, dict)) and isinstance(gateway_section, dict):
if "session_reset" not in yaml_cfg and isinstance(gateway_section, dict):
sr = gateway_section.get("session_reset")
if sr and isinstance(sr, dict):
gw_data["default_reset_policy"] = sr
@ -1208,7 +1212,7 @@ def load_gateway_config() -> GatewayConfig:
)
stt_cfg = yaml_cfg.get("stt")
if not isinstance(stt_cfg, dict) and isinstance(gateway_section, dict):
if "stt" not in yaml_cfg and isinstance(gateway_section, dict):
stt_cfg = gateway_section.get("stt")
if isinstance(stt_cfg, dict):
gw_data["stt"] = stt_cfg

View file

@ -827,6 +827,48 @@ class TestLoadGatewayConfig:
assert config.always_log_local is True
def test_present_empty_top_level_session_reset_blocks_nested_fallback(self, tmp_path, monkeypatch):
"""Key-presence precedence: a present (even empty) top-level
session_reset must NOT be replaced by gateway.session_reset
the fallback fires only when the top-level key is absent."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
"session_reset: {}\n"
"gateway:\n"
" session_reset:\n"
" mode: idle\n"
" idle_minutes: 30\n",
encoding="utf-8",
)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
config = load_gateway_config()
# The nested value must not leak through the present top-level key.
assert config.default_reset_policy.mode != "idle"
def test_present_top_level_stt_blocks_nested_fallback(self, tmp_path, monkeypatch):
"""Key-presence precedence for stt: a present top-level stt (even
mistyped/non-dict) must not be replaced by gateway.stt."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
"stt: {}\n"
"gateway:\n"
" stt:\n"
" enabled: false\n",
encoding="utf-8",
)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
config = load_gateway_config()
# gateway.stt.enabled=false must NOT win over the present top-level stt.
assert config.stt_enabled is True
def test_relay_platform_enabled_from_env_url(self, tmp_path, monkeypatch):
"""GATEWAY_RELAY_URL must enable Platform.RELAY in config.platforms so
start_gateway()'s connect loop actually dials the connector. Registering