"""Canonical keys and privilege consent through real admission.""" import shutil import pytest import hermes_yaml as yaml from tests.hermes_cli.plugin_worker_support import ( plugin_world as plugin_world, isolated_python as isolated_python, ) @pytest.mark.parametrize("query", ["trace-leaf", "trace-manifest", "observability/trace-leaf"]) def test_nested_enable_disable_and_composite_use_canonical_key(plugin_world, monkeypatch, query): from hermes_cli import plugins_cmd from rich.console import Console world = plugin_world origin, _ = world.origin(name="trace-manifest") key = "observability/trace-leaf" target = world.home / "plugins" / key shutil.copytree(origin, target) (world.home / "config.yaml").write_text(yaml.safe_dump({"plugins": { "enabled": [], "disabled": [key, "trace-leaf", "trace-manifest"]}}), encoding="utf-8") world.command("enable", name=query, no_allow_tool_override=True) assert world.enabled() == [key] config = yaml.safe_load((world.home / "config.yaml").read_text()) assert config["plugins"]["disabled"] == [] world.imports(key) world.command("disable", name=query) assert world.enabled() == [] assert yaml.safe_load((world.home / "config.yaml").read_text())["plugins"]["disabled"] == [key] # The fallback menu must persist canonical keys too, not labels: the row opens unticked (disabled) # and the user ticks it. answers = iter(("1", "")) monkeypatch.setattr("builtins.input", lambda prompt: next(answers)) plugins_cmd._run_composite_fallback([key], ["trace-manifest"], set(), {key}, [], Console()) assert world.enabled() == [key] world.imports(key) def test_ambiguous_and_unknown_names_cannot_change_config(plugin_world): from hermes_cli import plugins_cmd world = plugin_world for category in ("image_gen", "model-providers"): directory = world.home / "plugins" / category / "same-leaf" directory.mkdir(parents=True) (directory / "plugin.yaml").write_text(f"name: {category}-fixture\n", encoding="utf-8") before = (world.home / "config.yaml").read_bytes() for query in ("same-leaf", "not-installed"): with pytest.raises(SystemExit) as exc: world.command("enable", name=query) assert exc.value.code == 1 assert plugins_cmd._resolve_plugin_key("image_gen/same-leaf") == "image_gen/same-leaf" assert (world.home / "config.yaml").read_bytes() == before def test_dependency_free_enable_no_churn_and_tool_override_fails_closed(plugin_world, monkeypatch): from hermes_cli import plugins_cmd from pm import client, receipt world = plugin_world client.sync_venv(explicit=True) selected = world.selected() origin, sha = world.origin(surface=None) facts = (world.home / "config.yaml").read_bytes() world.command("install", identifier=origin.as_uri(), ref=sha, no_enable=True, allow_removed=True) assert world.selected() == selected assert (world.home / "config.yaml").read_bytes() == facts monkeypatch.setattr(plugins_cmd.sys.stdin, "isatty", lambda: True) monkeypatch.setattr(plugins_cmd.sys.stdout, "isatty", lambda: True) def eof(*args, **kwargs): raise EOFError monkeypatch.setattr("rich.console.Console.input", eof) world.command("enable", name="plugin-worker-proof") config = yaml.safe_load((world.home / "config.yaml").read_text()) # Enabling is not a request for undeclared privileges (#64228): no grant is prompted for or # written; only an explicit flag persists one. assert "allow_tool_override" not in config.get("plugins", {}).get("entries", {}).get("plugin-worker-proof", {}) world.command("enable", name="plugin-worker-proof", no_allow_tool_override=True) config = yaml.safe_load((world.home / "config.yaml").read_text()) assert config["plugins"]["entries"]["plugin-worker-proof"]["allow_tool_override"] is False assert world.selected() == selected assert receipt.latest()["venv_rebuild"]["ok"] is False bundled = world.core / "plugins/trusted-fixture" bundled.mkdir(parents=True) (bundled / "plugin.yaml").write_text("name: trusted-fixture\n", encoding="utf-8") monkeypatch.setattr("hermes_cli.plugins.get_bundled_plugins_dir", lambda: world.core / "plugins") def unexpected(*args, **kwargs): pytest.fail("bundled plugin requested privilege consent") monkeypatch.setattr("rich.console.Console.input", unexpected) world.command("enable", name="trusted-fixture") config = yaml.safe_load((world.home / "config.yaml").read_text()) assert "trusted-fixture" not in config["plugins"]["entries"] assert world.selected() == selected @pytest.mark.parametrize("config_changes", [False, True]) def test_fallback_compares_the_preinteraction_selection(plugin_world, monkeypatch, config_changes): from hermes_cli import plugins_cmd from pm import receipt world = plugin_world origin, sha = world.origin() world.command("install", identifier=origin.as_uri(), ref=sha, no_enable=True, allow_removed=True) world.command("enable", name="plugin-worker-proof", no_allow_tool_override=True) config_path = world.home / "config.yaml" before = config_path.read_bytes() selected = world.selected() monkeypatch.setattr("hermes_cli.plugins.get_bundled_plugins_dir", lambda: world.core / "plugins") monkeypatch.setattr(plugins_cmd, "_provider_categories", lambda: []) monkeypatch.setattr(plugins_cmd.sys.stdin, "isatty", lambda: True) monkeypatch.setitem(plugins_cmd.sys.modules, "curses", None) answers = iter(("1", "")) edited = before + b"model: edited-during-input\n" def respond(_prompt): answer = next(answers) if config_changes and answer == "1": config_path.write_bytes(edited) return answer monkeypatch.setattr("builtins.input", respond) plugins_cmd.cmd_toggle() latest = receipt.latest() assert latest is not None if config_changes: assert config_path.read_bytes() == edited assert world.enabled() == ["plugin-worker-proof"] assert world.selected() == selected assert latest["outcome"] == "failed" world.imports() else: assert world.enabled() == [] assert yaml.safe_load(config_path.read_text())["plugins"]["disabled"] == ["plugin-worker-proof"] assert latest["outcome"] == "ok"