fix(relay): clarify opt-in plugin layering
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
This commit is contained in:
@@ -1905,9 +1905,8 @@ def _configured_plugin_inputs(
|
||||
"Hermes [[dynamic_plugins]] records are unsupported; use Relay "
|
||||
"[[plugins.dynamic]] records"
|
||||
)
|
||||
plugins_section = config.get("plugins")
|
||||
dynamic_plugins: list[Any] = []
|
||||
if plugins_section:
|
||||
if "plugins" in config:
|
||||
dynamic_plugins = relay.plugin.load_dynamic_plugin_activation_specs(
|
||||
config_path
|
||||
)
|
||||
@@ -1923,6 +1922,7 @@ def _configured_plugin_inputs(
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _flush_relay_subscribers(relay: Any) -> None:
|
||||
"""Flush Relay without blocking an asyncio event-loop thread."""
|
||||
_resolve_plugin_awaitable(relay.subscribers.flush_async())
|
||||
|
||||
@@ -43,10 +43,15 @@ behalf.
|
||||
|
||||
Relay plugin activation is owned by the native runtime and remains explicitly
|
||||
opt-in. Set `HERMES_NEMO_RELAY_PLUGINS_TOML` to a selected `plugins.toml` to
|
||||
activate configured middleware, exporters, or dynamic plugins. When it is
|
||||
unset, Hermes does not invoke Relay's plugin initializer or trigger Relay
|
||||
plugin configuration discovery. Invalid explicit configuration is reported
|
||||
and Hermes continues without native plugin activation.
|
||||
activate configured middleware, exporters, or dynamic plugins. When the
|
||||
variable is unset, Hermes does not invoke Relay's plugin initializer, so Relay
|
||||
does not perform plugin configuration discovery or layering. When it is set
|
||||
and the selected file loads successfully, Relay performs its normal static
|
||||
`plugins.toml` discovery and layers the selected static configuration over the
|
||||
discovered configuration. Dynamic `[[plugins.dynamic]]` records are loaded
|
||||
from the selected file only. If the selected file cannot be loaded, Hermes
|
||||
reports the error and does not invoke Relay initialization or fall back to
|
||||
ambient discovery.
|
||||
|
||||
Hermes core owns one Relay host and one isolated Relay session scope per Hermes
|
||||
session. Core lifecycle producers use
|
||||
|
||||
@@ -215,6 +215,32 @@ def test_malformed_explicit_config_does_not_fall_back_to_discovery(
|
||||
host.shutdown()
|
||||
|
||||
|
||||
def test_present_plugins_section_is_validated_even_when_falsey(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
caplog,
|
||||
):
|
||||
config = tmp_path / "plugins.toml"
|
||||
config.write_text("plugins = []", encoding="utf-8")
|
||||
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
|
||||
relay = _FakeRelay()
|
||||
|
||||
def reject_invalid_plugins(_config_path):
|
||||
raise ValueError("'plugins' must be a table")
|
||||
|
||||
relay.plugin.load_dynamic_plugin_activation_specs = reject_invalid_plugins
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
|
||||
try:
|
||||
assert not host.managed_execution_enabled()
|
||||
assert relay.events == []
|
||||
assert "'plugins' must be a table" in caplog.text
|
||||
assert "continuing without Relay plugins" in caplog.text
|
||||
finally:
|
||||
host.shutdown()
|
||||
|
||||
|
||||
def test_two_profile_hosts_initialize_once_and_clear_after_final_shutdown(
|
||||
explicit_static_config,
|
||||
):
|
||||
@@ -767,6 +793,67 @@ mode = "overwrite"
|
||||
assert not (atof_dir / "events.jsonl").exists()
|
||||
|
||||
|
||||
def test_real_binding_layers_project_config_after_explicit_opt_in(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
):
|
||||
relay = pytest.importorskip("nemo_relay")
|
||||
if getattr(relay, "_native", None) is None:
|
||||
pytest.skip("NeMo Relay native binding is unavailable on this platform")
|
||||
|
||||
project_root = tmp_path / "project"
|
||||
working_directory = project_root / "workspace"
|
||||
config_directory = project_root / ".nemo-relay"
|
||||
selected_directory = tmp_path / "selected-config"
|
||||
atof_dir = tmp_path / "atof"
|
||||
working_directory.mkdir(parents=True)
|
||||
config_directory.mkdir()
|
||||
selected_directory.mkdir()
|
||||
(config_directory / "plugins.toml").write_text(
|
||||
f"""
|
||||
version = 1
|
||||
|
||||
[[components]]
|
||||
kind = "observability"
|
||||
enabled = true
|
||||
|
||||
[components.config]
|
||||
version = 3
|
||||
|
||||
[components.config.atof]
|
||||
enabled = true
|
||||
|
||||
[[components.config.atof.sinks]]
|
||||
type = "file"
|
||||
output_directory = "{atof_dir}"
|
||||
filename = "events.jsonl"
|
||||
mode = "overwrite"
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
selected_config = selected_directory / "plugins.toml"
|
||||
selected_config.write_text("version = 1", encoding="utf-8")
|
||||
xdg_config_home = tmp_path / "xdg"
|
||||
xdg_config_home.mkdir()
|
||||
monkeypatch.chdir(working_directory)
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_config_home))
|
||||
monkeypatch.setenv(
|
||||
relay_runtime.RELAY_PLUGINS_CONFIG_ENV,
|
||||
str(selected_config),
|
||||
)
|
||||
relay.plugin.clear()
|
||||
|
||||
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
|
||||
try:
|
||||
assert host.managed_execution_enabled()
|
||||
host.ensure_session({"session_id": "native-layered-plugins"})
|
||||
finally:
|
||||
host.shutdown()
|
||||
relay_runtime._reset_for_tests()
|
||||
|
||||
assert (atof_dir / "events.jsonl").is_file()
|
||||
|
||||
|
||||
def test_real_binding_loads_explicit_config_and_exports_native_activity(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
|
||||
Reference in New Issue
Block a user