Files
hermes-agent/tests/hermes_cli/test_plugin_ownership_ledger.py
ethernet 284dbaf537 fix(pm): isolate bootstrap dependencies and unify YAML on ruamel
Activation reaches plugin discovery before the application dependencies
exist. Give PM its own locked Python project and runtime so it can install
or repair the application without importing that dependency tree.

Keep PM outside the application workspace. A shared uv workspace resolves
the application graph and cannot provide this isolation. Route mutations
through an isolated worker and preserve transaction callbacks, cancellation,
custom package registrations, and correlated receipts.

Use the same runtime builder for source installs and packaged payloads.
Keep offline wheelhouse support in that builder. Nix builds the independent
PM lock as a separate derivation. Refuse lazy-disabled bootstrap before
installing tools or dependencies.

Move first-party YAML readers and writers to ruamel. Keep the application
lock's transitive PyYAML requirements for third-party packages.

Verification:
- Focused canonical Python suite: 177 passed, 1 host-gated skip.
- Electron backend probes: 12 passed. Electron typecheck passed.
- Both uv locks, scoped lint, Bash syntax, and whitespace checks passed.
- Cold activation, corrupt-app repair, offline staging, and relocation ran.
- Built and exercised the Nix PM runtime and standalone YAML merge script.

Six broader caller test files retain the same 24 failing test IDs as an
archive of HEAD. The existing real-home guard blocks those tests before
they can exercise the affected paths. No full-suite pass is claimed.
Native Windows signing and full Bionic package execution remain unverified.
2026-09-11 12:23:51 -04:00

1365 lines
52 KiB
Python

