use serde_json::{json, Value}; pub fn verification_check_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["label", "commands"], "properties": { "label": { "type": "string" }, "commands": { "type": "array", "items": { "type": "string" } } } }) } pub fn planning_persona_schema() -> Value { json!({ "type": "string", "enum": ["product-owner", "senior-engineer", "senior-maintainer"] }) } pub fn planning_quality_decision_schema() -> Value { json!({ "type": "string", "enum": ["accept", "downgraded", "blocked"] }) } pub fn planning_quality_gate_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["quality_gate_version", "decision_code", "rationale_codes", "rationale"], "properties": { "quality_gate_version": { "type": "integer" }, "decision_code": planning_quality_decision_schema(), "rationale_codes": { "type": "array", "items": { "type": "string" } }, "rationale": { "type": "array", "items": { "type": "string" } } } }) } pub fn planning_conflict_strategy_schema() -> Value { json!({ "type": "string", "enum": ["latest-stage-wins", "append-unique", "replace"] }) } pub fn planning_conflict_rule_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["field", "strategy"], "properties": { "field": { "type": "string" }, "strategy": planning_conflict_strategy_schema() } }) } pub fn planning_persona_evidence_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["facts", "assumptions", "questions"], "properties": { "facts": { "type": "array", "items": { "type": "string" } }, "assumptions": { "type": "array", "items": { "type": "string" } }, "questions": { "type": "array", "items": { "type": "string" } } } }) } pub fn planning_persona_pass_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": [ "persona", "intent", "constraints", "risks", "acceptance_criteria", "evidence" ], "properties": { "persona": planning_persona_schema(), "intent": { "type": "string" }, "constraints": { "type": "array", "items": { "type": "string" } }, "risks": { "type": "array", "items": { "type": "string" } }, "acceptance_criteria": { "type": "array", "items": { "type": "string" } }, "evidence": planning_persona_evidence_schema() } }) } pub fn planning_contract_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["contract_version", "ordered_personas", "conflict_rules"], "properties": { "contract_version": { "type": "integer" }, "ordered_personas": { "type": "array", "items": planning_persona_schema(), "minItems": 3, "maxItems": 3 }, "conflict_rules": { "type": "array", "items": planning_conflict_rule_schema() } } }) } pub fn legacy_output_projection_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["goal_md_stage", "standards_md_stage", "plan_stage"], "properties": { "goal_md_stage": planning_persona_schema(), "standards_md_stage": planning_persona_schema(), "plan_stage": planning_persona_schema() } }) } pub fn planner_contract_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": [ "kind", "question", "goal_md", "standards_md", "plan", "planning_contract_version", "contract", "persona_passes", "quality_gate", "single_pass_projection" ], "properties": { "kind": { "type": "string", "enum": ["question", "final"] }, "question": { "type": ["string", "null"] }, "goal_md": { "type": ["string", "null"] }, "standards_md": { "type": ["string", "null"] }, "plan": { "anyOf": [ plan_schema(), { "type": "null" } ] }, "planning_contract_version": { "type": "integer" }, "contract": planning_contract_schema(), "persona_passes": { "type": "array", "items": planning_persona_pass_schema() }, "quality_gate": { "anyOf": [ planning_quality_gate_schema(), { "type": "null" } ] }, "single_pass_projection": { "anyOf": [ legacy_output_projection_schema(), { "type": "null" } ] } } }) } pub fn cleanup_rule_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["label", "description"], "properties": { "label": { "type": "string" }, "description": { "type": "string" } } }) } pub fn plan_step_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": [ "id", "title", "purpose", "notes", "inputs", "outputs", "dependencies", "verification", "cleanup_requirements", "status", "attempts" ], "properties": { "id": { "type": "string" }, "title": { "type": "string" }, "purpose": { "type": "string" }, "notes": { "type": "string" }, "inputs": { "type": "array", "items": { "type": "string" } }, "outputs": { "type": "array", "items": { "type": "string" } }, "dependencies": { "type": "array", "items": { "type": "string" } }, "verification": { "type": "array", "items": verification_check_schema() }, "cleanup_requirements": { "type": "array", "items": cleanup_rule_schema() }, "status": { "type": "string", "enum": ["todo", "active", "done", "blocked"] }, "attempts": { "type": "integer" } } }) } pub fn plan_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["version", "goal_summary", "steps"], "properties": { "version": { "type": "integer" }, "goal_summary": { "type": "string" }, "steps": { "type": "array", "items": plan_step_schema() } } }) } pub fn plan_delta_schema() -> Value { json!({ "type": "object", "additionalProperties": false, "required": ["goal_summary", "step_updates", "remove_step_ids", "pending_step_order"], "properties": { "goal_summary": { "type": ["string", "null"] }, "step_updates": { "type": "array", "items": plan_step_schema() }, "remove_step_ids": { "type": "array", "items": { "type": "string" } }, "pending_step_order": { "type": "array", "items": { "type": "string" } } } }) } #[cfg(test)] mod tests { use super::*; #[test] fn plan_schema_locks_down_nested_objects() { let schema = plan_schema(); assert_eq!(schema["additionalProperties"], false); assert_eq!( schema["properties"]["steps"]["items"]["additionalProperties"], false ); assert_eq!( schema["properties"]["steps"]["items"]["properties"]["verification"]["items"] ["additionalProperties"], false ); assert_eq!( schema["properties"]["steps"]["items"]["properties"]["cleanup_requirements"]["items"] ["additionalProperties"], false ); assert_eq!( schema["properties"]["steps"]["items"]["properties"]["notes"]["type"], "string" ); } #[test] fn plan_delta_schema_allows_partial_replans() { let schema = plan_delta_schema(); assert_eq!(schema["additionalProperties"], false); assert_eq!( schema["required"], json!([ "goal_summary", "step_updates", "remove_step_ids", "pending_step_order" ]) ); assert_eq!( schema["properties"]["goal_summary"]["type"], json!(["string", "null"]) ); assert_eq!( schema["properties"]["step_updates"]["items"]["additionalProperties"], false ); } #[test] fn planner_contract_schema_carries_three_pass_contract_fields() { let schema = planner_contract_schema(); assert_eq!( schema["required"], json!(["kind","question","goal_md","standards_md","plan","planning_contract_version","contract","persona_passes","quality_gate","single_pass_projection"]) ); assert_eq!( schema["properties"]["contract"]["required"], json!(["contract_version","ordered_personas","conflict_rules"]) ); assert!(schema["properties"]["quality_gate"].is_object()); } }