use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct SimulationNode {
pub node_id: String,
pub node_type: String,
pub inputs: serde_json::Value,
pub outputs: serde_json::Value,
pub dependencies: Vec<String>,
pub status: NodeStatus,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum NodeStatus {
Pending,
Running,
Completed,
Blocked,
Failed,
}
impl std::fmt::Display for NodeStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NodeStatus::Pending => write!(f, "PENDING"),
NodeStatus::Running => write!(f, "RUNNING"),
NodeStatus::Completed => write!(f, "COMPLETED"),
NodeStatus::Blocked => write!(f, "BLOCKED"),
NodeStatus::Failed => write!(f, "FAILED"),
}
}
}
impl SimulationNode {
pub fn new(node_id: &str, node_type: &str) -> Self {
Self {
node_id: node_id.to_string(),
node_type: node_type.to_string(),
inputs: serde_json::Value::Object(serde_json::Map::new()),
outputs: serde_json::Value::Object(serde_json::Map::new()),
dependencies: Vec::new(),
status: NodeStatus::Pending,
}
}
pub fn with_dependencies(mut self, deps: Vec<&str>) -> Self {
self.dependencies = deps.into_iter().map(String::from).collect();
self
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SimulationResult {
pub success: bool,
pub executed_nodes: Vec<String>,
pub failed_nodes: Vec<String>,
pub execution_order: Vec<String>,
pub total_time_ms: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct DagValidationReport {
pub valid: bool,
pub has_cycle: bool,
pub dangling_references: Vec<String>,
pub topological_order: Vec<String>,
pub node_count: usize,
}
pub fn verify_dag_integrity(dag: &HashMap<String, SimulationNode>) -> DagValidationReport {
use petgraph::graph::{DiGraph, NodeIndex};
let dangling: Vec<String> = dag
.iter()
.flat_map(|(node_id, node)| {
node.dependencies
.iter()
.filter(|dep| !dag.contains_key(*dep))
.map(move |dep| format!("{} → {}", node_id, dep))
})
.collect();
let mut graph = DiGraph::<&str, ()>::new();
let mut node_map: HashMap<&str, NodeIndex> = HashMap::new();
for node_id in dag.keys() {
let idx = graph.add_node(node_id.as_str());
node_map.insert(node_id.as_str(), idx);
}
for (node_id, node) in dag {
for dep in &node.dependencies {
if let (Some(&dep_idx), Some(&node_idx)) =
(node_map.get(dep.as_str()), node_map.get(node_id.as_str()))
{
graph.add_edge(dep_idx, node_idx, ());
}
}
}
match petgraph::algo::toposort(&graph, None) {
Ok(sorted_indices) => {
let topological_order: Vec<String> = sorted_indices
.iter()
.map(|&idx| graph[idx].to_string())
.collect();
DagValidationReport {
valid: dangling.is_empty(),
has_cycle: false,
dangling_references: dangling,
topological_order,
node_count: dag.len(),
}
}
Err(_cycle) => DagValidationReport {
valid: false,
has_cycle: true,
dangling_references: dangling,
topological_order: Vec::new(),
node_count: dag.len(),
},
}
}
pub fn simulate_execution(dag: &mut HashMap<String, SimulationNode>) -> SimulationResult {
let start = std::time::Instant::now();
let integrity = verify_dag_integrity(dag);
if !integrity.valid {
return SimulationResult {
success: false,
executed_nodes: Vec::new(),
failed_nodes: dag.keys().cloned().collect(),
execution_order: Vec::new(),
total_time_ms: 0.0,
};
}
let mut executed: Vec<String> = Vec::new();
let mut failed: Vec<String> = Vec::new();
let order = integrity.topological_order.clone();
for node_id in &order {
let deps_met = {
let node = &dag[node_id];
node.dependencies.iter().all(|dep| {
dag.get(dep)
.map_or(false, |n| n.status == NodeStatus::Completed)
})
};
let node = dag.get_mut(node_id).unwrap();
node.status = NodeStatus::Running;
if deps_met {
node.status = NodeStatus::Completed;
executed.push(node_id.clone());
} else {
node.status = NodeStatus::Blocked;
failed.push(node_id.clone());
}
}
let elapsed = start.elapsed().as_secs_f64() * 1000.0;
SimulationResult {
success: failed.is_empty(),
executed_nodes: executed,
failed_nodes: failed,
execution_order: order,
total_time_ms: (elapsed * 100.0).round() / 100.0,
}
}
pub fn stitch_fractal_urns(parent_urn: &str, child_urns: &[&str]) -> HashMap<String, String> {
child_urns
.iter()
.map(|&child_urn| {
let leaf = child_urn.rsplit(':').next().unwrap_or(child_urn);
let fractal_urn = format!("{}:{}", parent_urn, leaf);
(child_urn.to_string(), fractal_urn)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_dag() -> HashMap<String, SimulationNode> {
let mut dag = HashMap::new();
dag.insert("a".into(), SimulationNode::new("a", "compute"));
dag.insert(
"b".into(),
SimulationNode::new("b", "compute").with_dependencies(vec!["a"]),
);
dag.insert(
"c".into(),
SimulationNode::new("c", "compute").with_dependencies(vec!["a"]),
);
dag.insert(
"d".into(),
SimulationNode::new("d", "output").with_dependencies(vec!["b", "c"]),
);
dag
}
#[test]
fn test_verify_dag_integrity_valid() {
let dag = make_dag();
let report = verify_dag_integrity(&dag);
assert!(report.valid);
assert!(!report.has_cycle);
assert!(report.dangling_references.is_empty());
assert_eq!(report.node_count, 4);
let order = &report.topological_order;
let a_pos = order.iter().position(|x| x == "a").unwrap();
let b_pos = order.iter().position(|x| x == "b").unwrap();
let c_pos = order.iter().position(|x| x == "c").unwrap();
let d_pos = order.iter().position(|x| x == "d").unwrap();
assert!(a_pos < b_pos);
assert!(a_pos < c_pos);
assert!(b_pos < d_pos);
assert!(c_pos < d_pos);
}
#[test]
fn test_verify_dag_detects_cycle() {
let mut dag = HashMap::new();
dag.insert(
"a".into(),
SimulationNode::new("a", "compute").with_dependencies(vec!["b"]),
);
dag.insert(
"b".into(),
SimulationNode::new("b", "compute").with_dependencies(vec!["a"]),
);
let report = verify_dag_integrity(&dag);
assert!(!report.valid);
assert!(report.has_cycle);
assert!(report.topological_order.is_empty());
}
#[test]
fn test_verify_dag_detects_dangling() {
let mut dag = HashMap::new();
dag.insert(
"a".into(),
SimulationNode::new("a", "compute").with_dependencies(vec!["missing"]),
);
let report = verify_dag_integrity(&dag);
assert!(!report.valid);
assert!(!report.dangling_references.is_empty());
}
#[test]
fn test_simulate_execution_valid() {
let mut dag = make_dag();
let result = simulate_execution(&mut dag);
assert!(result.success);
assert_eq!(result.executed_nodes.len(), 4);
assert!(result.failed_nodes.is_empty());
}
#[test]
fn test_simulate_execution_invalid_dag() {
let mut dag = HashMap::new();
dag.insert(
"a".into(),
SimulationNode::new("a", "compute").with_dependencies(vec!["b"]),
);
dag.insert(
"b".into(),
SimulationNode::new("b", "compute").with_dependencies(vec!["a"]),
);
let result = simulate_execution(&mut dag);
assert!(!result.success);
assert_eq!(result.failed_nodes.len(), 2);
}
#[test]
fn test_stitch_fractal_urns() {
let bindings = stitch_fractal_urns(
"urn:coreason:agent:orchestrator",
&["urn:coreason:tool:calculator", "urn:coreason:tool:search"],
);
assert_eq!(
bindings["urn:coreason:tool:calculator"],
"urn:coreason:agent:orchestrator:calculator"
);
assert_eq!(
bindings["urn:coreason:tool:search"],
"urn:coreason:agent:orchestrator:search"
);
}
#[test]
fn test_stitch_fractal_urns_no_colon() {
let bindings = stitch_fractal_urns("parent", &["child"]);
assert_eq!(bindings["child"], "parent:child");
}
#[test]
fn test_empty_dag() {
let dag: HashMap<String, SimulationNode> = HashMap::new();
let report = verify_dag_integrity(&dag);
assert!(report.valid);
assert_eq!(report.node_count, 0);
}
#[test]
fn test_single_node_dag() {
let mut dag = HashMap::new();
dag.insert("solo".into(), SimulationNode::new("solo", "compute"));
let mut dag_mut = dag.clone();
let result = simulate_execution(&mut dag_mut);
assert!(result.success);
assert_eq!(result.executed_nodes.len(), 1);
}
}