From db6f30fa33f1d8f251a9daccfbf75369fdb94814 Mon Sep 17 00:00:00 2001 From: bellman Date: Fri, 15 May 2026 09:55:43 +0900 Subject: [PATCH] verify plugin lifecycle JSON contract Lock the plugin inventory JSON contract so lifecycle state and lifecycle summary fields stay visible to orchestrators while allowing bundled plugins to coexist in isolated inventories. Constraint: G007-plugin-mcp Task 1 requires plugin/MCP lifecycle contract evidence without mutating .omx/ultragoal. Rejected: Assuming an empty plugin inventory in tests | bundled plugins are auto-synced and should not make lifecycle contract verification brittle. Confidence: high Scope-risk: narrow Directive: Keep plugin inventory JSON machine-readable for lifecycle_state, lifecycle, status, and load_failures; do not collapse it back to message-only JSON. Tested: cargo test -p plugins plugin_registry_report_collects_load_failures_without_dropping_valid_plugins -- --nocapture; cargo test -p commands renders_plugins_report -- --nocapture; cargo test -p rusty-claude-cli --test output_format_contract plugins_json_surfaces_lifecycle_contract_when_plugin_is_installed -- --nocapture; cargo test -p rusty-claude-cli --test output_format_contract inventory_commands_emit_structured_json_when_requested -- --nocapture; cargo check --workspace; cargo fmt --all -- --check Not-tested: cargo clippy -p rusty-claude-cli --test output_format_contract -- -D warnings is blocked by pre-existing runtime::policy_engine::LaneContext clippy::struct_excessive_bools. Co-authored-by: OmX --- .../tests/output_format_contract.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs index fb50d25b..a3780f5b 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -195,10 +195,17 @@ fn inventory_commands_emit_structured_json_when_requested() { "plugins target should be null when no plugin is targeted" ); assert_eq!(plugins["status"], "ok"); - assert!(plugins["plugins"] - .as_array() - .expect("plugins array") - .is_empty()); + let plugin_entries = plugins["plugins"].as_array().expect("plugins array"); + for plugin in plugin_entries { + assert!( + plugin["lifecycle_state"].is_string(), + "plugin entries should expose lifecycle_state" + ); + assert!( + plugin["lifecycle"]["configured"].is_boolean(), + "plugin entries should expose lifecycle contract summary" + ); + } assert!(plugins["load_failures"] .as_array() .expect("plugin load failures array")