use tatara_core::domain::compliance_binding::*;
use tatara_core::domain::convergence_graph::*;
use tatara_core::domain::convergence_state::*;
use tatara_core::domain::point_id::PointId;
pub struct ConvergencePlanner;
impl ConvergencePlanner {
pub fn plan(
graph: &ConvergenceGraph,
bindings: &[ComplianceBinding],
) -> Result<ConvergencePlan, GraphError> {
graph.validate()?;
let execution_order = graph.topological_order()?;
let compliance = Self::resolve_compliance(graph, bindings);
let cache_hits = Self::find_cache_hits(graph);
let critical_path = Self::compute_critical_path(graph, &execution_order);
Ok(ConvergencePlan {
graph: graph.clone(),
execution_order,
compliance,
cache_hits,
critical_path,
})
}
fn resolve_compliance(
graph: &ConvergenceGraph,
bindings: &[ComplianceBinding],
) -> ComplianceClosure {
let mut resolved = Vec::new();
let mut plan_time = 0usize;
let mut at_boundary = 0usize;
let mut post_convergence = 0usize;
for binding in bindings {
let matching_points: Vec<PointId> = graph
.points
.iter()
.filter(|(id, point)| {
binding
.selector
.matches(&point.point_type, &point.substrate, id, None, None)
})
.map(|(id, _)| *id)
.collect();
if !matching_points.is_empty() {
match binding.phase {
VerificationPhase::PlanTime => plan_time += 1,
VerificationPhase::AtBoundary => at_boundary += 1,
VerificationPhase::PostConvergence => post_convergence += 1,
}
resolved.push(ResolvedControl {
control: binding.control.clone(),
point_ids: matching_points,
phase: binding.phase,
});
}
}
ComplianceClosure {
bindings: bindings.to_vec(),
resolved,
plan_time_count: plan_time,
at_boundary_count: at_boundary,
post_convergence_count: post_convergence,
}
}
fn find_cache_hits(graph: &ConvergenceGraph) -> Vec<PointId> {
graph
.points
.iter()
.filter(|(_, point)| point.boundary.output_attestation.is_some())
.map(|(id, _)| *id)
.collect()
}
fn compute_critical_path(graph: &ConvergenceGraph, topo_order: &[PointId]) -> Vec<PointId> {
if topo_order.is_empty() {
return Vec::new();
}
let mut dist: std::collections::HashMap<PointId, (usize, Option<PointId>)> =
std::collections::HashMap::new();
for &id in topo_order {
dist.insert(id, (1, None));
}
for &id in topo_order {
let current_dist = dist[&id].0;
for edge in &graph.edges {
if edge.from == id {
let neighbor_dist = dist.get(&edge.to).map(|d| d.0).unwrap_or(0);
if current_dist + 1 > neighbor_dist {
dist.insert(edge.to, (current_dist + 1, Some(id)));
}
}
}
}
let (&end, _) = dist.iter().max_by_key(|(_, (d, _))| *d).unwrap();
let mut path = vec![end];
let mut current = end;
while let Some((_, Some(prev))) = dist.get(¤t) {
path.push(*prev);
current = *prev;
}
path.reverse();
path
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_point(name: &str, substrate: SubstrateType) -> (PointId, ConvergencePoint) {
let id = PointId::compute(name.as_bytes(), &[], b"desired");
let point = ConvergencePoint {
name: name.into(),
description: format!("{name} point"),
monotone: true,
mechanism: ConvergenceMechanism::Local,
state: ConvergenceState::new(name),
boundary: ConvergenceBoundary::default(),
point_type: ConvergencePointType::Transform,
horizon: ConvergenceHorizon::Bounded,
substrate,
computation_mode: ComputationMode::Mechanical,
};
(id, point)
}
#[test]
fn test_plan_simple_graph() {
let mut graph = ConvergenceGraph::new();
let (a, pa) = make_point("a", SubstrateType::Compute);
let (b, pb) = make_point("b", SubstrateType::Network);
graph.add_point(a, pa);
graph.add_point(b, pb);
graph.add_edge(TypedEdge {
from: a,
to: b,
edge_type: EdgeType::Attestation,
});
let plan = ConvergencePlanner::plan(&graph, &[]).unwrap();
assert_eq!(plan.execution_order.len(), 2);
assert!(plan.cache_hits.is_empty());
}
#[test]
fn test_plan_with_compliance_bindings() {
let mut graph = ConvergenceGraph::new();
let (a, pa) = make_point("a", SubstrateType::Security);
let (b, pb) = make_point("b", SubstrateType::Compute);
graph.add_point(a, pa);
graph.add_point(b, pb);
let bindings = vec![
ComplianceBinding {
selector: PointSelector::BySubstrate(SubstrateType::Security),
control: ComplianceControl {
framework: "nist-800-53".into(),
control_id: "AC-6".into(),
description: "Least privilege".into(),
},
phase: VerificationPhase::PlanTime,
},
ComplianceBinding {
selector: PointSelector::All,
control: ComplianceControl {
framework: "nist-800-53".into(),
control_id: "AU-2".into(),
description: "Audit events".into(),
},
phase: VerificationPhase::AtBoundary,
},
];
let plan = ConvergencePlanner::plan(&graph, &bindings).unwrap();
assert_eq!(plan.compliance.plan_time_count, 1);
assert_eq!(plan.compliance.at_boundary_count, 1);
assert_eq!(plan.compliance.resolved.len(), 2);
let ac6 = plan
.compliance
.resolved
.iter()
.find(|r| r.control.control_id == "AC-6")
.unwrap();
assert_eq!(ac6.point_ids.len(), 1);
let au2 = plan
.compliance
.resolved
.iter()
.find(|r| r.control.control_id == "AU-2")
.unwrap();
assert_eq!(au2.point_ids.len(), 2);
}
#[test]
fn test_critical_path_linear() {
let mut graph = ConvergenceGraph::new();
let (a, pa) = make_point("a", SubstrateType::Compute);
let (b, pb) = make_point("b", SubstrateType::Compute);
let (c, pc) = make_point("c", SubstrateType::Compute);
graph.add_point(a, pa);
graph.add_point(b, pb);
graph.add_point(c, pc);
graph.add_edge(TypedEdge {
from: a,
to: b,
edge_type: EdgeType::Data,
});
graph.add_edge(TypedEdge {
from: b,
to: c,
edge_type: EdgeType::Data,
});
let plan = ConvergencePlanner::plan(&graph, &[]).unwrap();
assert_eq!(plan.critical_path.len(), 3);
}
#[test]
fn test_critical_path_diamond() {
let mut graph = ConvergenceGraph::new();
let (a, pa) = make_point("root", SubstrateType::Compute);
let (b, pb) = make_point("left", SubstrateType::Compute);
let (c, pc) = make_point("right", SubstrateType::Compute);
let (d, pd) = make_point("join", SubstrateType::Compute);
graph.add_point(a, pa);
graph.add_point(b, pb);
graph.add_point(c, pc);
graph.add_point(d, pd);
graph.add_edge(TypedEdge {
from: a,
to: b,
edge_type: EdgeType::Data,
});
graph.add_edge(TypedEdge {
from: a,
to: c,
edge_type: EdgeType::Data,
});
graph.add_edge(TypedEdge {
from: b,
to: d,
edge_type: EdgeType::Data,
});
graph.add_edge(TypedEdge {
from: c,
to: d,
edge_type: EdgeType::Data,
});
let plan = ConvergencePlanner::plan(&graph, &[]).unwrap();
assert_eq!(plan.critical_path.len(), 3);
}
#[test]
fn test_cache_hits() {
let mut graph = ConvergenceGraph::new();
let (a, mut pa) = make_point("a", SubstrateType::Compute);
pa.boundary.output_attestation = Some("blake3:cached".into());
let (b, pb) = make_point("b", SubstrateType::Compute);
graph.add_point(a, pa);
graph.add_point(b, pb);
let plan = ConvergencePlanner::plan(&graph, &[]).unwrap();
assert_eq!(plan.cache_hits.len(), 1);
assert!(plan.cache_hits.contains(&a));
}
#[test]
fn test_invalid_graph_fails() {
let mut graph = ConvergenceGraph::new();
let (a, pa) = make_point("a", SubstrateType::Compute);
let (b, pb) = make_point("b", SubstrateType::Compute);
graph.add_point(a, pa);
graph.add_point(b, pb);
graph.add_edge(TypedEdge {
from: a,
to: b,
edge_type: EdgeType::Data,
});
graph.add_edge(TypedEdge {
from: b,
to: a,
edge_type: EdgeType::Data,
});
assert!(ConvergencePlanner::plan(&graph, &[]).is_err());
}
}