1use std::path::PathBuf;
26use std::sync::{Arc, Mutex};
27
28use async_trait::async_trait;
29use cpm_planner::audit::{AuditEvent, AuditSink};
30use cpm_planner::plan::{CallerId, Deliverable, DeliverableStatus, PlanGraph};
31use cpm_planner::ports::Planner;
32use cpm_planner::BasicCpmPlanner;
33
34#[derive(Debug, Default)]
36struct BufferingAudit {
37 events: Mutex<Vec<AuditEvent>>,
38}
39
40#[async_trait]
41impl AuditSink for BufferingAudit {
42 async fn record(&self, event: AuditEvent) -> anyhow::Result<()> {
43 let mut events = self
44 .events
45 .lock()
46 .map_err(|e| anyhow::anyhow!("audit buffer poisoned: {e}"))?;
47 events.push(event);
48 Ok(())
49 }
50}
51
52fn deliverable(
53 id: &str,
54 owned_files: &[&str],
55 prerequisites: &[&str],
56 estimated_effort_hours: f32,
57) -> Deliverable {
58 Deliverable {
59 id: id.to_string(),
60 owned_files: owned_files.iter().map(PathBuf::from).collect(),
61 prerequisites: prerequisites.iter().map(|s| s.to_string()).collect(),
62 estimated_effort_hours: Some(estimated_effort_hours),
63 metadata: serde_json::Value::Null,
64 }
65}
66
67#[tokio::main]
68async fn main() -> anyhow::Result<()> {
69 let audit = Arc::new(BufferingAudit::default());
71 let planner = BasicCpmPlanner::with_audit(audit.clone());
72
73 let graph = PlanGraph {
82 deliverables: vec![
83 deliverable("d1", &["src/a.rs"], &[], 1.0),
84 deliverable("d2", &["src/b.rs"], &[], 3.0),
85 deliverable("d3", &["src/c.rs"], &["d1", "d2"], 2.0),
86 deliverable("d4", &["src/d.rs"], &["d3"], 1.0),
87 ],
88 max_chained_dispatch: None,
89 };
90 let plan_id = planner.submit_plan(graph).await?;
91 println!("submitted plan: {plan_id}");
92
93 let status = planner.status(&plan_id).await?;
95 println!(
96 "critical path: {:?} ({:.1}h total)",
97 status.critical_path, status.critical_path_hours
98 );
99
100 let caller = CallerId("demo-orchestrator".to_string());
102 let mut round = 0;
103 loop {
104 round += 1;
105 let cohort = planner.acquire_cohort(&plan_id, &caller, 4).await?;
109 if cohort.rows.is_empty() {
110 println!("round {round}: no work remaining; plan is terminal.");
114 break;
115 }
116 let ids: Vec<&str> = cohort
117 .rows
118 .iter()
119 .map(|r| r.deliverable.id.as_str())
120 .collect();
121 println!("round {round}: acquired cohort {ids:?}");
122
123 for row in &cohort.rows {
124 planner
125 .mark_status(
126 &plan_id,
127 &row.deliverable.id,
128 &caller,
129 DeliverableStatus::Complete,
130 )
131 .await?;
132 println!(" marked {} complete", row.deliverable.id);
133 }
134 }
135
136 let status = planner.status(&plan_id).await?;
138 let complete = status
139 .deliverables
140 .iter()
141 .filter(|(_, s)| matches!(s, DeliverableStatus::Complete))
142 .count();
143 println!(
144 "final state: {complete}/{total} deliverables complete; {locks} locks held",
145 total = status.deliverables.len(),
146 locks = status.locks_held.len()
147 );
148
149 let events = audit
151 .events
152 .lock()
153 .map_err(|e| anyhow::anyhow!("audit buffer poisoned: {e}"))?;
154 println!("\naudit events ({} total):", events.len());
155 for event in events.iter() {
156 println!(" - {}", event.event_type);
157 }
158
159 Ok(())
160}