Skip to content

Commit 445d3d6

Browse files
committed
feat(observability): add dynamic export activation policies
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
1 parent e310ab4 commit 445d3d6

46 files changed

Lines changed: 1611 additions & 141 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/cli/src/server/mod.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ use axum::response::{IntoResponse, Response};
2020
use axum::routing::{get, post};
2121
use axum::{Json, Router};
2222
use nemo_relay::plugin::dynamic::{
23-
DynamicPluginKind, NativePluginActivation, NativePluginLoadSpec, WorkerPluginActivation,
24-
WorkerPluginLoadSpec, load_native_plugins, load_worker_plugins,
23+
DynamicPluginCapability, DynamicPluginKind, DynamicPluginManifest, NativePluginActivation,
24+
NativePluginLoadSpec, WorkerPluginActivation, WorkerPluginLoadSpec, load_native_plugins,
25+
load_worker_plugins,
2526
};
2627
use nemo_relay::plugin::{
2728
PluginComponentSpec, PluginConfig, clear_plugin_configuration, initialize_plugins_exact,
@@ -977,13 +978,6 @@ impl PluginActivation {
977978
{
978979
return Err(CliError::Config(error.to_string()));
979980
}
980-
plugin_config
981-
.components
982-
.extend(dynamic_plugins.iter().map(|plugin| PluginComponentSpec {
983-
kind: plugin.plugin_id.clone(),
984-
enabled: true,
985-
config: plugin.config.clone(),
986-
}));
987981
for plugin in &dynamic_plugins {
988982
if let Some(snapshot) = plugin.activation_snapshot.as_ref() {
989983
snapshot.verify_current()?;
@@ -1038,6 +1032,40 @@ impl PluginActivation {
10381032
})
10391033
})
10401034
.collect::<Result<Vec<_>, CliError>>()?;
1035+
let mut policy_components = Vec::new();
1036+
let mut regular_components = Vec::new();
1037+
for plugin in &dynamic_plugins {
1038+
let manifest_ref = plugin
1039+
.activation_snapshot
1040+
.as_ref()
1041+
.map(|snapshot| snapshot.activation_manifest_ref())
1042+
.or_else(|| plugin.manifest_ref.clone())
1043+
.ok_or_else(|| {
1044+
CliError::Config(format!(
1045+
"dynamic plugin '{}' has no manifest_ref in lifecycle state",
1046+
plugin.plugin_id
1047+
))
1048+
})?;
1049+
let (manifest, _) = DynamicPluginManifest::load_from_path(&manifest_ref)
1050+
.map_err(|error| CliError::Config(error.to_string()))?;
1051+
let component = PluginComponentSpec {
1052+
kind: plugin.plugin_id.clone(),
1053+
enabled: true,
1054+
config: plugin.config.clone(),
1055+
};
1056+
if manifest
1057+
.capabilities
1058+
.items
1059+
.contains(&DynamicPluginCapability::ExportActivationPolicy)
1060+
{
1061+
policy_components.push(component);
1062+
} else {
1063+
regular_components.push(component);
1064+
}
1065+
}
1066+
policy_components.append(&mut plugin_config.components);
1067+
policy_components.append(&mut regular_components);
1068+
plugin_config.components = policy_components;
10411069
let snapshots = dynamic_plugins
10421070
.iter()
10431071
.filter_map(|plugin| plugin.activation_snapshot.clone())
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Activation-time policy hooks for Relay-managed remote exporters.
5+
6+
use std::collections::HashMap;
7+
use std::future::Future;
8+
use std::pin::Pin;
9+
use std::sync::{Arc, LazyLock, RwLock};
10+
11+
use crate::error::{FlowError, Result};
12+
pub use nemo_relay_types::plugin::{
13+
ExportActivationDecision, ExportActivationRequest, ExportActivationTargetKind,
14+
};
15+
16+
/// Asynchronous callback registered by one export-activation policy provider.
17+
pub type ExportActivationPolicyFn = Arc<
18+
dyn Fn(
19+
ExportActivationRequest,
20+
) -> Pin<Box<dyn Future<Output = Result<ExportActivationDecision>> + Send>>
21+
+ Send
22+
+ Sync,
23+
>;
24+
25+
static EXPORT_ACTIVATION_POLICIES: LazyLock<RwLock<HashMap<String, ExportActivationPolicyFn>>> =
26+
LazyLock::new(|| RwLock::new(HashMap::new()));
27+
28+
pub(crate) fn register_export_activation_policy(
29+
provider: &str,
30+
callback: ExportActivationPolicyFn,
31+
) -> Result<()> {
32+
let mut policies = EXPORT_ACTIVATION_POLICIES.write().map_err(|error| {
33+
FlowError::Internal(format!(
34+
"export activation policy registry lock poisoned: {error}"
35+
))
36+
})?;
37+
if policies.contains_key(provider) {
38+
return Err(FlowError::AlreadyExists(provider.to_string()));
39+
}
40+
policies.insert(provider.to_string(), callback);
41+
Ok(())
42+
}
43+
44+
pub(crate) fn deregister_export_activation_policy(provider: &str) -> Result<bool> {
45+
EXPORT_ACTIVATION_POLICIES
46+
.write()
47+
.map(|mut policies| policies.remove(provider).is_some())
48+
.map_err(|error| {
49+
FlowError::Internal(format!(
50+
"export activation policy registry lock poisoned: {error}"
51+
))
52+
})
53+
}
54+
55+
pub(crate) async fn evaluate_export_activation_policy(
56+
provider: &str,
57+
request: ExportActivationRequest,
58+
) -> Result<ExportActivationDecision> {
59+
let callback = EXPORT_ACTIVATION_POLICIES
60+
.read()
61+
.map_err(|error| {
62+
FlowError::Internal(format!(
63+
"export activation policy registry lock poisoned: {error}"
64+
))
65+
})?
66+
.get(provider)
67+
.cloned()
68+
.ok_or_else(|| FlowError::NotFound(provider.to_string()))?;
69+
callback(request).await
70+
}

crates/core/src/api/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
66
/// Lifecycle event types and builder-backed event constructors.
77
pub mod event;
8+
/// Activation-time policy types for Relay-managed remote exporters.
9+
pub mod export_activation;
810
/// LLM lifecycle helpers and managed execution entry points.
911
pub mod llm;
1012
/// Plugin-neutral evidence recording for managed LLM calls.

0 commit comments

Comments
 (0)