plugin: filter static enabled surfaces

This commit is contained in:
Keisuke Hirata 2026-06-21 00:12:55 +09:00
parent 79ca0f7f81
commit 627c8f36ff
No known key found for this signature in database
2 changed files with 209 additions and 76 deletions

View File

@ -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,

View File

@ -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(),