//! End-to-end: Orchestrator authors a contract + a graph, a //! Worker runs each feature, the Validator returns the verdict. //! //! Run with: `cargo run -p jouleclaw-mission --example three_role_demo` use async_trait::async_trait; use jouleclaw_contract::{ Acceptance, Assertion, AssertionCheck, EvidenceItem, Feature, Priority, ValidationContract, }; use jouleclaw_graph::{ Edge, MergeStrategy, Node, NodeId, NodeKind, RunGraph, WorkerHandoffEvent, }; use jouleclaw_mission::{ Mission, MissionOptions, Worker, WorkerAssignment, WorkerOutcome, }; // One Worker stand-in. Real implementations route to an L1 tool, // an L3 model, or a human-in-the-loop. struct DemoWorker; #[async_trait] impl Worker for DemoWorker { async fn run( &self, a: WorkerAssignment, ) -> Result> { // Pretend each feature emits the evidence its claimed // assertion requires. let mut ev = Vec::new(); for c in &a.feature.claims { if let Some(assertion) = a.contract.assertion(c) { match &assertion.check { AssertionCheck::Command { command_hash, expected_exit, } => ev.push(EvidenceItem::CommandRun { command_hash: command_hash.clone(), exit_code: *expected_exit, }), AssertionCheck::Artifact { path, expected_hash } => { ev.push(EvidenceItem::ArtifactPresent { path: path.clone(), hash: expected_hash .clone() .unwrap_or_else(|| "ff".repeat(32)), }) } AssertionCheck::Tool { tool_id, args_hash } => { ev.push(EvidenceItem::ToolResult { tool_id: tool_id.clone(), args_hash: args_hash.clone(), passed: true, }) } AssertionCheck::Checker { checker_id } => { ev.push(EvidenceItem::CheckerResult { checker_id: checker_id.clone(), passed: true, }) } } } } Ok(WorkerOutcome { handoff: WorkerHandoffEvent { worker: a.worker_id.clone(), node_id: None, feature_id: Some(a.feature.id.clone()), closed_at: 1_700_000_000, completed: vec![format!("did {}", a.feature.title)], undone: vec![], commands: vec![], issues_found: vec![], procedures_followed: vec!["AGENTS.md".into()], }, evidence: ev, }) } } #[tokio::main] async fn main() { // ── Orchestrator: author the contract + the graph ──────────── let contract = ValidationContract::seal( "Three-role mission demo", "did:key:zOrchestrator", vec![ Assertion { id: "A-1".into(), title: "Build passes".into(), rationale: "cargo test -p target".into(), priority: Priority::MustHave, check: AssertionCheck::Command { command_hash: "build-hash".into(), expected_exit: 0, }, }, Assertion { id: "A-2".into(), title: "Receipt journal written".into(), rationale: String::new(), priority: Priority::MustHave, check: AssertionCheck::Artifact { path: "out/receipts.jsonl".into(), expected_hash: None, }, }, ], vec![ Feature { id: "F-build".into(), title: "Build".into(), description: String::new(), claims: vec!["A-1".into()], }, Feature { id: "F-receipts".into(), title: "Wire receipts".into(), description: String::new(), claims: vec!["A-2".into()], }, ], Acceptance::default(), ) .expect("contract seals"); let graph = RunGraph { entry: NodeId(1), terminals: vec![NodeId(2)], nodes: vec![ Node { id: NodeId(1), kind: NodeKind::Stage("F-build".into()), label: "F-build".into(), merge: MergeStrategy::AllRequired, }, Node { id: NodeId(2), kind: NodeKind::Stage("F-receipts".into()), label: "F-receipts".into(), merge: MergeStrategy::AllRequired, }, ], edges: vec![Edge { from: NodeId(1), to: NodeId(2), label: None, }], }; let mut mission = Mission::start( contract, graph, "demo", MissionOptions { strict_workers: true, }, ) .expect("mission starts"); // ── Workers: dispatched serially by the convenience helper ─── let worker = DemoWorker; let verdict = mission .run_serial(&worker, "did:key:zWorker") .await .expect("workers run"); // ── Validator: the verdict ─────────────────────────────────── println!("contract: {}", verdict.contract_hash); println!("passed: {}", verdict.passed); println!("summary: {}", verdict.summary); println!("handoffs: {}", mission.run.journal.entries().len() - 1); println!( "coverage: {} verified / {} should-have-gaps / {} blocking", verdict .coverage .assertion_rollup .values() .filter(|r| matches!(r, jouleclaw_contract::AssertionRollup::Verified)) .count(), verdict.should_have_gaps.len(), verdict.blocking_gaps.len(), ); }