starweaver_runtime/capability/
ordering.rs1use std::{
4 collections::{BTreeMap, BTreeSet},
5 sync::Arc,
6};
7
8use thiserror::Error;
9
10use super::{AgentCapability, CapabilityId};
11
12#[derive(Debug, Error)]
14pub enum CapabilityOrderError {
15 #[error("capability id '{0}' is duplicated")]
17 DuplicateId(String),
18 #[error("capability '{capability}' references missing dependency '{dependency}'")]
20 MissingDependency {
21 capability: String,
23 dependency: String,
25 },
26 #[error("capability ordering cycle detected among {0}")]
28 Cycle(String),
29}
30
31pub fn resolve_capability_order(
37 capabilities: &[Arc<dyn AgentCapability>],
38) -> Result<Vec<Arc<dyn AgentCapability>>, CapabilityOrderError> {
39 let mut ids = Vec::with_capacity(capabilities.len());
40 let mut by_id = BTreeMap::new();
41 for (index, capability) in capabilities.iter().enumerate() {
42 let id = capability.spec().id;
43 if by_id.insert(id.clone(), index).is_some() {
44 return Err(CapabilityOrderError::DuplicateId(id.as_str().to_string()));
45 }
46 ids.push(id);
47 }
48
49 let mut outgoing = BTreeMap::<CapabilityId, BTreeSet<CapabilityId>>::new();
50 let mut incoming = BTreeMap::<CapabilityId, usize>::new();
51 for id in &ids {
52 outgoing.entry(id.clone()).or_default();
53 incoming.entry(id.clone()).or_default();
54 }
55
56 for (index, capability) in capabilities.iter().enumerate() {
57 let spec = capability.spec();
58 let current = ids[index].clone();
59 for dependency in spec.ordering.after {
60 if !by_id.contains_key(&dependency) {
61 return Err(CapabilityOrderError::MissingDependency {
62 capability: current.as_str().to_string(),
63 dependency: dependency.as_str().to_string(),
64 });
65 }
66 if outgoing
67 .entry(dependency.clone())
68 .or_default()
69 .insert(current.clone())
70 {
71 *incoming.entry(current.clone()).or_default() += 1;
72 }
73 }
74 for target in spec.ordering.before {
75 if !by_id.contains_key(&target) {
76 return Err(CapabilityOrderError::MissingDependency {
77 capability: current.as_str().to_string(),
78 dependency: target.as_str().to_string(),
79 });
80 }
81 if outgoing
82 .entry(current.clone())
83 .or_default()
84 .insert(target.clone())
85 {
86 *incoming.entry(target).or_default() += 1;
87 }
88 }
89 }
90
91 let mut emitted = BTreeSet::<CapabilityId>::new();
92 let mut ordered = Vec::with_capacity(capabilities.len());
93 while ordered.len() < capabilities.len() {
94 let Some(next) = ids
95 .iter()
96 .find(|id| !emitted.contains(*id) && incoming.get(*id).copied().unwrap_or(0) == 0)
97 .cloned()
98 else {
99 let cycle = ids
100 .iter()
101 .filter(|id| !emitted.contains(*id))
102 .map(|id| id.as_str().to_string())
103 .collect::<Vec<_>>()
104 .join(",");
105 return Err(CapabilityOrderError::Cycle(cycle));
106 };
107 emitted.insert(next.clone());
108 let index = by_id[&next];
109 ordered.push(capabilities[index].clone());
110 if let Some(targets) = outgoing.get(&next) {
111 for target in targets {
112 if let Some(count) = incoming.get_mut(target) {
113 *count = count.saturating_sub(1);
114 }
115 }
116 }
117 }
118 Ok(ordered)
119}