diff --git a/hermes_cli/plugin_validate.py b/hermes_cli/plugin_validate.py index 490c412099..04185f8229 100644 --- a/hermes_cli/plugin_validate.py +++ b/hermes_cli/plugin_validate.py @@ -24,12 +24,12 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Tuple from hermes_cli.plugin_validate_desktop import check_desktop_surface +from hermes_cli.plugins_manifest import _CONFIG_SCHEMA_TYPES _UPPER_SNAKE_RE = re.compile(r"^[A-Z][A-Z0-9_]*$") -_CONFIG_TYPES = { - "str", "string", "int", "integer", "float", "number", - "bool", "boolean", "list", "array", "dict", "mapping", "map", -} +# Admission accepts exactly the ``config_schema`` types the loader type-checks at load time (and the +# Desktop settings renderer keys its field table on) — a private copy drifted and rejected ``secret``. +_CONFIG_TYPES = frozenset(_CONFIG_SCHEMA_TYPES) _PROBE_TIMEOUT = 30 _PROBE_SENTINEL = "HERMES_VALIDATE_JSON:" diff --git a/tests/hermes_cli/test_plugin_validate.py b/tests/hermes_cli/test_plugin_validate.py index ecfd09db4c..0569ccb03a 100644 --- a/tests/hermes_cli/test_plugin_validate.py +++ b/tests/hermes_cli/test_plugin_validate.py @@ -87,6 +87,26 @@ def test_requires_hermes_spec_is_validated(tmp_path): assert any(name == "requires_hermes" and ok for name, ok, _ in report.checks) +def test_config_schema_admits_every_type_the_loader_and_renderer_accept(tmp_path): + """A ``type:`` the Desktop settings renderer/loader accept (``secret`` + ``env:``, ``object``) must + pass admission — the catalog validator rejecting a documented type blocks pins of plugins that + declare a secret setting.""" + from hermes_cli.plugins_manifest import _CONFIG_SCHEMA_TYPES + from hermes_cli.plugins_settings import _FIELD_TYPES + + assert set(_FIELD_TYPES) == set(_CONFIG_SCHEMA_TYPES) + schema = {f"k_{t}": {"type": t} for t in _FIELD_TYPES} + schema["api_key"] = {"type": "secret", "env": "FIXTURE_API_KEY", "description": "token"} + d = _make_plugin(tmp_path, manifest=dict(BASE_MANIFEST, config_schema=schema)) + + report = validate_plugin_dir(d) + + assert ("config schema", True, "shape valid") in report.checks, report.failures + bad = validate_plugin_dir(_make_plugin( + tmp_path / "bad", manifest=dict(BASE_MANIFEST, config_schema={"x": {"type": "mapping"}}))) + assert [ok for n, ok, _ in bad.checks if n == "config schema"] == [False], bad.checks + + def test_admission_runs_the_install_scanner(tmp_path): """Admission and install must agree: a tree the installer would hard-block (dangerous) fails validation; caution findings are surfaced to the reviewer as warnings without failing."""