1use serde::{Deserialize, Serialize};
8use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
9
10use super::compliance_binding::ComplianceClosure;
11use super::convergence_state::{ConvergencePoint, SubstrateType};
12use super::multi_distance::ConvergenceBandwidth;
13use super::point_id::PointId;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum EdgeType {
19 Data,
21 Control,
23 Attestation,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct TypedEdge {
30 pub from: PointId,
32 pub to: PointId,
34 pub edge_type: EdgeType,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, Default)]
40pub struct ConvergenceGraph {
41 #[serde(with = "point_id_map")]
44 pub points: BTreeMap<PointId, ConvergencePoint>,
45 pub edges: Vec<TypedEdge>,
47}
48
49mod point_id_map {
50 use super::*;
51 use serde::de::{self, MapAccess, Visitor};
52 use serde::ser::SerializeMap;
53
54 pub fn serialize<S>(
55 map: &BTreeMap<PointId, ConvergencePoint>,
56 serializer: S,
57 ) -> Result<S::Ok, S::Error>
58 where
59 S: serde::Serializer,
60 {
61 let mut m = serializer.serialize_map(Some(map.len()))?;
62 for (k, v) in map {
63 m.serialize_entry(&k.to_hex(), v)?;
64 }
65 m.end()
66 }
67
68 pub fn deserialize<'de, D>(
69 deserializer: D,
70 ) -> Result<BTreeMap<PointId, ConvergencePoint>, D::Error>
71 where
72 D: serde::Deserializer<'de>,
73 {
74 struct PointIdMapVisitor;
75 impl<'de> Visitor<'de> for PointIdMapVisitor {
76 type Value = BTreeMap<PointId, ConvergencePoint>;
77 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
78 f.write_str("a map with hex PointId keys")
79 }
80 fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
81 let mut map = BTreeMap::new();
82 while let Some((key, value)) = access.next_entry::<String, ConvergencePoint>()? {
83 let id = PointId::from_hex(&key).map_err(de::Error::custom)?;
84 map.insert(id, value);
85 }
86 Ok(map)
87 }
88 }
89 deserializer.deserialize_map(PointIdMapVisitor)
90 }
91}
92
93impl ConvergenceGraph {
94 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn add_point(&mut self, id: PointId, point: ConvergencePoint) {
101 self.points.insert(id, point);
102 }
103
104 pub fn add_edge(&mut self, edge: TypedEdge) {
106 self.edges.push(edge);
107 }
108
109 pub fn topological_order(&self) -> Result<Vec<PointId>, GraphError> {
112 let mut in_degree: HashMap<PointId, usize> = HashMap::new();
113 let mut adjacency: HashMap<PointId, Vec<PointId>> = HashMap::new();
114
115 for id in self.points.keys() {
116 in_degree.entry(*id).or_insert(0);
117 adjacency.entry(*id).or_default();
118 }
119
120 for edge in &self.edges {
121 *in_degree.entry(edge.to).or_insert(0) += 1;
122 adjacency.entry(edge.from).or_default().push(edge.to);
123 }
124
125 let mut queue: VecDeque<PointId> = in_degree
126 .iter()
127 .filter(|(_, °)| deg == 0)
128 .map(|(id, _)| *id)
129 .collect();
130
131 let mut order = Vec::new();
132
133 while let Some(id) = queue.pop_front() {
134 order.push(id);
135 if let Some(neighbors) = adjacency.get(&id) {
136 for neighbor in neighbors {
137 if let Some(deg) = in_degree.get_mut(neighbor) {
138 *deg -= 1;
139 if *deg == 0 {
140 queue.push_back(*neighbor);
141 }
142 }
143 }
144 }
145 }
146
147 if order.len() != self.points.len() {
148 Err(GraphError::CycleDetected)
149 } else {
150 Ok(order)
151 }
152 }
153
154 pub fn validate(&self) -> Result<(), GraphError> {
156 for edge in &self.edges {
157 if !self.points.contains_key(&edge.from) {
158 return Err(GraphError::MissingPoint(edge.from));
159 }
160 if !self.points.contains_key(&edge.to) {
161 return Err(GraphError::MissingPoint(edge.to));
162 }
163 }
164 self.topological_order()?;
165 Ok(())
166 }
167
168 pub fn points_by_substrate(
170 &self,
171 substrate: &SubstrateType,
172 ) -> Vec<(&PointId, &ConvergencePoint)> {
173 self.points
174 .iter()
175 .filter(|(_, p)| &p.substrate == substrate)
176 .collect()
177 }
178
179 pub fn point_count(&self) -> usize {
181 self.points.len()
182 }
183
184 pub fn edge_count(&self) -> usize {
186 self.edges.len()
187 }
188
189 pub fn forward_closure(&self, point_id: &PointId) -> BTreeSet<PointId> {
193 let mut adjacency: HashMap<PointId, Vec<PointId>> = HashMap::new();
194 for edge in &self.edges {
195 adjacency.entry(edge.to).or_default().push(edge.from);
196 }
197
198 let mut visited = BTreeSet::new();
199 let mut queue = VecDeque::new();
200 queue.push_back(*point_id);
201
202 while let Some(id) = queue.pop_front() {
203 if !visited.insert(id) {
204 continue;
205 }
206 if let Some(deps) = adjacency.get(&id) {
207 for dep in deps {
208 queue.push_back(*dep);
209 }
210 }
211 }
212
213 visited.remove(point_id);
214 visited
215 }
216
217 pub fn reverse_closure(&self, point_id: &PointId) -> BTreeSet<PointId> {
221 let mut adjacency: HashMap<PointId, Vec<PointId>> = HashMap::new();
222 for edge in &self.edges {
223 adjacency.entry(edge.from).or_default().push(edge.to);
224 }
225
226 let mut visited = BTreeSet::new();
227 let mut queue = VecDeque::new();
228 queue.push_back(*point_id);
229
230 while let Some(id) = queue.pop_front() {
231 if !visited.insert(id) {
232 continue;
233 }
234 if let Some(deps) = adjacency.get(&id) {
235 for dep in deps {
236 queue.push_back(*dep);
237 }
238 }
239 }
240
241 visited.remove(point_id);
242 visited
243 }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct SubstrateDAG {
249 pub substrate: SubstrateType,
251 pub points: BTreeMap<PointId, ConvergencePoint>,
253 pub internal_edges: Vec<TypedEdge>,
255 pub cross_edges: Vec<TypedEdge>,
257 pub bandwidth: ConvergenceBandwidth,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct ConvergencePlan {
264 pub graph: ConvergenceGraph,
266 pub execution_order: Vec<PointId>,
268 pub compliance: ComplianceClosure,
270 pub cache_hits: Vec<PointId>,
272 pub critical_path: Vec<PointId>,
274}
275
276#[derive(Debug, Clone, thiserror::Error)]
278pub enum GraphError {
279 #[error("cycle detected in convergence graph")]
280 CycleDetected,
281 #[error("edge references missing point: {0}")]
282 MissingPoint(PointId),
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::domain::convergence_state::*;
289
290 fn make_point(name: &str, substrate: SubstrateType) -> (PointId, ConvergencePoint) {
291 let id = PointId::compute(name.as_bytes(), &[], b"desired");
292 let point = ConvergencePoint {
293 name: name.into(),
294 description: format!("{name} point"),
295 monotone: true,
296 mechanism: ConvergenceMechanism::Local,
297 state: ConvergenceState::new(name),
298 boundary: ConvergenceBoundary::default(),
299 point_type: ConvergencePointType::Transform,
300 horizon: ConvergenceHorizon::Bounded,
301 substrate,
302 computation_mode: ComputationMode::Mechanical,
303 };
304 (id, point)
305 }
306
307 #[test]
308 fn test_empty_graph() {
309 let g = ConvergenceGraph::new();
310 assert_eq!(g.point_count(), 0);
311 assert!(g.validate().is_ok());
312 assert!(g.topological_order().unwrap().is_empty());
313 }
314
315 #[test]
316 fn test_single_point() {
317 let mut g = ConvergenceGraph::new();
318 let (id, p) = make_point("a", SubstrateType::Compute);
319 g.add_point(id, p);
320 assert_eq!(g.point_count(), 1);
321 assert!(g.validate().is_ok());
322 assert_eq!(g.topological_order().unwrap(), vec![id]);
323 }
324
325 #[test]
326 fn test_linear_chain() {
327 let mut g = ConvergenceGraph::new();
328 let (a, pa) = make_point("a", SubstrateType::Compute);
329 let (b, pb) = make_point("b", SubstrateType::Compute);
330 let (c, pc) = make_point("c", SubstrateType::Compute);
331 g.add_point(a, pa);
332 g.add_point(b, pb);
333 g.add_point(c, pc);
334 g.add_edge(TypedEdge {
335 from: a,
336 to: b,
337 edge_type: EdgeType::Attestation,
338 });
339 g.add_edge(TypedEdge {
340 from: b,
341 to: c,
342 edge_type: EdgeType::Attestation,
343 });
344
345 let order = g.topological_order().unwrap();
346 assert_eq!(order.len(), 3);
347 let pos_a = order.iter().position(|x| *x == a).unwrap();
348 let pos_b = order.iter().position(|x| *x == b).unwrap();
349 let pos_c = order.iter().position(|x| *x == c).unwrap();
350 assert!(pos_a < pos_b);
351 assert!(pos_b < pos_c);
352 }
353
354 #[test]
355 fn test_cycle_detection() {
356 let mut g = ConvergenceGraph::new();
357 let (a, pa) = make_point("a", SubstrateType::Compute);
358 let (b, pb) = make_point("b", SubstrateType::Compute);
359 g.add_point(a, pa);
360 g.add_point(b, pb);
361 g.add_edge(TypedEdge {
362 from: a,
363 to: b,
364 edge_type: EdgeType::Data,
365 });
366 g.add_edge(TypedEdge {
367 from: b,
368 to: a,
369 edge_type: EdgeType::Data,
370 });
371
372 assert!(matches!(
373 g.topological_order(),
374 Err(GraphError::CycleDetected)
375 ));
376 }
377
378 #[test]
379 fn test_missing_point_validation() {
380 let mut g = ConvergenceGraph::new();
381 let (a, pa) = make_point("a", SubstrateType::Compute);
382 let missing = PointId::compute(b"missing", &[], b"state");
383 g.add_point(a, pa);
384 g.add_edge(TypedEdge {
385 from: a,
386 to: missing,
387 edge_type: EdgeType::Data,
388 });
389
390 assert!(matches!(g.validate(), Err(GraphError::MissingPoint(_))));
391 }
392
393 #[test]
394 fn test_points_by_substrate() {
395 let mut g = ConvergenceGraph::new();
396 let (a, pa) = make_point("a", SubstrateType::Compute);
397 let (b, pb) = make_point("b", SubstrateType::Security);
398 let (c, pc) = make_point("c", SubstrateType::Compute);
399 g.add_point(a, pa);
400 g.add_point(b, pb);
401 g.add_point(c, pc);
402
403 assert_eq!(g.points_by_substrate(&SubstrateType::Compute).len(), 2);
404 assert_eq!(g.points_by_substrate(&SubstrateType::Security).len(), 1);
405 assert_eq!(g.points_by_substrate(&SubstrateType::Financial).len(), 0);
406 }
407
408 #[test]
409 fn test_forward_closure() {
410 let mut g = ConvergenceGraph::new();
411 let (a, pa) = make_point("a", SubstrateType::Compute);
412 let (b, pb) = make_point("b", SubstrateType::Compute);
413 let (c, pc) = make_point("c", SubstrateType::Compute);
414 g.add_point(a, pa);
415 g.add_point(b, pb);
416 g.add_point(c, pc);
417 g.add_edge(TypedEdge {
418 from: a,
419 to: b,
420 edge_type: EdgeType::Data,
421 });
422 g.add_edge(TypedEdge {
423 from: b,
424 to: c,
425 edge_type: EdgeType::Data,
426 });
427
428 let closure = g.forward_closure(&c);
429 assert!(closure.contains(&a));
430 assert!(closure.contains(&b));
431 assert!(!closure.contains(&c));
432 }
433
434 #[test]
435 fn test_reverse_closure() {
436 let mut g = ConvergenceGraph::new();
437 let (a, pa) = make_point("a", SubstrateType::Compute);
438 let (b, pb) = make_point("b", SubstrateType::Compute);
439 let (c, pc) = make_point("c", SubstrateType::Compute);
440 g.add_point(a, pa);
441 g.add_point(b, pb);
442 g.add_point(c, pc);
443 g.add_edge(TypedEdge {
444 from: a,
445 to: b,
446 edge_type: EdgeType::Data,
447 });
448 g.add_edge(TypedEdge {
449 from: b,
450 to: c,
451 edge_type: EdgeType::Data,
452 });
453
454 let closure = g.reverse_closure(&a);
455 assert!(closure.contains(&b));
456 assert!(closure.contains(&c));
457 assert!(!closure.contains(&a));
458 }
459
460 #[test]
461 fn test_diamond_dag() {
462 let mut g = ConvergenceGraph::new();
463 let (a, pa) = make_point("a", SubstrateType::Compute);
464 let (b, pb) = make_point("b", SubstrateType::Compute);
465 let (c, pc) = make_point("c", SubstrateType::Compute);
466 let (d, pd) = make_point("d", SubstrateType::Compute);
467 g.add_point(a, pa);
468 g.add_point(b, pb);
469 g.add_point(c, pc);
470 g.add_point(d, pd);
471 g.add_edge(TypedEdge {
472 from: a,
473 to: b,
474 edge_type: EdgeType::Data,
475 });
476 g.add_edge(TypedEdge {
477 from: a,
478 to: c,
479 edge_type: EdgeType::Data,
480 });
481 g.add_edge(TypedEdge {
482 from: b,
483 to: d,
484 edge_type: EdgeType::Data,
485 });
486 g.add_edge(TypedEdge {
487 from: c,
488 to: d,
489 edge_type: EdgeType::Data,
490 });
491
492 let order = g.topological_order().unwrap();
493 assert_eq!(order.len(), 4);
494 let pos_a = order.iter().position(|x| *x == a).unwrap();
495 let pos_d = order.iter().position(|x| *x == d).unwrap();
496 assert!(pos_a < pos_d);
497 }
498
499 #[test]
500 fn test_graph_serde() {
501 let mut g = ConvergenceGraph::new();
502 let (a, pa) = make_point("a", SubstrateType::Compute);
503 let (b, pb) = make_point("b", SubstrateType::Network);
504 g.add_point(a, pa);
505 g.add_point(b, pb);
506 g.add_edge(TypedEdge {
507 from: a,
508 to: b,
509 edge_type: EdgeType::Control,
510 });
511
512 let json = serde_json::to_string(&g).unwrap();
513 let parsed: ConvergenceGraph = serde_json::from_str(&json).unwrap();
514 assert_eq!(parsed.point_count(), 2);
515 assert_eq!(parsed.edge_count(), 1);
516 }
517}