plugin: reject configured invalid packages

This commit is contained in:
Keisuke Hirata 2026-06-19 22:51:02 +09:00
parent 982a1b75ed
commit a5f3b0b554
No known key found for this signature in database

View File

@ -716,6 +716,10 @@ impl ItemBuilder {
}); });
let has_diagnostic = let has_diagnostic =
!self.diagnostics.is_empty() || rejected_tool || static_runtime_rejected; !self.diagnostics.is_empty() || rejected_tool || static_runtime_rejected;
let has_non_missing_diagnostic = self
.diagnostics
.iter()
.any(|diagnostic| diagnostic.kind != "missing");
let status = if self.resolved { let status = if self.resolved {
if usable_tool && has_diagnostic { if usable_tool && has_diagnostic {
"partial" "partial"
@ -727,7 +731,11 @@ impl ItemBuilder {
} else if self.discovered && !self.configured { } else if self.discovered && !self.configured {
"disabled" "disabled"
} else if self.configured && !self.discovered { } else if self.configured && !self.discovered {
"missing" if has_non_missing_diagnostic {
"rejected"
} else {
"missing"
}
} else { } else {
"rejected" "rejected"
} }
@ -989,6 +997,80 @@ mod tests {
); );
} }
#[test]
fn configured_invalid_or_incompatible_package_is_rejected_not_missing() {
let dir = tempdir().unwrap();
let workspace = dir.path();
fs::create_dir_all(workspace.join(".yoi/plugins")).unwrap();
write_stored_zip(
&workspace.join(".yoi/plugins/invalid.yoi-plugin"),
&[("plugin.toml", b"not = [valid")],
);
let incompatible_manifest = plugin_manifest_with_schema("incompat", "Echo", 999);
write_stored_zip(
&workspace.join(".yoi/plugins/incompat.yoi-plugin"),
&[
("plugin.toml", incompatible_manifest.as_bytes()),
("plugin.wasm", b"not wasm"),
],
);
let mut config = PluginConfig::default();
config.enabled.push(enablement_without_digest(
"project:invalid",
"0.1.0",
&["Echo"],
));
config.enabled.push(enablement_without_digest(
"project:incompat",
"0.1.0",
&["Echo"],
));
let snapshot = inspect_snapshot(workspace, &config);
let invalid = select_item(&snapshot, "project:invalid").unwrap();
let incompatible = select_item(&snapshot, "project:incompat").unwrap();
assert_eq!(invalid.status, "rejected");
assert_eq!(incompatible.status, "rejected");
assert!(invalid.configured);
assert!(!invalid.discovered);
assert!(incompatible.configured);
assert!(!incompatible.discovered);
assert!(!invalid.diagnostics.is_empty());
assert!(
incompatible
.diagnostics
.iter()
.any(|diagnostic| diagnostic.kind == "api")
);
let list_json = serde_json::to_value(&snapshot).unwrap();
assert!(list_json["items"].as_array().unwrap().iter().any(|item| {
item["reference"] == "project:invalid"
&& item["status"] == "rejected"
&& item["package_path"]
.as_str()
.unwrap_or_default()
.ends_with(".yoi/plugins/invalid.yoi-plugin")
}));
let show_json = serde_json::to_value(incompatible).unwrap();
assert_eq!(show_json["status"], "rejected");
assert!(
show_json["diagnostics"][0]["message"]
.as_str()
.unwrap_or_default()
.contains("unsupported")
);
let list_output = render_list_snapshot_human(&snapshot).unwrap();
assert!(list_output.contains("project:invalid [rejected]"));
assert!(list_output.contains("project:incompat [rejected]"));
assert!(!list_output.contains("project:invalid [missing]"));
let show_output = render_item_human(invalid).unwrap();
assert!(show_output.contains("status: rejected"));
assert!(show_output.contains("diagnostics:"));
}
#[test] #[test]
fn invalid_tool_schema_and_name_are_rejected_in_json_and_human_output() { fn invalid_tool_schema_and_name_are_rejected_in_json_and_human_output() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
@ -1127,11 +1209,51 @@ mod tests {
} }
} }
fn enablement_without_digest(
id: &str,
version: &str,
tool_permissions: &[&str],
) -> PluginEnablementConfig {
let mut permissions = vec![PluginPermission::surface(PluginSurface::Tool)];
permissions.extend(
tool_permissions
.iter()
.map(|tool_name| PluginPermission::tool(*tool_name)),
);
PluginEnablementConfig {
id: id.to_string(),
digest: None,
version: Some(PluginExactVersion(version.to_string())),
surfaces: vec![PluginSurface::Tool],
grants: PluginGrantConfig {
id: Some(id.to_string()),
version: Some(PluginExactVersion(version.to_string())),
digest: None,
permissions,
},
config: None,
}
}
fn plugin_manifest( fn plugin_manifest(
id: &str, id: &str,
tool_name: &str, tool_name: &str,
schema_type: &str, schema_type: &str,
permission_tools: &[&str], permission_tools: &[&str],
) -> String {
plugin_manifest_with_schema_and_tool(id, tool_name, schema_type, permission_tools, 1)
}
fn plugin_manifest_with_schema(id: &str, tool_name: &str, schema_version: u32) -> String {
plugin_manifest_with_schema_and_tool(id, tool_name, "object", &[tool_name], schema_version)
}
fn plugin_manifest_with_schema_and_tool(
id: &str,
tool_name: &str,
schema_type: &str,
permission_tools: &[&str],
schema_version: u32,
) -> String { ) -> String {
let permissions = permission_tools let permissions = permission_tools
.iter() .iter()
@ -1140,7 +1262,7 @@ mod tests {
.join(", "); .join(", ");
format!( format!(
r#" r#"
schema_version = 1 schema_version = {schema_version}
id = "{id}" id = "{id}"
name = "{id}" name = "{id}"
version = "0.1.0" version = "0.1.0"