feat(managed-scope): surface managed scope in config show and doctor

- show_config prints an administrator header naming the managed source and
  lists the pinned config/env keys when a scope is active (silent otherwise).
- hermes doctor gains a managed_scope_check under Configuration Files that
  reports the resolved managed dir + pinned key counts, and flags a
  HERMES_MANAGED_DIR redirect (the documented foot-gun).
This commit is contained in:
Ben 2026-06-18 14:17:16 +10:00 committed by Teknium
parent 4f9e15df97
commit ddd519ea70
3 changed files with 128 additions and 2 deletions

View file

@ -6308,12 +6308,38 @@ def redact_key(key: str) -> str:
def show_config():
"""Display current configuration."""
config = load_config()
print()
print(color("┌─────────────────────────────────────────────────────────┐", Colors.CYAN))
print(color("│ ⚕ Hermes Configuration │", Colors.CYAN))
print(color("└─────────────────────────────────────────────────────────┘", Colors.CYAN))
# Managed scope: surface that some settings are administrator-pinned so the
# user understands why their config.yaml value may not be the effective one.
from hermes_cli import managed_scope
_managed_keys = managed_scope.managed_config_keys()
_managed_env = managed_scope.load_managed_env()
if _managed_keys or _managed_env:
_managed_dir = managed_scope.get_managed_dir()
print()
print(color(
f" ⚷ Some settings are managed by your administrator ({_managed_dir}) "
f"and cannot be changed",
Colors.YELLOW,
Colors.BOLD,
))
if _managed_keys:
print(color(
f" Managed config keys: {', '.join(sorted(_managed_keys))}",
Colors.YELLOW,
))
if _managed_env:
print(color(
f" Managed env keys: {', '.join(sorted(_managed_env))}",
Colors.YELLOW,
))
# Paths
print()
print(color("◆ Paths", Colors.CYAN, Colors.BOLD))

View file

@ -462,6 +462,31 @@ def _build_apikey_providers_list() -> list:
return _static
def managed_scope_check() -> None:
"""Report the active managed scope (resolved dir + pinned key counts).
Silent when no managed scope is present. When the managed directory was
resolved from the HERMES_MANAGED_DIR override (rather than the system
default), that is surfaced too a redirected scope is the documented
foot-gun (see docs/design/managed-scope.md §7) and an operator should see it.
"""
try:
from hermes_cli import managed_scope
managed_dir = managed_scope.get_managed_dir()
except Exception: # noqa: BLE001 — diagnostics must never crash
return
if managed_dir is None:
return
n_cfg = len(managed_scope.managed_config_keys())
n_env = len(managed_scope.load_managed_env())
check_ok(
f"Managed scope active: {n_cfg} config key(s), {n_env} env key(s) "
f"pinned by {managed_dir}"
)
if os.environ.get("HERMES_MANAGED_DIR", "").strip():
check_info(f"managed dir set via HERMES_MANAGED_DIR={managed_dir}")
def run_doctor(args):
"""Run diagnostic checks."""
should_fix = getattr(args, 'fix', False)
@ -642,6 +667,8 @@ def run_doctor(args):
check_warn(name, "(optional, not installed)")
_section("Configuration Files")
# Managed scope (administrator-pinned config/env), when present.
managed_scope_check()
# Check ~/.hermes/.env (primary location for user config)
env_path = HERMES_HOME / '.env'
if env_path.exists():

View file

@ -0,0 +1,73 @@
"""Surfacing tests — managed scope shown in `config show` and `hermes doctor`."""
import pytest
@pytest.fixture
def homes(tmp_path, monkeypatch):
home = tmp_path / "home"
home.mkdir()
managed = tmp_path / "managed"
managed.mkdir()
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setenv("HERMES_MANAGED_DIR", str(managed))
(home / "config.yaml").write_text("model:\n default: user/model\n", encoding="utf-8")
(managed / "config.yaml").write_text(
"model:\n default: managed/model\n", encoding="utf-8"
)
import hermes_cli.config as cfg
from hermes_cli import managed_scope
cfg._LOAD_CONFIG_CACHE.clear()
cfg._RAW_CONFIG_CACHE.clear()
managed_scope.invalidate_managed_cache()
return home, managed
def test_config_show_flags_managed(homes, capsys):
from hermes_cli.config import show_config
show_config()
out = capsys.readouterr().out.lower()
assert "managed" in out # header + key list present
assert "model.default" in out # the pinned key is named
assert "managed/model" in out # effective (managed) value, not user/model
def test_config_show_no_managed_scope_silent(tmp_path, monkeypatch, capsys):
"""With no managed scope, the managed header must not appear."""
home = tmp_path / "home"
home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setenv("HERMES_MANAGED_DIR", str(tmp_path / "nope"))
(home / "config.yaml").write_text("model:\n default: user/model\n", encoding="utf-8")
import hermes_cli.config as cfg
from hermes_cli import managed_scope
cfg._LOAD_CONFIG_CACHE.clear()
cfg._RAW_CONFIG_CACHE.clear()
managed_scope.invalidate_managed_cache()
from hermes_cli.config import show_config
show_config()
out = capsys.readouterr().out.lower()
assert "managed by your administrator" not in out
def test_doctor_reports_managed_scope(homes, capsys):
# homes fixture has 1 managed config key (model.default) and 0 managed env keys.
from hermes_cli import doctor
doctor.managed_scope_check()
out = capsys.readouterr().out.lower()
assert "managed scope active" in out
assert str(homes[1]).lower() in out # resolved dir reported
assert "1 config key" in out
def test_doctor_silent_with_no_managed_scope(tmp_path, monkeypatch, capsys):
monkeypatch.setenv("HERMES_MANAGED_DIR", str(tmp_path / "nope"))
from hermes_cli import managed_scope, doctor
managed_scope.invalidate_managed_cache()
doctor.managed_scope_check()
assert capsys.readouterr().out.strip() == ""