"""End-to-end coverage for plugin registration ownership and reload cleanup."""
from __future__ import annotations
from concurrent.futures import ThreadPoolExecutor, TimeoutError
from pathlib import Path
from threading import Event
from time import monotonic, sleep
from types import MethodType
import hermes_yaml as yaml
def _write_plugin(hermes_home: Path) -> None:
plugin_dir = hermes_home / "plugins" / "ledger_probe"
plugin_dir.mkdir(parents=True)
(plugin_dir / "plugin.yaml").write_text(
yaml.safe_dump(
{
"name": "ledger_probe",
"version": "0.1.0",
"description": "ownership ledger probe",
}
)
)
(plugin_dir / "SKILL.md").write_text("# Ledger probe\n")
(plugin_dir / "__init__.py").write_text(
"from pathlib import Path\n"
"\n"
"def _hook(**kwargs):\n"
" return {'hook': 'ledger'}\n"
"\n"
"def _middleware(**kwargs):\n"
" return {'middleware': 'ledger'}\n"
"\n"
"UNLOADED = []\n"
"\n"
"def register(ctx):\n"
" ctx.register_tool(\n"
" name='ledger_probe_tool',\n"
" toolset='plugin_ledger_probe',\n"
" schema={'name': 'ledger_probe_tool', 'parameters': {'type': 'object', 'properties': {}}},\n"
" handler=lambda args, **kwargs: 'ledger',\n"
" )\n"
" ctx.register_platform(\n"
" name='ledger_probe_platform',\n"
" label='Ledger probe',\n"
" adapter_factory=lambda config: object(),\n"
" check_fn=lambda: True,\n"
" )\n"
" ctx.register_cli_command(\n"
" 'ledger-probe-cli', 'Ledger CLI', lambda parser: None,\n"
" handler_fn=lambda args: None,\n"
" )\n"
" ctx.register_command(\n"
" 'ledger-probe-command', lambda args: args,\n"
" description='Ledger command',\n"
" )\n"
" ctx.register_hook('pre_tool_call', _hook)\n"
" ctx.register_middleware('tool_request', _middleware)\n"
" ctx.register_auxiliary_task(\n"
" key='ledger_probe_task',\n"
" display_name='Ledger probe task',\n"
" description='Ledger task',\n"
" )\n"
" ctx.register_skill(\n"
" 'ledger-probe', Path(__file__).with_name('SKILL.md'),\n"
" 'Ledger skill',\n"
" )\n"
" ctx.register_system_prompt_section(\n"
" 'ledger-probe-section', 'ledger probe section content',\n"
" )\n"
" ctx.register_approval_transport(\n"
" 'ledger_probe_transport', lambda request: 'deny',\n"
" )\n"
" ctx.on_unload(lambda: UNLOADED.append('ledger_probe'))\n"
)
(hermes_home / "config.yaml").write_text(
yaml.safe_dump({"plugins": {"enabled": ["ledger_probe"]}})
)
def _write_profile_probe(hermes_home: Path, marker: str) -> None:
plugin_dir = hermes_home / "plugins" / "profile_probe"
plugin_dir.mkdir(parents=True)
(plugin_dir / "plugin.yaml").write_text(
yaml.safe_dump(
{
"name": "profile_probe",
"version": "0.1.0",
"description": f"profile probe {marker}",
}
)
)
(plugin_dir / "__init__.py").write_text(
"def register(ctx):\n"
" ctx.register_tool(\n"
" name='shared_profile_tool',\n"
" toolset='profile_probe',\n"
" schema={'name': 'shared_profile_tool', 'parameters': {'type': 'object', 'properties': {}}},\n"
f" handler=lambda args, **kwargs: {marker!r},\n"
" )\n"
" ctx.register_platform(\n"
" name='shared_profile_platform',\n"
f" label={marker!r},\n"
f" adapter_factory=lambda config: {marker!r},\n"
" check_fn=lambda: True,\n"
" )\n"
)
(hermes_home / "config.yaml").write_text(
yaml.safe_dump({"plugins": {"enabled": ["profile_probe"]}})
)
def test_load_force_reload_and_unload_remove_every_manager_registration(
tmp_path,
monkeypatch,
):
"""A real temporary plugin has one live registration after each reload."""
import hermes_cli.plugins as plugins_mod
from gateway.platform_registry import platform_registry
from hermes_cli.plugins import PluginManager
from tools.registry import registry
hermes_home = tmp_path / "hermes"
_write_plugin(hermes_home)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
monkeypatch.setattr(
plugins_mod,
"get_bundled_plugins_dir",
lambda: tmp_path / "empty-bundled",
)
monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])
manager = PluginManager()
manager.discover_and_load()
first_tool = registry.get_entry("ledger_probe_tool")
first_platform = platform_registry.get("ledger_probe_platform")
first_hook = manager._hooks["pre_tool_call"][0]
first_middleware = manager._middleware["tool_request"][0]
first_command = manager._plugin_commands["ledger-probe-command"]
first_cli_command = manager._cli_commands["ledger-probe-cli"]
first_skill = manager._plugin_skills["ledger_probe:ledger-probe"]
module_name = manager._plugins["ledger_probe"].module.__name__
assert first_tool is not None
assert first_platform is not None
assert set(registration.kind for registration in manager._ownership_ledger["ledger_probe"]) == {
"tool",
"platform",
"cli_command",
"command",
"hook",
"middleware",
"auxiliary_task",
"skill",
"tool_override_policy",
"system_prompt_section",
"approval_transport",
"on_unload",
}
assert "ledger-probe-section" in manager._system_prompt_sections
assert "ledger_probe_transport" in manager._approval_transports
manager.discover_and_load(force=True)
second_tool = registry.get_entry("ledger_probe_tool")
second_platform = platform_registry.get("ledger_probe_platform")
assert second_tool is not None and second_tool is not first_tool
assert second_platform is not None and second_platform is not first_platform
assert second_tool.handler is not first_tool.handler
assert first_hook not in manager._hooks["pre_tool_call"]
assert first_middleware not in manager._middleware["tool_request"]
assert len(manager._hooks["pre_tool_call"]) == 1
assert len(manager._middleware["tool_request"]) == 1
assert manager._plugin_commands["ledger-probe-command"] is not first_command
assert manager._cli_commands["ledger-probe-cli"] is not first_cli_command
assert manager._plugin_skills["ledger_probe:ledger-probe"] is not first_skill
assert len(manager._aux_tasks) == 1
assert [
entry
for entry in platform_registry.plugin_entries()
if entry.name == "ledger_probe_platform"
] == [second_platform]
assert manager.unload("ledger_probe") is True
assert registry.get_entry("ledger_probe_tool") is None
assert not platform_registry.is_registered("ledger_probe_platform")
assert "pre_tool_call" not in manager._hooks
assert "tool_request" not in manager._middleware
assert "ledger-probe-command" not in manager._plugin_commands
assert "ledger-probe-cli" not in manager._cli_commands
assert "ledger_probe:ledger-probe" not in manager._plugin_skills
assert "ledger-probe-section" not in manager._system_prompt_sections
assert "ledger_probe_transport" not in manager._approval_transports
assert manager._plugins == {} or "ledger_probe" not in manager._plugins
reloaded_module_name = None
import sys as _sys
for _name, _mod in list(_sys.modules.items()):
if getattr(_mod, "UNLOADED", None) and "ledger_probe" in _mod.UNLOADED:
reloaded_module_name = _name
break
assert reloaded_module_name is not None, "on_unload callback never fired"
assert manager._aux_tasks == {}
assert manager._ownership_ledger == {}
assert registry.snapshot_plugin_override_policy(
module_name, scope=manager.scope_key
) is None
assert registry.plugin_scope_for_module(module_name) == manager.scope_key
def test_reverse_unload_restores_an_overridden_platform_registration():
"""Reverse teardown reveals an older entry before removing it."""
from gateway.platform_registry import platform_registry
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
name = "ledger_override_platform"
scope = platform_registry.current_scope_key()
previous = platform_registry.snapshot_registration(name, scope=scope)
manager_a = PluginManager(scope_key=scope)
manager_b = PluginManager(scope_key=scope)
context_a = PluginContext(
PluginManifest(name="ledger_owner_a", key="ledger_owner_a"), manager_a
)
context_b = PluginContext(
PluginManifest(name="ledger_owner_b", key="ledger_owner_b"), manager_b
)
try:
handle_a = context_a.register_platform(
name=name,
label="Ledger A",
adapter_factory=lambda config: "a",
check_fn=lambda: True,
)
entry_a = platform_registry.get(name)
handle_b = context_b.register_platform(
name=name,
label="Ledger B",
adapter_factory=lambda config: "b",
check_fn=lambda: True,
)
entry_b = platform_registry.get(name)
assert handle_a is not None and handle_b is not None
assert entry_a is not None and entry_b is not None
assert entry_a is not entry_b
handle_b.dispose()
assert platform_registry.get(name) is entry_a
handle_a.dispose()
assert platform_registry.snapshot_registration(name, scope=scope) == previous
finally:
# The test uses a deliberately unique name, but restore any state that
# a surrounding test may have installed under it.
current = platform_registry.snapshot_registration(name, scope=scope)
platform_registry.restore_registration(
name, current, previous, scope=scope
)
def test_targeted_unload_does_not_resurrect_an_older_tool_override():
"""The tool overlay follows the same arbitrary-order ownership contract."""
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
from tools.registry import registry
name = "ledger_out_of_order_tool"
scope = registry.current_scope_key()
previous = registry.snapshot_registration(name, scope=scope)
manager_a = PluginManager(scope_key=scope)
manager_b = PluginManager(scope_key=scope)
context_a = PluginContext(PluginManifest(name="tool_a", key="tool_a"), manager_a)
context_b = PluginContext(PluginManifest(name="tool_b", key="tool_b"), manager_b)
def register(context, marker):
return context.register_tool(
name=name,
toolset="ledger_test",
schema={
"name": name,
"parameters": {"type": "object", "properties": {}},
},
handler=lambda args, **kwargs: marker,
)
try:
assert register(context_a, "a") is not None
old_entry = registry.get_entry(name, scope=scope)
assert register(context_b, "b") is not None
new_entry = registry.get_entry(name, scope=scope)
assert old_entry is not None and new_entry is not old_entry
manager_a.unload("tool_a")
assert registry.get_entry(name, scope=scope) is new_entry
assert name not in manager_a._plugin_tool_names
manager_b.unload("tool_b")
assert registry.get_entry(name, scope=scope) is not old_entry
assert registry.snapshot_registration(name, scope=scope) is previous
finally:
current = registry.snapshot_registration(name, scope=scope)
if current is not None:
registry.restore_registration(name, current, previous, scope=scope)
def test_rejected_tool_registration_does_not_claim_global_fallback():
"""Effective fallback identity cannot masquerade as a successful write."""
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
from tools.registry import registry
name = "ledger_rejected_tool"
previous = registry.snapshot_registration(name)
def shared_handler(args, **kwargs):
return "base"
registry.register(
name=name,
toolset="ledger_base",
schema={"name": name, "parameters": {"type": "object", "properties": {}}},
handler=shared_handler,
)
base_entry = registry.snapshot_registration(name)
manager = PluginManager()
context = PluginContext(
PluginManifest(name="rejected_owner", key="rejected_owner"), manager
)
try:
handle = context.register_tool(
name=name,
toolset="different_toolset",
schema={
"name": name,
"parameters": {"type": "object", "properties": {}},
},
handler=shared_handler,
)
assert handle is None
assert name not in manager._plugin_tool_names
assert manager._ownership_ledger == {}
assert registry.snapshot_registration(name) is base_entry
finally:
if base_entry is not None:
registry.restore_registration(name, base_entry, previous)
def test_plugin_context_cannot_shadow_same_toolset_global_with_core_callable():
"""Explicit context scope cannot launder an imported/core handler."""
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
from tools.registry import registry
name = "ledger_same_toolset_global"
previous = registry.snapshot_registration(name)
registry.register(
name=name,
toolset="ledger_same_toolset",
schema={"name": name, "parameters": {"type": "object", "properties": {}}},
handler=lambda args, **kwargs: "base",
)
base_entry = registry.snapshot_registration(name)
manager = PluginManager()
context = PluginContext(
PluginManifest(name="core_callable", key="core_callable"), manager
)
try:
assert context.register_tool(
name=name,
toolset="ledger_same_toolset",
schema={
"name": name,
"parameters": {"type": "object", "properties": {}},
},
handler=str,
) is None
assert registry.get_entry(name, scope=manager.scope_key) is base_entry
assert registry.snapshot_registration(name, scope=manager.scope_key) is None
finally:
if base_entry is not None:
registry.restore_registration(name, base_entry, previous)
def test_rejected_tool_registration_does_not_claim_local_predecessor():
"""A same-handler rejection cannot manufacture a replacement lease."""
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
from tools.registry import registry
name = "ledger_rejected_local_tool"
scope = registry.current_scope_key()
previous = registry.snapshot_registration(name, scope=scope)
def shared_handler(args, **kwargs):
return "shared"
manager_a = PluginManager(scope_key=scope)
manager_b = PluginManager(scope_key=scope)
context_a = PluginContext(PluginManifest(name="local_owner", key="local_owner"), manager_a)
context_b = PluginContext(PluginManifest(name="false_owner", key="false_owner"), manager_b)
try:
assert context_a.register_tool(
name=name,
toolset="owner_toolset",
schema={"name": name, "parameters": {"type": "object", "properties": {}}},
handler=shared_handler,
) is not None
owner_entry = registry.snapshot_registration(name, scope=scope)
assert context_b.register_tool(
name=name,
toolset="different_toolset",
schema={"name": name, "parameters": {"type": "object", "properties": {}}},
handler=shared_handler,
) is None
assert manager_b._ownership_ledger == {}
manager_a.unload("local_owner")
assert registry.snapshot_registration(name, scope=scope) is previous
assert owner_entry is not None
finally:
current = registry.snapshot_registration(name, scope=scope)
if current is not None:
registry.restore_registration(name, current, previous, scope=scope)
def test_scoped_plugin_cannot_deregister_a_process_global_tool():
"""Profile-local plugin cleanup must never mutate the shared base layer."""
from unittest.mock import patch
import pytest
from tools.registry import ToolRegistry
registry = ToolRegistry()
name = "ledger_global_base"
registry.register(
name=name,
toolset="ledger_base",
schema={"name": name, "parameters": {"type": "object", "properties": {}}},
handler=lambda args, **kwargs: "base",
)
module_name = "hermes_plugins.scoped_cleanup"
registry.register_plugin_override_policy(
module_name, True, scope="/profiles/isolated"
)
with patch.object(ToolRegistry, "_caller_module", return_value=module_name):
with pytest.raises(PermissionError, match="process-global"):
registry.deregister(name)
assert registry.snapshot_registration(name) is not None
def test_shared_entrypoint_module_uses_the_active_profile_scope(tmp_path):
"""One pip module can serve A and B without becoming process-global."""
import pytest
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
from tools.registry import ToolRegistry
registry = ToolRegistry()
module_name = "third_party.shared_hermes_plugin"
home_a = str((tmp_path / "entrypoint-a").resolve())
home_b = str((tmp_path / "entrypoint-b").resolve())
home_c = str((tmp_path / "entrypoint-c").resolve())
policy_a = registry.register_plugin_override_policy(
module_name, False, scope=home_a
)
policy_b = registry.register_plugin_override_policy(
module_name, False, scope=home_b
)
handler = eval("lambda args, **kwargs: 'shared'", {"__name__": module_name})
def register_in(home, name):
token = set_hermes_home_override(home)
try:
registry.register(
name=name,
toolset="entrypoint_shared",
schema={
"name": name,
"parameters": {"type": "object", "properties": {}},
},
handler=handler,
)
finally:
reset_hermes_home_override(token)
register_in(home_a, "shared_entrypoint_a")
register_in(home_b, "shared_entrypoint_b")
assert registry.snapshot_registration("shared_entrypoint_a", scope=home_a) is not None
assert registry.snapshot_registration("shared_entrypoint_a", scope=home_b) is None
assert registry.snapshot_registration("shared_entrypoint_b", scope=home_b) is not None
assert registry.snapshot_registration("shared_entrypoint_b") is None
from unittest.mock import patch
token = set_hermes_home_override(home_a)
try:
with patch.object(ToolRegistry, "_caller_module", return_value=module_name):
registry.deregister("shared_entrypoint_a")
finally:
reset_hermes_home_override(token)
assert registry.snapshot_registration("shared_entrypoint_a", scope=home_a) is None
assert registry.snapshot_registration("shared_entrypoint_b", scope=home_b) is not None
# Policy unload revokes authorization but durable scope attribution keeps
# a stale delayed callback out of the process-global registry.
registry.restore_plugin_override_policy(
module_name, policy_a, None, scope=home_a
)
registry.restore_plugin_override_policy(
module_name, policy_b, None, scope=home_b
)
register_in(home_a, "shared_entrypoint_stale")
assert registry.snapshot_registration("shared_entrypoint_stale", scope=home_a)
assert registry.snapshot_registration("shared_entrypoint_stale") is None
with pytest.raises(PermissionError, match="multiple profiles"):
register_in(home_c, "shared_entrypoint_ambiguous")
assert registry.snapshot_registration("shared_entrypoint_ambiguous") is None
def test_decorated_plugin_callable_keeps_its_defining_module_scope(tmp_path):
"""functools.wraps must not replace the plugin wrapper's provenance."""
import functools
from tools.registry import ToolRegistry
registry = ToolRegistry()
module_name = "third_party.decorated_plugin"
scope = str((tmp_path / "decorated-profile").resolve())
registry.register_plugin_override_policy(module_name, False, scope=scope)
namespace = {"__name__": module_name, "functools": functools}
exec(
"@functools.wraps(str)\n"
"def handler(args, **kwargs):\n"
" return 'decorated'\n",
namespace,
)
handler = namespace["handler"]
registry.register(
name="decorated_plugin_tool",
toolset="decorated_plugin",
schema={
"name": "decorated_plugin_tool",
"parameters": {"type": "object", "properties": {}},
},
handler=handler,
)
assert registry.snapshot_registration(
"decorated_plugin_tool", scope=scope
) is not None
assert registry.snapshot_registration("decorated_plugin_tool") is None
def test_entrypoint_policy_uses_the_most_specific_module_prefix(tmp_path):
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
from tools.registry import ToolRegistry
registry = ToolRegistry()
broad_scope = str((tmp_path / "broad").resolve())
narrow_scope = str((tmp_path / "narrow").resolve())
registry.register_plugin_override_policy("vendor", True, scope=broad_scope)
registry.register_plugin_override_policy(
"vendor.plugin", False, scope=narrow_scope
)
handler = eval(
"lambda args, **kwargs: 'narrow'",
{"__name__": "vendor.plugin.handlers"},
)
token = set_hermes_home_override(narrow_scope)
try:
registry.register(
name="specific_entrypoint_tool",
toolset="specific_entrypoint",
schema={
"name": "specific_entrypoint_tool",
"parameters": {"type": "object", "properties": {}},
},
handler=handler,
)
finally:
reset_hermes_home_override(token)
assert registry.snapshot_registration(
"specific_entrypoint_tool", scope=narrow_scope
) is not None
assert registry.snapshot_registration(
"specific_entrypoint_tool", scope=broad_scope
) is None
def test_targeted_unload_does_not_resurrect_an_older_override():
"""Removing A under B tombstones A so B cannot restore it later."""
from gateway.platform_registry import platform_registry
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
name = "ledger_out_of_order_platform"
scope = platform_registry.current_scope_key()
previous = platform_registry.snapshot_registration(name, scope=scope)
manager_a = PluginManager(scope_key=scope)
manager_b = PluginManager(scope_key=scope)
context_a = PluginContext(
PluginManifest(name="ledger_old", key="ledger_old"), manager_a
)
context_b = PluginContext(
PluginManifest(name="ledger_new", key="ledger_new"), manager_b
)
try:
context_a.register_platform(
name=name,
label="old",
adapter_factory=lambda config: "old",
check_fn=lambda: True,
)
old_entry = platform_registry.get(name)
context_b.register_platform(
name=name,
label="new",
adapter_factory=lambda config: "new",
check_fn=lambda: True,
)
new_entry = platform_registry.get(name)
assert old_entry is not None and new_entry is not None
assert manager_a.unload("ledger_old") is True
assert platform_registry.get(name) is new_entry
assert name not in manager_a._plugin_platform_names
assert manager_b.unload("ledger_new") is True
assert platform_registry.get(name) is not old_entry
assert platform_registry.snapshot_registration(name, scope=scope) == previous
finally:
current = platform_registry.snapshot_registration(name, scope=scope)
platform_registry.restore_registration(
name, current, previous, scope=scope
)
def test_manager_local_override_does_not_resurrect_after_targeted_unload():
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
manager = PluginManager()
context_a = PluginContext(PluginManifest(name="local_a", key="local_a"), manager)
context_b = PluginContext(PluginManifest(name="local_b", key="local_b"), manager)
context_a.register_cli_command("shared-local", "A", lambda parser: None)
context_b.register_cli_command("shared-local", "B", lambda parser: None)
assert manager.unload("local_a") is True
assert manager._cli_commands["shared-local"]["plugin"] == "local_b"
assert manager.unload("local_b") is True
assert "shared-local" not in manager._cli_commands
def test_provider_overlay_switches_profiles_and_reveals_fresh_global_fallback(
tmp_path,
monkeypatch,
):
"""Provider consumers see A→B→A, and unload never pins a stale base."""
from agent.image_gen_provider import ImageGenProvider
import agent.image_gen_registry as image_registry
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
class Provider(ImageGenProvider):
def __init__(self, marker):
self.marker = marker
@property
def name(self):
return "ledger_profile_provider"
def generate(self, prompt, aspect_ratio="landscape", **kwargs):
return {"marker": self.marker}
name = "ledger_profile_provider"
baseline = image_registry.snapshot_registration(name)
global_a = Provider("global-a")
global_b = Provider("global-b")
provider_a = Provider("profile-a")
provider_b = Provider("profile-b")
home_a = str((tmp_path / "provider-a").resolve())
home_b = str((tmp_path / "provider-b").resolve())
manager_a = PluginManager(scope_key=home_a)
manager_b = PluginManager(scope_key=home_b)
context_a = PluginContext(PluginManifest(name="provider_a", key="provider_a"), manager_a)
context_b = PluginContext(PluginManifest(name="provider_b", key="provider_b"), manager_b)
monkeypatch.setattr(
"hermes_cli.config.load_config_readonly",
lambda: {"image_gen": {"provider": name}},
)
def active_for(home):
token = set_hermes_home_override(home)
try:
return image_registry.get_active_provider()
finally:
reset_hermes_home_override(token)
image_registry.register_provider(global_a)
try:
assert context_a.register_image_gen_provider(provider_a) is not None
assert context_b.register_image_gen_provider(provider_b) is not None
assert active_for(home_a) is provider_a
assert active_for(home_b) is provider_b
assert active_for(home_a) is provider_a
assert manager_a.unload("provider_a") is True
assert active_for(home_a) is global_a
image_registry.register_provider(global_b)
assert active_for(home_a) is global_b
assert active_for(home_b) is provider_b
assert manager_b.unload("provider_b") is True
assert active_for(home_b) is global_b
finally:
manager_a.unload("provider_a")
manager_b.unload("provider_b")
current = image_registry.snapshot_registration(name)
if current is not None:
image_registry.restore_registration(name, current, baseline)
def test_reused_provider_singleton_keeps_registration_generations_distinct():
"""Reusing one object after unload must not revive its retired generation."""
from agent.image_gen_provider import ImageGenProvider
from agent.image_gen_registry import (
get_provider,
restore_registration,
snapshot_registration,
)
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
class ProbeProvider(ImageGenProvider):
def __init__(self, marker):
self.marker = marker
@property
def name(self):
return "ledger_generation_provider"
def generate(self, prompt, aspect_ratio="landscape", **kwargs):
return {"marker": self.marker}
managers = [PluginManager() for _ in range(4)]
scope = managers[0].scope_key
baseline = get_provider("ledger_generation_provider", scope=scope)
baseline_local = snapshot_registration("ledger_generation_provider", scope=scope)
provider_a = ProbeProvider("a")
provider_b = ProbeProvider("b")
provider_c = ProbeProvider("c")
contexts = [
PluginContext(
PluginManifest(name=f"provider_{index}", key=f"provider_{index}"),
manager,
)
for index, manager in enumerate(managers)
]
try:
contexts[0].register_image_gen_provider(provider_a)
contexts[1].register_image_gen_provider(provider_b)
managers[0].unload("provider_0")
assert get_provider(provider_a.name, scope=scope) is provider_b
# A fresh ownership generation deliberately reuses the same singleton.
contexts[2].register_image_gen_provider(provider_a)
contexts[3].register_image_gen_provider(provider_c)
managers[3].unload("provider_3")
assert get_provider(provider_a.name, scope=scope) is provider_a
managers[2].unload("provider_2")
assert get_provider(provider_a.name, scope=scope) is provider_b
managers[1].unload("provider_1")
assert get_provider(provider_a.name, scope=scope) is baseline
finally:
current = snapshot_registration("ledger_generation_provider", scope=scope)
if current is not baseline_local and current is not None:
restore_registration(
"ledger_generation_provider", current, baseline_local, scope=scope
)
def test_same_provider_singleton_can_have_two_live_owners():
"""Retiring an older identical lease must not remove the newer owner."""
from agent.image_gen_provider import ImageGenProvider
from agent.image_gen_registry import (
get_provider,
restore_registration,
snapshot_registration,
)
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
class SharedProvider(ImageGenProvider):
@property
def name(self):
return "ledger_shared_singleton"
def generate(self, prompt, aspect_ratio="landscape", **kwargs):
return {"prompt": prompt}
provider = SharedProvider()
manager_a = PluginManager()
manager_b = PluginManager()
scope = manager_a.scope_key
baseline = get_provider("ledger_shared_singleton", scope=scope)
baseline_local = snapshot_registration("ledger_shared_singleton", scope=scope)
context_a = PluginContext(PluginManifest(name="shared_a", key="shared_a"), manager_a)
context_b = PluginContext(PluginManifest(name="shared_b", key="shared_b"), manager_b)
try:
assert context_a.register_image_gen_provider(provider) is not None
assert context_b.register_image_gen_provider(provider) is not None
manager_a.unload("shared_a")
assert get_provider(provider.name, scope=scope) is provider
manager_b.unload("shared_b")
assert get_provider(provider.name, scope=scope) is baseline
finally:
current = snapshot_registration(provider.name, scope=scope)
if current is not baseline_local and current is not None:
restore_registration(
provider.name, current, baseline_local, scope=scope
)
def test_provider_cleanup_uses_the_captured_normalized_name():
"""Whitespace and later name mutation cannot orphan a provider entry."""
from agent.image_gen_provider import ImageGenProvider
from agent.image_gen_registry import (
get_provider,
restore_registration,
snapshot_registration,
)
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
class MutableProvider(ImageGenProvider):
def __init__(self):
self._name = " ledger_mutable_provider "
@property
def name(self):
return self._name
def generate(self, prompt, aspect_ratio="landscape", **kwargs):
return {"prompt": prompt}
key = "ledger_mutable_provider"
provider = MutableProvider()
manager = PluginManager()
scope = manager.scope_key
baseline = get_provider(key, scope=scope)
baseline_local = snapshot_registration(key, scope=scope)
context = PluginContext(
PluginManifest(name="mutable_provider", key="mutable_provider"), manager
)
try:
assert context.register_image_gen_provider(provider) is not None
assert get_provider(key, scope=scope) is provider
provider._name = "renamed_after_registration"
manager.unload("mutable_provider")
assert get_provider(key, scope=scope) is baseline
finally:
current = snapshot_registration(key, scope=scope)
if current is not baseline_local and current is not None:
restore_registration(key, current, baseline_local, scope=scope)
def test_registration_transaction_excludes_concurrent_disposal(monkeypatch):
"""A lease cannot be retired between another generation's write/acquire."""
from agent.image_gen_provider import ImageGenProvider
import agent.image_gen_registry as image_registry
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
class Provider(ImageGenProvider):
def __init__(self, marker):
self.marker = marker
@property
def name(self):
return "ledger_transaction_provider"
def generate(self, prompt, aspect_ratio="landscape", **kwargs):
return {"marker": self.marker}
provider_a = Provider("a")
provider_b = Provider("b")
manager_a = PluginManager()
manager_b = PluginManager()
scope = manager_a.scope_key
baseline = image_registry.get_provider("ledger_transaction_provider", scope=scope)
baseline_local = image_registry.snapshot_registration(
"ledger_transaction_provider", scope=scope
)
context_a = PluginContext(PluginManifest(name="tx_a", key="tx_a"), manager_a)
context_b = PluginContext(PluginManifest(name="tx_b", key="tx_b"), manager_b)
context_a.register_image_gen_provider(provider_a)
original_register = image_registry.register_provider
wrote = Event()
release = Event()
def paused_register(provider, *, scope=None):
original_register(provider, scope=scope)
if provider is provider_b:
wrote.set()
assert release.wait(timeout=2)
monkeypatch.setattr(image_registry, "register_provider", paused_register)
try:
with ThreadPoolExecutor(max_workers=2) as pool:
registration = pool.submit(context_b.register_image_gen_provider, provider_b)
assert wrote.wait(timeout=1)
disposal = pool.submit(manager_a.unload, "tx_a")
try:
disposal.result(timeout=0.05)
except TimeoutError:
pass
else:
raise AssertionError("dispose interleaved with registration transaction")
release.set()
assert registration.result(timeout=1) is not None
assert disposal.result(timeout=1) is True
assert image_registry.get_provider(provider_a.name, scope=scope) is provider_b
manager_b.unload("tx_b")
assert image_registry.get_provider(provider_a.name, scope=scope) is baseline
finally:
release.set()
current = image_registry.snapshot_registration(
"ledger_transaction_provider", scope=scope
)
if current is not baseline_local and current is not None:
image_registry.restore_registration(
"ledger_transaction_provider",
current,
baseline_local,
scope=scope,
)
def test_deferred_platform_resolution_is_atomic_across_threads():
"""Concurrent first lookups both observe the materialized adapter."""
from gateway.platform_registry import PlatformEntry, PlatformRegistry
registry = PlatformRegistry()
entry = PlatformEntry(
name="threaded_platform",
label="Threaded",
adapter_factory=lambda config: object(),
check_fn=lambda: True,
source="plugin",
)
loads = []
started = Event()
release = Event()
def load():
loads.append(1)
started.set()
assert release.wait(timeout=2)
registry.register(entry)
registry.register_deferred("threaded_platform", load)
with ThreadPoolExecutor(max_workers=3) as pool:
first = pool.submit(registry.get, "threaded_platform")
assert started.wait(timeout=1)
assert registry.is_registered("threaded_platform")
second = pool.submit(registry.get, "threaded_platform")
try:
second.result(timeout=0.05)
except TimeoutError:
pass
else:
raise AssertionError("concurrent lookup did not wait for materialization")
enumeration = pool.submit(registry.all_entries)
try:
enumeration.result(timeout=0.05)
except TimeoutError:
pass
else:
raise AssertionError("enumeration omitted an in-flight platform")
release.set()
results = [first.result(timeout=1), second.result(timeout=1)]
assert enumeration.result(timeout=1) == [entry]
assert results == [entry, entry]
assert loads == [1]
def test_deferred_platform_recursive_lookup_does_not_deadlock():
"""A loader that asks for its own entry fails fast until registration."""
from gateway.platform_registry import PlatformEntry, PlatformRegistry
registry = PlatformRegistry()
nested_results = []
entry = PlatformEntry(
name="recursive_platform",
label="Recursive",
adapter_factory=lambda config: object(),
check_fn=lambda: True,
source="plugin",
)
def load():
nested_results.append(registry.get("recursive_platform"))
registry.register(entry)
registry.register_deferred("recursive_platform", load)
assert registry.get("recursive_platform") is entry
assert nested_results == [None]
def test_resolved_deferred_platform_restores_its_displaced_loader():
"""Deferred-to-concrete loading remains one replacement chain."""
from gateway.platform_registry import platform_registry
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
name = "ledger_transfer"
scope = platform_registry.current_scope_key()
previous = platform_registry.snapshot_registration(name, scope=scope)
old_loader = lambda: None
platform_registry.register_deferred(name, old_loader, scope=scope)
displaced = platform_registry.snapshot_registration(name, scope=scope)
manager = PluginManager(scope_key=scope)
manifest = PluginManifest(
name=f"{name}-platform",
key=f"{name}-platform",
source="bundled",
path="unused",
)
def load_scoped(self, loaded_manifest):
PluginContext(loaded_manifest, self).register_platform(
name=name,
label="Transferred",
adapter_factory=lambda config: object(),
check_fn=lambda: True,
)
manager._load_plugin_scoped = MethodType(load_scoped, manager)
try:
manager._register_deferred_platform(manifest)
entry = platform_registry.get(name)
assert entry is not None and entry.label == "Transferred"
manager.unload(manifest)
assert platform_registry.snapshot_registration(name, scope=scope) == displaced
finally:
current = platform_registry.snapshot_registration(name, scope=scope)
platform_registry.restore_registration(
name, current, previous, scope=scope
)
def test_unload_cancels_a_deferred_platform_before_module_load():
"""Losing the in-flight race cannot publish registrations after unload."""
from gateway.platform_registry import platform_registry
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
name = "ledger_cancel"
scope = platform_registry.current_scope_key()
previous = platform_registry.snapshot_registration(name, scope=scope)
def old_loader():
from gateway.platform_registry import PlatformEntry
platform_registry.register(
PlatformEntry(
name=name,
label="Restored predecessor",
adapter_factory=lambda config: object(),
check_fn=lambda: True,
source="plugin",
),
scope=scope,
)
platform_registry.register_deferred(name, old_loader, scope=scope)
displaced = platform_registry.snapshot_registration(name, scope=scope)
manager = PluginManager(scope_key=scope)
manifest = PluginManifest(
name=f"{name}-platform",
key=f"{name}-platform",
source="bundled",
path="unused",
)
def load_scoped(self, loaded_manifest):
PluginContext(loaded_manifest, self).register_platform(
name=name,
label="Should not publish",
adapter_factory=lambda config: object(),
check_fn=lambda: True,
)
manager._load_plugin_scoped = MethodType(load_scoped, manager)
manager._register_deferred_platform(manifest)
try:
with ThreadPoolExecutor(max_workers=1) as pool:
with manager._discovery_lock:
lookup = pool.submit(platform_registry.get, name)
deadline = monotonic() + 1
while (scope, name) not in platform_registry._inflight:
if monotonic() >= deadline:
raise AssertionError("deferred loader never became in-flight")
sleep(0.001)
assert manager.unload(manifest) is True
restored_entry = lookup.result(timeout=1)
assert restored_entry is not None
assert restored_entry.label == "Restored predecessor"
assert platform_registry.snapshot_registration(name, scope=scope)[0] is restored_entry
assert name not in manager._plugin_platform_names
finally:
current = platform_registry.snapshot_registration(name, scope=scope)
platform_registry.restore_registration(
name, current, previous, scope=scope
)
def test_direct_plugin_platform_registration_infers_immutable_scope(tmp_path):
"""The documented direct registry API cannot leak into another profile."""
from gateway.platform_registry import PlatformEntry, platform_registry
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
from tools.registry import registry as tool_registry
home_a = str((tmp_path / "direct-a").resolve())
home_b = tmp_path / "direct-b"
module_name = "company.hermes.direct_platform_probe"
policy = tool_registry.register_plugin_override_policy(
module_name, False, scope=home_a
)
factory = eval(
"lambda config: 'direct-a'",
{"__name__": f"{module_name}.handlers"},
)
name = "ledger_direct_platform"
previous_a = platform_registry.snapshot_registration(name, scope=home_a)
previous_global = platform_registry.snapshot_registration(name)
token = set_hermes_home_override(home_b)
try:
platform_registry.register(
PlatformEntry(
name=name,
label="Direct A",
adapter_factory=factory,
check_fn=lambda: True,
source="plugin",
)
)
assert platform_registry.get(name) is None
finally:
reset_hermes_home_override(token)
token = set_hermes_home_override(home_a)
try:
assert platform_registry.get(name).label == "Direct A"
finally:
reset_hermes_home_override(token)
current_a = platform_registry.snapshot_registration(name, scope=home_a)
platform_registry.restore_registration(
name, current_a, previous_a, scope=home_a
)
current_global = platform_registry.snapshot_registration(name)
if current_global != previous_global:
platform_registry.restore_registration(
name, current_global, previous_global
)
tool_registry.restore_plugin_override_policy(
module_name, policy, None, scope=home_a
)
def test_same_name_tool_and_platform_are_isolated_by_hermes_home(
tmp_path,
monkeypatch,
):
"""Real A→B→A profile switching keeps dispatch and adapters isolated."""
import hermes_cli.plugins as plugins_mod
from gateway.platform_registry import platform_registry
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
from hermes_cli.plugins import PluginManager
from tools.registry import registry
home_a = tmp_path / "profile-a"
home_b = tmp_path / "profile-b"
_write_profile_probe(home_a, "profile-a")
_write_profile_probe(home_b, "profile-b")
monkeypatch.setattr(
plugins_mod,
"get_bundled_plugins_dir",
lambda: tmp_path / "empty-bundled",
)
monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])
def load_profile(home: Path):
token = set_hermes_home_override(home)
try:
manager = plugins_mod.get_plugin_manager()
manager.discover_and_load()
tool_entry = registry.get_entry("shared_profile_tool")
platform_entry = platform_registry.get("shared_profile_platform")
return manager, tool_entry, platform_entry
finally:
reset_hermes_home_override(token)
manager_a, tool_a, platform_a = load_profile(home_a)
manager_b, tool_b, platform_b = load_profile(home_b)
assert manager_a is not manager_b
assert tool_a is not None and tool_b is not None and tool_a is not tool_b
assert platform_a is not None and platform_b is not None and platform_a is not platform_b
token_a = set_hermes_home_override(home_a)
try:
assert registry.dispatch("shared_profile_tool", {}) == "profile-a"
assert platform_registry.get("shared_profile_platform") is platform_a
finally:
reset_hermes_home_override(token_a)
token_b = set_hermes_home_override(home_b)
try:
assert registry.dispatch("shared_profile_tool", {}) == "profile-b"
assert platform_registry.get("shared_profile_platform") is platform_b
finally:
reset_hermes_home_override(token_b)
token_a = set_hermes_home_override(home_a)
try:
assert registry.dispatch("shared_profile_tool", {}) == "profile-a"
assert platform_registry.get("shared_profile_platform") is platform_a
finally:
reset_hermes_home_override(token_a)
def test_manager_discovery_uses_its_home_not_the_ambient_profile(
tmp_path,
monkeypatch,
):
"""A retained manager cannot scan another concurrently active profile."""
import hermes_cli.plugins as plugins_mod
from gateway.platform_registry import platform_registry
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
from hermes_cli.plugins import PluginManager
from tools.registry import registry
home_a = tmp_path / "retained-a"
home_b = tmp_path / "ambient-b"
_write_profile_probe(home_a, "retained-a")
_write_profile_probe(home_b, "ambient-b")
monkeypatch.setattr(
plugins_mod,
"get_bundled_plugins_dir",
lambda: tmp_path / "empty-bundled",
)
monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])
manager_a = PluginManager(scope_key=str(home_a.resolve()))
ambient = set_hermes_home_override(home_b)
try:
manager_a.discover_and_load()
finally:
reset_hermes_home_override(ambient)
tool_a = registry.get_entry("shared_profile_tool", scope=manager_a.scope_key)
platform_a = platform_registry.snapshot_registration(
"shared_profile_platform", scope=manager_a.scope_key
)[0]
assert tool_a is not None and tool_a.handler({}) == "retained-a"
assert platform_a is not None and platform_a.label == "retained-a"
assert registry.snapshot_registration(
"shared_profile_tool", scope=str(home_b.resolve())
) is None
assert platform_registry.snapshot_registration(
"shared_profile_platform", scope=str(home_b.resolve())
) == (None, None)
def test_same_slug_profiles_allocate_distinct_modules_concurrently(
tmp_path,
monkeypatch,
):
"""Policy binding and import use one atomic profile-specific namespace."""
import hermes_cli.plugins as plugins_mod
from hermes_cli.plugins import PluginManager
home_a = tmp_path / "concurrent-a"
home_b = tmp_path / "concurrent-b"
_write_profile_probe(home_a, "concurrent-a")
_write_profile_probe(home_b, "concurrent-b")
monkeypatch.setattr(
plugins_mod,
"get_bundled_plugins_dir",
lambda: tmp_path / "empty-bundled",
)
monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])
managers = [
PluginManager(scope_key=str(home_a.resolve())),
PluginManager(scope_key=str(home_b.resolve())),
]
with ThreadPoolExecutor(max_workers=2) as pool:
list(pool.map(lambda manager: manager.discover_and_load(), managers))
modules = [manager._plugins["profile_probe"].module.__name__ for manager in managers]
assert modules[0] != modules[1]
def test_spawned_supervised_task_is_cancelled_on_unload():
"""A plugin-spawned background task is tracked and cancelled on unload."""
import asyncio
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
manager = PluginManager()
manifest = PluginManifest(
name="task_probe", version="0.1", description="", source="user",
)
ctx = PluginContext(manifest, manager)
cancelled = []
async def scenario():
async def forever():
try:
await asyncio.sleep(3600)
except asyncio.CancelledError:
cancelled.append(True)
raise
task = ctx.spawn_task(forever(), name="probe-task")
await asyncio.sleep(0)
kinds = {
registration.kind
for registration in manager._ownership_ledger["task_probe"]
}
assert "background_task" in kinds
assert manager.unload("task_probe") is True
with __import__("pytest").raises(asyncio.CancelledError):
await task
# Done-callback disposal removes the handle from the ledger.
await asyncio.sleep(0)
assert "task_probe" not in manager._ownership_ledger
asyncio.run(scenario())
assert cancelled == [True]
def test_on_unload_exception_does_not_block_other_teardown():
"""A raising on_unload callback is isolated; later cleanup still runs."""
from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
manager = PluginManager()
manifest = PluginManifest(
name="boom_probe", version="0.1", description="", source="user",
)
ctx = PluginContext(manifest, manager)
order = []
ctx.on_unload(lambda: order.append("first"))
def _boom():
order.append("boom")
raise RuntimeError("cleanup failed")
ctx.on_unload(_boom)
ctx.on_unload(lambda: order.append("last"))
assert manager.unload("boom_probe") is True
# Reverse acquisition order, exception isolated.
assert order == ["last", "boom", "first"]
assert "boom_probe" not in manager._ownership_ledger