From aeba7c6f06c2a6ac254895d5d60556097048e0b5 Mon Sep 17 00:00:00 2001 From: Bryan Bednarski Date: Wed, 19 Aug 2026 17:59:09 -0700 Subject: [PATCH 1/2] fix(cli): validate static components before dynamic plugins Signed-off-by: Bryan Bednarski --- crates/cli/src/server/mod.rs | 12 ++++++------ crates/cli/tests/coverage/shared/server_tests.rs | 10 ++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/crates/cli/src/server/mod.rs b/crates/cli/src/server/mod.rs index 5a9695114..d1b146e9c 100644 --- a/crates/cli/src/server/mod.rs +++ b/crates/cli/src/server/mod.rs @@ -971,6 +971,12 @@ impl PluginActivation { .map_err(|error| CliError::Config(format!("invalid plugin config: {error}")))?, None => PluginConfig::default(), }; + if let Some(error) = register_and_validate_plugin_components(&plugin_config) + .into_iter() + .next() + { + return Err(CliError::Config(error.to_string())); + } plugin_config .components .extend(dynamic_plugins.iter().map(|plugin| PluginComponentSpec { @@ -978,12 +984,6 @@ impl PluginActivation { enabled: true, config: plugin.config.clone(), })); - if let Some(error) = register_and_validate_plugin_components(&plugin_config) - .into_iter() - .next() - { - return Err(CliError::Config(error.to_string())); - } for plugin in &dynamic_plugins { if let Some(snapshot) = plugin.activation_snapshot.as_ref() { snapshot.verify_current()?; diff --git a/crates/cli/tests/coverage/shared/server_tests.rs b/crates/cli/tests/coverage/shared/server_tests.rs index d5ed1e88e..dae191014 100644 --- a/crates/cli/tests/coverage/shared/server_tests.rs +++ b/crates/cli/tests/coverage/shared/server_tests.rs @@ -1905,17 +1905,19 @@ async fn plugin_activation_covers_empty_invalid_and_missing_manifest_paths() { .expect("invalid config should fail activation"); assert!(invalid.to_string().contains("invalid plugin config")); - let native = PluginActivation::initialize( + let dynamic_switchyard = PluginActivation::initialize( None, vec![dynamic_component_without_manifest( - "acme.native-missing", + "switchyard", DynamicPluginKind::RustDynamic, )], ) .await .err() - .expect("native plugin without a manifest should fail activation"); - assert!(native.to_string().contains("native dynamic plugin")); + .expect("dynamic Switchyard plugin without a manifest should reach dynamic activation"); + let dynamic_switchyard = dynamic_switchyard.to_string(); + assert!(dynamic_switchyard.contains("native dynamic plugin")); + assert!(!dynamic_switchyard.contains("removed in NeMo Relay 0.8")); let worker = PluginActivation::initialize( None, From 1c4abb3ed55076a2e5b78f7eefa8480caf082afd Mon Sep 17 00:00:00 2001 From: Bryan Bednarski Date: Wed, 19 Aug 2026 18:15:13 -0700 Subject: [PATCH 2/2] test(cli): cover static Switchyard migration validation Signed-off-by: Bryan Bednarski --- .../cli/tests/coverage/shared/server_tests.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/cli/tests/coverage/shared/server_tests.rs b/crates/cli/tests/coverage/shared/server_tests.rs index dae191014..f7593509a 100644 --- a/crates/cli/tests/coverage/shared/server_tests.rs +++ b/crates/cli/tests/coverage/shared/server_tests.rs @@ -1837,6 +1837,28 @@ async fn static_only_cli_configuration_keeps_the_legacy_lifecycle() { let _ = deregister_plugin(GENERIC_TEST_PLUGIN_KIND); } +#[test] +fn register_and_validate_plugin_components_rejects_legacy_switchyard_components() { + for enabled in [true, false] { + let config = PluginConfig { + components: vec![PluginComponentSpec { + kind: "switchyard".into(), + enabled, + config: Map::new(), + }], + ..PluginConfig::default() + }; + + let errors = register_and_validate_plugin_components(&config); + assert!( + errors + .iter() + .any(|error| matches!(error, PluginComponentSetupError::RemovedSwitchyard)), + "legacy Switchyard components must be rejected when enabled is {enabled}" + ); + } +} + #[test] fn plugin_component_setup_errors_render_every_diagnostic_variant() { let adaptive = PluginComponentSetupError::Adaptive("adaptive failure".into());