fix(config): apply managed overlay when the user config.yaml is absent
Narrow the PR's load_yaml_layer rewrite to the one behavior the relay fix needs: an absent user file is an empty layer, so administrator- pinned values (a managed relay opt-out among them) still reach the gateway on a host with no config.yaml. Everything else returns to the loader's existing contract. A MALFORMED user file still raises so load_gateway_config() keeps emitting its 'Failed to process config.yaml — falling back to .env / gateway.json' warning instead of silently applying the managed layer over the broken one; the loader keeps its own yaml read rather than importing the CLI's raw reader. Changing the malformed-file contract touches every platform and belongs in its own PR.
This commit is contained in:
+14
-12
@@ -336,19 +336,21 @@ def bridge_core_env_settings(yaml_cfg: dict, platforms_data: dict) -> None:
|
||||
|
||||
|
||||
def load_yaml_layer(home: Path, gw_data: dict) -> None:
|
||||
"""Overlay raw user + managed YAML onto *gw_data* without introducing CLI defaults."""
|
||||
from hermes_cli.config import read_user_config_raw
|
||||
from hermes_cli import managed_scope
|
||||
"""Overlay ``config.yaml`` + managed YAML onto *gw_data* in place. Raises on a malformed user
|
||||
file (caller warns + falls back). An ABSENT user file is an empty layer, not a reason to skip
|
||||
the administrator's values: a fleet host with no ``config.yaml`` must still honor them.
|
||||
"""
|
||||
import yaml
|
||||
|
||||
# Match the raw runtime/predicate readers: absent, unreadable or malformed
|
||||
# user YAML is an empty layer, not a reason to skip administrator settings.
|
||||
# Use the explicit home rather than a process-global path (multiplex-safe).
|
||||
try:
|
||||
yaml_cfg = read_user_config_raw(home / "config.yaml")
|
||||
except Exception as e:
|
||||
logger.warning("Failed to read %s; applying managed config over an empty user layer: %s",
|
||||
home / "config.yaml", e)
|
||||
yaml_cfg = {}
|
||||
config_yaml_path = home / "config.yaml"
|
||||
yaml_cfg: dict = {}
|
||||
if config_yaml_path.exists():
|
||||
with open(config_yaml_path, encoding="utf-8") as f:
|
||||
yaml_cfg = yaml.safe_load(f) or {}
|
||||
|
||||
# Managed scope: overlay administrator-pinned values (this loader bypasses
|
||||
# hermes_cli.config.load_config, so managed quick_commands / stt would otherwise be ignored).
|
||||
from hermes_cli import managed_scope
|
||||
yaml_cfg = managed_scope.apply_managed_overlay(yaml_cfg)
|
||||
if not yaml_cfg:
|
||||
return
|
||||
|
||||
@@ -224,21 +224,18 @@ def test_legacy_json_disable_is_advisory_like_other_platforms(profile, monkeypat
|
||||
assert config.platforms[Platform.TELEGRAM].enabled
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user_yaml", ["no-files", "absent", "empty", "null", "list", "malformed", "unreadable", "sibling", "enabled", "disabled"])
|
||||
@pytest.mark.parametrize("user_yaml", ["no-files", "absent", "empty", "null", "sibling", "enabled", "disabled"])
|
||||
@pytest.mark.parametrize("managed_state", ["absent", "url-only", "enabled", "disabled"])
|
||||
def test_managed_layer_agrees_with_native_config(profile, monkeypatch, user_yaml, managed_state):
|
||||
"""Managed leaves override user leaves, not CLI defaults or a second merge policy."""
|
||||
path = profile / "config.yaml"
|
||||
user_docs = {
|
||||
"empty": "", "null": "null\n", "list": "- not-a-config\n",
|
||||
"malformed": "platforms: [\n",
|
||||
"empty": "", "null": "null\n",
|
||||
"sibling": "platforms:\n relay:\n extra:\n user_sibling: kept\n",
|
||||
"enabled": "platforms:\n relay:\n enabled: true\n",
|
||||
"disabled": "platforms:\n relay:\n enabled: false\n",
|
||||
}
|
||||
if user_yaml == "unreadable":
|
||||
path.mkdir() # A directory fails open even when tests run as root.
|
||||
elif user_yaml not in {"absent", "no-files"}:
|
||||
if user_yaml not in {"absent", "no-files"}:
|
||||
path.write_text(user_docs[user_yaml])
|
||||
managed = profile / "managed"
|
||||
managed.mkdir()
|
||||
|
||||
Reference in New Issue
Block a user