plugin: filter static enabled surfaces
This commit is contained in:
parent
79ca0f7f81
commit
627c8f36ff
|
|
@ -303,87 +303,101 @@ pub fn inspect_resolved_plugin_static(record: &ResolvedPluginRecord) -> PluginSt
|
|||
.collect();
|
||||
|
||||
let duplicate_tool_names = duplicate_tool_names(record);
|
||||
let tools = record
|
||||
.manifest
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| {
|
||||
let permission = PluginPermission::tool(&tool.name);
|
||||
let requested = permission_requested(record, &permission);
|
||||
let granted = grant_allows(record, &permission);
|
||||
let mut diagnostics = validate_plugin_tool_definition(tool, &duplicate_tool_names);
|
||||
if let Err(error) = authorize_plugin_tool(record, tool) {
|
||||
diagnostics.push(error.bounded_message());
|
||||
}
|
||||
let diagnostic = join_tool_diagnostics(diagnostics);
|
||||
PluginToolEligibility {
|
||||
name: tool.name.clone(),
|
||||
permission: permission.label(),
|
||||
requested,
|
||||
granted,
|
||||
eligible: diagnostic.is_none(),
|
||||
external_write: tool.external_write,
|
||||
diagnostic,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let tools = if surface_enabled(record, PluginSurface::Tool) {
|
||||
record
|
||||
.manifest
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| {
|
||||
let permission = PluginPermission::tool(&tool.name);
|
||||
let requested = permission_requested(record, &permission);
|
||||
let granted = grant_allows(record, &permission);
|
||||
let mut diagnostics = validate_plugin_tool_definition(tool, &duplicate_tool_names);
|
||||
if let Err(error) = authorize_plugin_tool(record, tool) {
|
||||
diagnostics.push(error.bounded_message());
|
||||
}
|
||||
let diagnostic = join_tool_diagnostics(diagnostics);
|
||||
PluginToolEligibility {
|
||||
name: tool.name.clone(),
|
||||
permission: permission.label(),
|
||||
requested,
|
||||
granted,
|
||||
eligible: diagnostic.is_none(),
|
||||
external_write: tool.external_write,
|
||||
diagnostic,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let instance_world = record.manifest.runtime.as_ref().is_some_and(|runtime| {
|
||||
runtime.kind == PLUGIN_RUNTIME_COMPONENT_KIND
|
||||
&& runtime.world.as_deref() == Some(PLUGIN_COMPONENT_INSTANCE_WORLD)
|
||||
});
|
||||
let services = record
|
||||
.manifest
|
||||
.services
|
||||
.iter()
|
||||
.map(|service| {
|
||||
let permission = PluginPermission::service(&service.name);
|
||||
let requested = permission_requested(record, &permission);
|
||||
let granted = grant_allows(record, &permission);
|
||||
let mut diagnostics = Vec::new();
|
||||
if !instance_world {
|
||||
diagnostics.push("service requires instance-capable component world".to_string());
|
||||
}
|
||||
if let Err(error) = authorize_plugin_service(record, &service.name) {
|
||||
diagnostics.push(error.bounded_message());
|
||||
}
|
||||
let diagnostic = join_tool_diagnostics(diagnostics);
|
||||
PluginSurfaceEligibility {
|
||||
name: service.name.clone(),
|
||||
permission: permission.label(),
|
||||
requested,
|
||||
granted,
|
||||
eligible: diagnostic.is_none(),
|
||||
diagnostic,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let ingresses = record
|
||||
.manifest
|
||||
.ingresses
|
||||
.iter()
|
||||
.map(|ingress| {
|
||||
let permission = PluginPermission::ingress(&ingress.name);
|
||||
let requested = permission_requested(record, &permission);
|
||||
let granted = grant_allows(record, &permission);
|
||||
let mut diagnostics = Vec::new();
|
||||
if !instance_world {
|
||||
diagnostics.push("ingress requires instance-capable component world".to_string());
|
||||
}
|
||||
if let Err(error) = authorize_plugin_ingress(record, &ingress.name) {
|
||||
diagnostics.push(error.bounded_message());
|
||||
}
|
||||
let diagnostic = join_tool_diagnostics(diagnostics);
|
||||
PluginSurfaceEligibility {
|
||||
name: ingress.name.clone(),
|
||||
permission: permission.label(),
|
||||
requested,
|
||||
granted,
|
||||
eligible: diagnostic.is_none(),
|
||||
diagnostic,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let services = if surface_enabled(record, PluginSurface::Service) {
|
||||
record
|
||||
.manifest
|
||||
.services
|
||||
.iter()
|
||||
.map(|service| {
|
||||
let permission = PluginPermission::service(&service.name);
|
||||
let requested = permission_requested(record, &permission);
|
||||
let granted = grant_allows(record, &permission);
|
||||
let mut diagnostics = Vec::new();
|
||||
if !instance_world {
|
||||
diagnostics
|
||||
.push("service requires instance-capable component world".to_string());
|
||||
}
|
||||
if let Err(error) = authorize_plugin_service(record, &service.name) {
|
||||
diagnostics.push(error.bounded_message());
|
||||
}
|
||||
let diagnostic = join_tool_diagnostics(diagnostics);
|
||||
PluginSurfaceEligibility {
|
||||
name: service.name.clone(),
|
||||
permission: permission.label(),
|
||||
requested,
|
||||
granted,
|
||||
eligible: diagnostic.is_none(),
|
||||
diagnostic,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let ingresses = if surface_enabled(record, PluginSurface::Ingress) {
|
||||
record
|
||||
.manifest
|
||||
.ingresses
|
||||
.iter()
|
||||
.map(|ingress| {
|
||||
let permission = PluginPermission::ingress(&ingress.name);
|
||||
let requested = permission_requested(record, &permission);
|
||||
let granted = grant_allows(record, &permission);
|
||||
let mut diagnostics = Vec::new();
|
||||
if !instance_world {
|
||||
diagnostics
|
||||
.push("ingress requires instance-capable component world".to_string());
|
||||
}
|
||||
if let Err(error) = authorize_plugin_ingress(record, &ingress.name) {
|
||||
diagnostics.push(error.bounded_message());
|
||||
}
|
||||
let diagnostic = join_tool_diagnostics(diagnostics);
|
||||
PluginSurfaceEligibility {
|
||||
name: ingress.name.clone(),
|
||||
permission: permission.label(),
|
||||
requested,
|
||||
granted,
|
||||
eligible: diagnostic.is_none(),
|
||||
diagnostic,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
PluginStaticInspection {
|
||||
runtime,
|
||||
|
|
|
|||
|
|
@ -1090,6 +1090,18 @@ fn fill_resolved(builder: &mut ItemBuilder, resolved: &ResolvedPlugin) {
|
|||
.iter()
|
||||
.filter_map(|tool| tool.diagnostic.as_ref()),
|
||||
)
|
||||
.chain(
|
||||
static_runtime
|
||||
.services
|
||||
.iter()
|
||||
.filter_map(|service| service.diagnostic.as_ref()),
|
||||
)
|
||||
.chain(
|
||||
static_runtime
|
||||
.ingresses
|
||||
.iter()
|
||||
.filter_map(|ingress| ingress.diagnostic.as_ref()),
|
||||
)
|
||||
{
|
||||
builder.diagnostics.push(DiagnosticSummary {
|
||||
kind: "static_eligibility".to_string(),
|
||||
|
|
@ -1491,6 +1503,58 @@ mod tests {
|
|||
assert!(show.contains("configured_grants: surfaces.tool, tool.Echo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_only_enablement_ignores_unselected_tool_static_grants() {
|
||||
let dir = tempdir().unwrap();
|
||||
let workspace = dir.path();
|
||||
let digest = write_mixed_tool_service_package(workspace, "mixed");
|
||||
let mut config = PluginConfig::default();
|
||||
config.enabled.push(PluginEnablementConfig {
|
||||
id: "project:mixed".to_string(),
|
||||
digest: Some(digest.clone()),
|
||||
version: Some(PluginExactVersion("0.1.0".to_string())),
|
||||
surfaces: vec![PluginSurface::Service],
|
||||
grants: PluginGrantConfig {
|
||||
id: Some("project:mixed".to_string()),
|
||||
version: Some(PluginExactVersion("0.1.0".to_string())),
|
||||
digest: Some(digest),
|
||||
permissions: vec![
|
||||
PluginPermission::surface(PluginSurface::Service),
|
||||
PluginPermission::service("svc"),
|
||||
],
|
||||
https: Vec::new(),
|
||||
fs: Vec::new(),
|
||||
},
|
||||
config: None,
|
||||
});
|
||||
|
||||
let snapshot = inspect_snapshot(workspace, &config);
|
||||
let item = select_item(&snapshot, "project:mixed").unwrap();
|
||||
|
||||
assert_eq!(item.status, "active");
|
||||
assert!(item.static_eligible);
|
||||
assert_eq!(item.enabled_surfaces, vec!["service"]);
|
||||
assert!(
|
||||
item.tools.is_empty(),
|
||||
"unselected Tool must not be reported"
|
||||
);
|
||||
assert!(
|
||||
item.diagnostics
|
||||
.iter()
|
||||
.all(|diagnostic| !diagnostic.message.contains("tool.Echo")),
|
||||
"unselected Tool grant diagnostics must not affect service-only enablement: {:#?}",
|
||||
item.diagnostics
|
||||
);
|
||||
|
||||
let show_json = serde_json::to_value(item).unwrap();
|
||||
assert_eq!(show_json["status"], "active");
|
||||
assert_eq!(
|
||||
show_json["enabled_surfaces"],
|
||||
serde_json::json!(["service"])
|
||||
);
|
||||
assert_eq!(show_json["tools"], serde_json::json!([]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn human_list_uses_required_status_vocabulary() {
|
||||
let dir = tempdir().unwrap();
|
||||
|
|
@ -2098,6 +2162,61 @@ mod tests {
|
|||
assert!(error.len() < 160);
|
||||
}
|
||||
|
||||
fn write_mixed_tool_service_package(workspace: &Path, id: &str) -> String {
|
||||
let package_dir = workspace.join(".yoi/plugins");
|
||||
fs::create_dir_all(&package_dir).unwrap();
|
||||
let package = package_dir.join(format!("{id}.yoi-plugin"));
|
||||
let manifest = format!(
|
||||
r#"schema_version = 1
|
||||
id = "{id}"
|
||||
name = "{id}"
|
||||
version = "0.1.0"
|
||||
description = "mixed surface package"
|
||||
surfaces = ["tool", "service"]
|
||||
permissions = [
|
||||
{{ kind = "surface", surface = "tool" }},
|
||||
{{ kind = "tool", name = "Echo" }},
|
||||
{{ kind = "surface", surface = "service" }},
|
||||
{{ kind = "service", name = "svc" }},
|
||||
]
|
||||
|
||||
[runtime]
|
||||
kind = "wasm-component"
|
||||
world = "yoi:plugin/instance@1.0.0"
|
||||
component = "plugin.component.wasm"
|
||||
|
||||
[[tools]]
|
||||
name = "Echo"
|
||||
description = "unselected tool"
|
||||
input_schema = {{ type = "object" }}
|
||||
|
||||
[[services]]
|
||||
name = "svc"
|
||||
description = "selected service"
|
||||
lifecycle = "host-managed"
|
||||
"#,
|
||||
);
|
||||
write_stored_zip(
|
||||
&package,
|
||||
&[
|
||||
("plugin.toml", manifest.as_bytes()),
|
||||
("plugin.component.wasm", b"placeholder component bytes"),
|
||||
],
|
||||
);
|
||||
let discovery = discover_plugins(&PluginDiscoveryOptions {
|
||||
workspace_root: workspace.to_path_buf(),
|
||||
user_data_home: None,
|
||||
limits: PluginDiscoveryLimits::default(),
|
||||
});
|
||||
discovery
|
||||
.packages
|
||||
.iter()
|
||||
.find(|package| package.identity.local_id == id)
|
||||
.unwrap()
|
||||
.digest
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn inspect_snapshot(workspace: &Path, config: &PluginConfig) -> PluginInspectionSnapshot {
|
||||
let discovery = discover_plugins(&PluginDiscoveryOptions {
|
||||
workspace_root: workspace.to_path_buf(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user