plugin: enforce enabled lifecycle surfaces
This commit is contained in:
parent
870bcc76a5
commit
79ca0f7f81
|
|
@ -98,6 +98,11 @@ impl PluginToolFeature {
|
||||||
ingress_name: &str,
|
ingress_name: &str,
|
||||||
event: PluginIngressEvent,
|
event: PluginIngressEvent,
|
||||||
) -> Result<PluginIngressDispatchReport, PluginWasmError> {
|
) -> Result<PluginIngressDispatchReport, PluginWasmError> {
|
||||||
|
if !surface_enabled(&self.record, PluginSurface::Ingress) {
|
||||||
|
return Err(PluginWasmError::Module(
|
||||||
|
"plugin ingress surface is not enabled".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let handle = self
|
let handle = self
|
||||||
.registry
|
.registry
|
||||||
.handle(&self.record.identity.to_string())
|
.handle(&self.record.identity.to_string())
|
||||||
|
|
@ -119,6 +124,10 @@ impl PluginToolFeature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn surface_enabled(record: &ResolvedPluginRecord, surface: PluginSurface) -> bool {
|
||||||
|
record.enabled_surfaces.contains(&surface)
|
||||||
|
}
|
||||||
|
|
||||||
fn plugin_tool_origin(record: &ResolvedPluginRecord) -> ToolOrigin {
|
fn plugin_tool_origin(record: &ResolvedPluginRecord) -> ToolOrigin {
|
||||||
ToolOrigin {
|
ToolOrigin {
|
||||||
kind: "plugin".into(),
|
kind: "plugin".into(),
|
||||||
|
|
@ -461,6 +470,7 @@ impl FeatureModule for PluginToolFeature {
|
||||||
requires_services: Vec::new(),
|
requires_services: Vec::new(),
|
||||||
protocol_providers: Vec::new(),
|
protocol_providers: Vec::new(),
|
||||||
};
|
};
|
||||||
|
if surface_enabled(&self.record, PluginSurface::Service) {
|
||||||
for service in &self.record.manifest.services {
|
for service in &self.record.manifest.services {
|
||||||
descriptor.provides_services.push(ServiceDeclaration::new(
|
descriptor.provides_services.push(ServiceDeclaration::new(
|
||||||
plugin_service_id(&self.record, &service.name),
|
plugin_service_id(&self.record, &service.name),
|
||||||
|
|
@ -468,20 +478,26 @@ impl FeatureModule for PluginToolFeature {
|
||||||
service.description.clone(),
|
service.description.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if surface_enabled(&self.record, PluginSurface::Tool) {
|
||||||
for tool in &self.record.manifest.tools {
|
for tool in &self.record.manifest.tools {
|
||||||
descriptor = descriptor.with_tool(ToolDeclaration::new(
|
descriptor = descriptor.with_tool(ToolDeclaration::new(
|
||||||
tool.name.clone(),
|
tool.name.clone(),
|
||||||
tool.description.clone(),
|
tool.description.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
descriptor
|
descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install(&self, context: &mut FeatureInstallContext<'_>) -> Result<(), FeatureInstallError> {
|
fn install(&self, context: &mut FeatureInstallContext<'_>) -> Result<(), FeatureInstallError> {
|
||||||
|
if surface_enabled(&self.record, PluginSurface::Tool) {
|
||||||
validate_declared_tool_names(&self.record)?;
|
validate_declared_tool_names(&self.record)?;
|
||||||
|
}
|
||||||
let mut instance: Option<PluginInstanceHandle> = None;
|
let mut instance: Option<PluginInstanceHandle> = None;
|
||||||
let mut registered = 0usize;
|
let mut exposed = 0usize;
|
||||||
let mut denied = Vec::new();
|
let mut denied = Vec::new();
|
||||||
|
if surface_enabled(&self.record, PluginSurface::Service) {
|
||||||
for service in &self.record.manifest.services {
|
for service in &self.record.manifest.services {
|
||||||
validate_tool_name(&service.name).map_err(|reason| {
|
validate_tool_name(&service.name).map_err(|reason| {
|
||||||
FeatureInstallError::Install(format!(
|
FeatureInstallError::Install(format!(
|
||||||
|
|
@ -508,7 +524,10 @@ impl FeatureModule for PluginToolFeature {
|
||||||
self.record.manifest.version.clone(),
|
self.record.manifest.version.clone(),
|
||||||
service.description.clone(),
|
service.description.clone(),
|
||||||
))?;
|
))?;
|
||||||
|
exposed += 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if surface_enabled(&self.record, PluginSurface::Ingress) {
|
||||||
for ingress in &self.record.manifest.ingresses {
|
for ingress in &self.record.manifest.ingresses {
|
||||||
validate_tool_name(&ingress.name).map_err(|reason| {
|
validate_tool_name(&ingress.name).map_err(|reason| {
|
||||||
FeatureInstallError::Install(format!(
|
FeatureInstallError::Install(format!(
|
||||||
|
|
@ -525,10 +544,15 @@ impl FeatureModule for PluginToolFeature {
|
||||||
);
|
);
|
||||||
context.diagnostics().warning(message.clone());
|
context.diagnostics().warning(message.clone());
|
||||||
denied.push(message);
|
denied.push(message);
|
||||||
} else if instance.is_none() {
|
} else {
|
||||||
|
if instance.is_none() {
|
||||||
instance = Some(self.ensure_instance()?);
|
instance = Some(self.ensure_instance()?);
|
||||||
}
|
}
|
||||||
|
exposed += 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if surface_enabled(&self.record, PluginSurface::Tool) {
|
||||||
for tool in &self.record.manifest.tools {
|
for tool in &self.record.manifest.tools {
|
||||||
validate_tool_name(&tool.name).map_err(|reason| {
|
validate_tool_name(&tool.name).map_err(|reason| {
|
||||||
FeatureInstallError::Install(format!(
|
FeatureInstallError::Install(format!(
|
||||||
|
|
@ -570,9 +594,10 @@ impl FeatureModule for PluginToolFeature {
|
||||||
tool.input_schema.clone(),
|
tool.input_schema.clone(),
|
||||||
),
|
),
|
||||||
))?;
|
))?;
|
||||||
registered += 1;
|
exposed += 1;
|
||||||
}
|
}
|
||||||
if registered == 0 && !denied.is_empty() {
|
}
|
||||||
|
if exposed == 0 && !denied.is_empty() {
|
||||||
let summary = if denied.len() == 1 {
|
let summary = if denied.len() == 1 {
|
||||||
denied.remove(0)
|
denied.remove(0)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2137,6 +2162,11 @@ impl PluginInstance {
|
||||||
tool_name: &str,
|
tool_name: &str,
|
||||||
input: Vec<u8>,
|
input: Vec<u8>,
|
||||||
) -> Result<ToolOutput, PluginWasmError> {
|
) -> Result<ToolOutput, PluginWasmError> {
|
||||||
|
if !surface_enabled(&self.record, PluginSurface::Tool) {
|
||||||
|
return Err(PluginWasmError::Module(
|
||||||
|
"plugin tool surface is not enabled".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let tool = self
|
let tool = self
|
||||||
.record
|
.record
|
||||||
.manifest
|
.manifest
|
||||||
|
|
@ -2177,6 +2207,11 @@ impl PluginInstance {
|
||||||
ingress_name: &str,
|
ingress_name: &str,
|
||||||
event: PluginIngressEvent,
|
event: PluginIngressEvent,
|
||||||
) -> Result<PluginIngressDispatchReport, PluginWasmError> {
|
) -> Result<PluginIngressDispatchReport, PluginWasmError> {
|
||||||
|
if !surface_enabled(&self.record, PluginSurface::Ingress) {
|
||||||
|
return Err(PluginWasmError::Module(
|
||||||
|
"plugin ingress surface is not enabled".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
if serde_json::to_vec(&event)
|
if serde_json::to_vec(&event)
|
||||||
.map(|bytes| bytes.len())
|
.map(|bytes| bytes.len())
|
||||||
.unwrap_or(usize::MAX)
|
.unwrap_or(usize::MAX)
|
||||||
|
|
@ -3840,6 +3875,64 @@ mod tests {
|
||||||
.push(PluginPermission::ingress(name));
|
.push(PluginPermission::ingress(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_selected_ignores_unselected_tool_without_grants() {
|
||||||
|
let mut record = record(vec![tool("hidden_tool")]);
|
||||||
|
add_service(&mut record, "svc");
|
||||||
|
record.enabled_surfaces = vec![PluginSurface::Service];
|
||||||
|
record.manifest.permissions = vec![
|
||||||
|
PluginPermission::surface(PluginSurface::Service),
|
||||||
|
PluginPermission::service("svc"),
|
||||||
|
];
|
||||||
|
record.grants.permissions = record.manifest.permissions.clone();
|
||||||
|
let feature = PluginToolFeature::new(record);
|
||||||
|
assert!(feature.descriptor().tools.is_empty());
|
||||||
|
assert_eq!(feature.descriptor().provides_services.len(), 1);
|
||||||
|
let (report, pending) = install_feature(feature.clone());
|
||||||
|
assert!(
|
||||||
|
report.reports.iter().all(|report| report.installed),
|
||||||
|
"{report:#?}"
|
||||||
|
);
|
||||||
|
assert!(pending.is_empty(), "unselected Tool must not register");
|
||||||
|
assert_eq!(report.reports[0].provided_services.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
feature.instance_status().unwrap().lifecycle,
|
||||||
|
PluginInstanceLifecycleState::Ready
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tool_selected_ignores_unselected_service_ingress_even_with_grants() {
|
||||||
|
let mut record = record(vec![tool("visible_tool")]);
|
||||||
|
add_service(&mut record, "hidden_service");
|
||||||
|
add_ingress(&mut record, "hidden_ingress");
|
||||||
|
record.enabled_surfaces = vec![PluginSurface::Tool];
|
||||||
|
let feature = PluginToolFeature::new(record);
|
||||||
|
assert!(feature.descriptor().provides_services.is_empty());
|
||||||
|
assert_eq!(feature.descriptor().tools.len(), 1);
|
||||||
|
let (report, pending) = install_feature(feature.clone());
|
||||||
|
assert!(
|
||||||
|
report.reports.iter().all(|report| report.installed),
|
||||||
|
"{report:#?}"
|
||||||
|
);
|
||||||
|
assert_eq!(pending.len(), 1);
|
||||||
|
assert!(report.reports[0].provided_services.is_empty());
|
||||||
|
let dispatch = feature.dispatch_ingress(
|
||||||
|
"hidden_ingress",
|
||||||
|
PluginIngressEvent {
|
||||||
|
kind: "test".into(),
|
||||||
|
source: "unit".into(),
|
||||||
|
payload: serde_json::json!({}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
dispatch
|
||||||
|
.unwrap_err()
|
||||||
|
.bounded_message()
|
||||||
|
.contains("not enabled")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn service_only_install_retains_host_managed_instance() {
|
fn service_only_install_retains_host_managed_instance() {
|
||||||
let mut record = record(Vec::new());
|
let mut record = record(Vec::new());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user