feat(config): derive _KNOWN_ROOT_KEYS from DEFAULT_CONFIG + warn on unknown root keys

Extracted from the config-validation portion of PR #67345 (the token-cost
half was not salvaged). Unknown top-level config keys now warn (naming the
key) instead of being silently ignored; known roots derive from
DEFAULT_CONFIG.keys() plus a small extras set for valid-on-disk roots
absent from defaults.
This commit is contained in:
loes5050 2026-07-19 00:48:53 -07:00 committed by Teknium
parent 336c3b13aa
commit f5bacee274

View file

@ -5493,14 +5493,17 @@ def check_config_version() -> Tuple[int, int]:
# Config structure validation # Config structure validation
# ============================================================================= # =============================================================================
# Fields that are valid at root level of config.yaml # Fields that are valid at root level of config.yaml.
_KNOWN_ROOT_KEYS = { # DEFAULT_CONFIG is the single source of truth for documented roots; keep this
"_config_version", "model", "providers", "fallback_model", # set derived so new defaults (skills, security, browser, …) are accepted
"fallback_providers", "credential_pool_strategies", "toolsets", # automatically. A few optional/legacy roots are valid on disk but intentionally
"agent", "terminal", "display", "compression", "delegation", # absent from DEFAULT_CONFIG (omitted when unused / alternate schema forms).
"auxiliary", "moa", "custom_providers", "context", "memory", "gateway", _EXTRA_KNOWN_ROOT_KEYS = {
"sessions", "streaming", "updates", "mcp_servers", "custom_providers", # legacy list form; modern equivalent is providers: {}
"fallback_model", # optional single dict or chain list; omitted when disabled
"mcp_servers", # MCP server definitions written by setup/tools flows
} }
_KNOWN_ROOT_KEYS = frozenset(DEFAULT_CONFIG.keys()) | _EXTRA_KNOWN_ROOT_KEYS
# Valid fields inside a custom_providers list entry # Valid fields inside a custom_providers list entry
_VALID_CUSTOM_PROVIDER_FIELDS = { _VALID_CUSTOM_PROVIDER_FIELDS = {
@ -5655,16 +5658,28 @@ def validate_config_structure(config: Optional[Dict[str, Any]] = None) -> List["
" base_url: https://...", " base_url: https://...",
)) ))
# ── Root-level keys that look misplaced ────────────────────────────── # ── Unknown / misplaced root-level keys ──────────────────────────────
# Typos like skillz:/secrity: were previously silent (only provider-like
# fields were flagged). Warn on any unknown top-level key so config
# hygiene surfaces without breaking startup.
for key in config: for key in config:
if key.startswith("_"): if key.startswith("_"):
continue continue
if key not in _KNOWN_ROOT_KEYS and key in _CUSTOM_PROVIDER_LIKE_FIELDS: if key in _KNOWN_ROOT_KEYS:
continue
if key in _CUSTOM_PROVIDER_LIKE_FIELDS:
issues.append(ConfigIssue( issues.append(ConfigIssue(
"warning", "warning",
f"Root-level key '{key}' looks misplaced — should it be under 'model:' or inside a 'custom_providers' entry?", f"Root-level key '{key}' looks misplaced — should it be under 'model:' or inside a 'custom_providers' entry?",
f"Move '{key}' under the appropriate section", f"Move '{key}' under the appropriate section",
)) ))
else:
issues.append(ConfigIssue(
"warning",
f"Unknown top-level config key '{key}' — it will be ignored",
"Check for typos, or remove the key if it is not a supported config root. "
"Run 'hermes doctor' for more detail.",
))
return issues return issues