fix(config): treat empty-dict DEFAULT_CONFIG sections as open containers in the typo gate
compression.model_thresholds.<model>, terminal.docker_env.<VAR>, lsp.servers.<lang>.*,
auxiliary.<task>.extra_body.<k> and similar free-form mappings are declared as {} in
DEFAULT_CONFIG; the fail-closed gate walked into the empty dict, found the user-chosen key
missing and refused the write. An empty dict now accepts the rest of the path, like a scalar
leaf or a platforms container does. Populated sections keep the did-you-mean refusal.
This commit is contained in:
@@ -3298,13 +3298,15 @@ def _validate_config_key(key: str) -> tuple[bool, Optional[str]]:
|
||||
if top in _OPEN_SUBKEY_TOP_LEVEL_KEYS:
|
||||
return True, None
|
||||
|
||||
# Walk DEFAULT_CONFIG: a nested ``platforms`` container or a scalar leaf hit before the path is
|
||||
# consumed both accept (the latter matches set_config_value's leaf->dict replacement); an
|
||||
# unknown sub-key fails with a same-level "did you mean" suggestion.
|
||||
# Walk DEFAULT_CONFIG: a nested ``platforms`` container, a scalar leaf, or an EMPTY dict hit
|
||||
# before the path is consumed all accept. An empty dict is a free-form mapping section
|
||||
# (``compression.model_thresholds.<model>``, ``terminal.docker_env.<VAR>``,
|
||||
# ``lsp.servers.<lang>``): its keys are user-chosen, so nothing under it can be a typo. An
|
||||
# unknown sub-key of a populated section fails with a same-level "did you mean" suggestion.
|
||||
node: Any = DEFAULT_CONFIG.get(top)
|
||||
consumed = [top]
|
||||
for seg in segments[1:]:
|
||||
if seg in _PLATFORM_CONTAINER_KEYS or not isinstance(node, dict):
|
||||
if seg in _PLATFORM_CONTAINER_KEYS or not isinstance(node, dict) or not node:
|
||||
return True, None
|
||||
if seg not in node:
|
||||
sibling = _suggest_closest_key(seg, set(node.keys()))
|
||||
|
||||
@@ -1941,3 +1941,20 @@ def test_gateway_multiplex_keys_are_recognized_config_keys():
|
||||
known, suggestion = _validate_config_key("gateway.auto_migrate")
|
||||
assert known is False
|
||||
assert suggestion == "gateway.auto_multiplex_migration"
|
||||
|
||||
|
||||
def test_empty_dict_default_sections_are_open_containers():
|
||||
"""``compression.model_thresholds.<model>`` / ``terminal.docker_env.<VAR>`` are free-form
|
||||
mappings declared as ``{}`` in DEFAULT_CONFIG: their user-chosen keys must not be refused as
|
||||
typos, while a real typo under a populated sibling section still gets a suggestion."""
|
||||
from hermes_cli.config import _validate_config_key
|
||||
from hermes_cli.config_defaults import DEFAULT_CONFIG
|
||||
assert DEFAULT_CONFIG["compression"]["model_thresholds"] == {}
|
||||
assert DEFAULT_CONFIG["terminal"]["docker_env"] == {}
|
||||
assert _validate_config_key("compression.model_thresholds.gpt-5") == (True, None)
|
||||
assert _validate_config_key("terminal.docker_env.FOO") == (True, None)
|
||||
assert _validate_config_key("lsp.servers.python.command") == (True, None)
|
||||
assert _validate_config_key("auxiliary.vision.extra_body.reasoning") == (True, None)
|
||||
known, suggestion = _validate_config_key("compression.model_threshold.gpt-5")
|
||||
assert known is False
|
||||
assert suggestion == "compression.model_thresholds"
|
||||
|
||||
Reference in New Issue
Block a user