// SPDX-License-Identifier: LicenseRef-Sovereign-Leviathan-SL-AGPL3-001 // Licensing: see LICENSE in the repository root. /// ICP-DAG Governance Protocol Validator /// Validates agent communication against Integrity Constraint Protocol rules /// with Directed Acyclic Graph (DAG) consistency checking /// /// Rules: /// I2: DAG acyclicity - no cycles in agent dependency graph /// I3: Unknown claims cannot authorize - unknown state prevents decision /// I4: Contradicted claims cannot authorize - conflicting proofs invalidate /// I5: Execution needs authorized decision - exec→decision→proof→claim chain /// I9: Claim state consistency - no simultaneous unknown+verified /// I10: Reachability acyclicity - transitive dependencies must be acyclic use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ClaimState { Unknown, Verified, Contradicted, Pending, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Claim { pub id: String, pub agent: String, pub content: String, pub state: ClaimState, pub timestamp: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Proof { pub claim_id: String, pub proof_chain: Vec, // chain of claims this proof depends on pub verifier_agent: String, pub is_valid: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Decision { pub decision_id: String, pub agent: String, pub supporting_proofs: Vec, pub conflicting_proofs: Vec, pub authorized: bool, pub timestamp: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Execution { pub exec_id: String, pub agent: String, pub decision_id: String, pub executed: bool, pub timestamp: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentMessage { pub sender: String, pub receiver: String, pub claim: Claim, pub message_id: String, pub depends_on: Vec, // message IDs this depends on } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProtocolViolation { pub violation_type: String, pub description: String, pub affected_entity: String, pub rule_id: String, } #[derive(Debug, Serialize, Deserialize)] pub struct ValidationReport { pub violations: Vec, pub is_valid: bool, pub dag_structure: DAGStructure, pub claim_analysis: ClaimAnalysis, pub execution_chains: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct DAGStructure { pub nodes: Vec, pub edges: Vec<(String, String)>, pub is_acyclic: bool, pub cycles: Vec>, pub topological_sort: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct ClaimAnalysis { pub total_claims: usize, pub unknown_claims: usize, pub verified_claims: usize, pub contradicted_claims: usize, pub state_inconsistencies: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct ExecutionChain { pub chain_id: String, pub execution_id: String, pub decision_id: String, pub proofs: Vec, pub claims: Vec, pub is_valid_chain: bool, } pub struct ICPDAGValidator { claims: HashMap, proofs: HashMap, decisions: HashMap, executions: HashMap, messages: Vec, violations: Vec, } impl ICPDAGValidator { pub fn new() -> Self { ICPDAGValidator { claims: HashMap::new(), proofs: HashMap::new(), decisions: HashMap::new(), executions: HashMap::new(), messages: Vec::new(), violations: Vec::new(), } } /// Register a claim in the validation system pub fn register_claim(&mut self, claim: Claim) { self.claims.insert(claim.id.clone(), claim); } /// Register a proof linking claims pub fn register_proof(&mut self, proof: Proof) { self.proofs.insert(proof.claim_id.clone(), proof); } /// Register a decision with supporting/conflicting proofs pub fn register_decision(&mut self, decision: Decision) { self.decisions.insert(decision.decision_id.clone(), decision); } /// Register an execution of a decision pub fn register_execution(&mut self, execution: Execution) { self.executions.insert(execution.exec_id.clone(), execution); } /// Register agent-to-agent communication messages pub fn register_message(&mut self, message: AgentMessage) { self.messages.push(message); } /// Rule I3: Unknown claims cannot authorize fn validate_i3_unknown_claims(&mut self) { for (decision_id, decision) in &self.decisions { for proof_id in &decision.supporting_proofs { if let Some(proof) = self.proofs.get(proof_id) { for claim_id in &proof.proof_chain { if let Some(claim) = self.claims.get(claim_id) { if claim.state == ClaimState::Unknown { self.violations.push(ProtocolViolation { violation_type: "I3_VIOLATION".to_string(), description: format!( "Unknown claim {} used in proof chain for decision {}", claim_id, decision_id ), affected_entity: claim_id.clone(), rule_id: "I3".to_string(), }); } } } } } } } /// Rule I4: Contradicted claims cannot authorize fn validate_i4_contradicted_claims(&mut self) { for (decision_id, decision) in &self.decisions { // Check if supporting proofs depend on contradicted claims for proof_id in &decision.supporting_proofs { if let Some(proof) = self.proofs.get(proof_id) { for claim_id in &proof.proof_chain { if let Some(claim) = self.claims.get(claim_id) { if claim.state == ClaimState::Contradicted { self.violations.push(ProtocolViolation { violation_type: "I4_VIOLATION".to_string(), description: format!( "Contradicted claim {} used in proof for decision {}", claim_id, decision_id ), affected_entity: claim_id.clone(), rule_id: "I4".to_string(), }); } } } } } // Check for conflicting proofs if !decision.conflicting_proofs.is_empty() && !decision.supporting_proofs.is_empty() { self.violations.push(ProtocolViolation { violation_type: "I4_VIOLATION".to_string(), description: format!( "Decision {} has both supporting and conflicting proofs", decision_id ), affected_entity: decision_id.clone(), rule_id: "I4".to_string(), }); } } } /// Rule I5: Execution needs authorized decision fn validate_i5_execution_chain(&mut self) { for (exec_id, execution) in &self.executions { if !execution.executed { return; // Skip unexecuted } // Trace: execution → decision → proof → claim if let Some(decision) = self.decisions.get(&execution.decision_id) { if !decision.authorized { self.violations.push(ProtocolViolation { violation_type: "I5_VIOLATION".to_string(), description: format!( "Execution {} uses unauthorized decision {}", exec_id, &execution.decision_id ), affected_entity: exec_id.clone(), rule_id: "I5".to_string(), }); continue; } // Verify proofs exist and are valid for proof_id in &decision.supporting_proofs { if let Some(proof) = self.proofs.get(proof_id) { if !proof.is_valid { self.violations.push(ProtocolViolation { violation_type: "I5_VIOLATION".to_string(), description: format!( "Execution {} depends on invalid proof {}", exec_id, proof_id ), affected_entity: exec_id.clone(), rule_id: "I5".to_string(), }); } } } } } } /// Rule I9: Claim state consistency fn validate_i9_claim_consistency(&mut self) -> ClaimAnalysis { let mut analysis = ClaimAnalysis { total_claims: self.claims.len(), unknown_claims: 0, verified_claims: 0, contradicted_claims: 0, state_inconsistencies: Vec::new(), }; let mut claim_state_map: HashMap> = HashMap::new(); for (claim_id, claim) in &self.claims { match claim.state { ClaimState::Unknown => analysis.unknown_claims += 1, ClaimState::Verified => analysis.verified_claims += 1, ClaimState::Contradicted => analysis.contradicted_claims += 1, ClaimState::Pending => {} } claim_state_map .entry(claim_id.clone()) .or_insert_with(HashSet::new) .insert(claim.state.clone()); } // Check for simultaneous unknown+verified for (claim_id, states) in &claim_state_map { if states.contains(&ClaimState::Unknown) && states.contains(&ClaimState::Verified) { analysis.state_inconsistencies.push(claim_id.clone()); self.violations.push(ProtocolViolation { violation_type: "I9_VIOLATION".to_string(), description: format!( "Claim {} has simultaneous unknown and verified states", claim_id ), affected_entity: claim_id.clone(), rule_id: "I9".to_string(), }); } // Cannot be both verified and contradicted if states.contains(&ClaimState::Verified) && states.contains(&ClaimState::Contradicted) { analysis.state_inconsistencies.push(claim_id.clone()); self.violations.push(ProtocolViolation { violation_type: "I9_VIOLATION".to_string(), description: format!( "Claim {} has simultaneous verified and contradicted states", claim_id ), affected_entity: claim_id.clone(), rule_id: "I9".to_string(), }); } } analysis } /// Rules I2 & I10: Acyclicity detection using DFS fn validate_acyclicity(&self) -> DAGStructure { let mut dag_structure = DAGStructure { nodes: Vec::new(), edges: Vec::new(), is_acyclic: true, cycles: Vec::new(), topological_sort: Vec::new(), }; // Build dependency graph from messages let mut graph: HashMap> = HashMap::new(); let mut all_agents: HashSet = HashSet::new(); for message in &self.messages { all_agents.insert(message.sender.clone()); all_agents.insert(message.receiver.clone()); graph .entry(message.sender.clone()) .or_insert_with(Vec::new) .push(message.receiver.clone()); // Add dependencies for dep_id in &message.depends_on { if let Some(dep_msg) = self.messages.iter().find(|m| &m.message_id == dep_id) { graph .entry(dep_msg.sender.clone()) .or_insert_with(Vec::new) .push(message.sender.clone()); } } } dag_structure.nodes = all_agents.iter().cloned().collect(); for (from, tos) in &graph { for to in tos { dag_structure.edges.push((from.clone(), to.clone())); } } // Detect cycles using DFS let cycles = self.detect_cycles(&graph); dag_structure.is_acyclic = cycles.is_empty(); dag_structure.cycles = cycles; // Topological sort if acyclic if dag_structure.is_acyclic { dag_structure.topological_sort = self.topological_sort(&graph); } dag_structure } /// Detect cycles using depth-first search fn detect_cycles(&self, graph: &HashMap>) -> Vec> { let mut visited: HashMap = HashMap::new(); let mut cycles: Vec> = Vec::new(); let mut path: Vec = Vec::new(); for node in graph.keys() { if !visited.contains_key(node) { self.dfs_cycle(node, graph, &mut visited, &mut path, &mut cycles); } } cycles } /// DFS helper for cycle detection fn dfs_cycle( &self, node: &str, graph: &HashMap>, visited: &mut HashMap, path: &mut Vec, cycles: &mut Vec>, ) { visited.insert(node.to_string(), 0); // 0 = visiting path.push(node.to_string()); if let Some(neighbors) = graph.get(node) { for neighbor in neighbors { match visited.get(neighbor).copied() { None => { self.dfs_cycle(neighbor, graph, visited, path, cycles); } Some(0) => { // Back edge found - cycle detected if let Some(cycle_start) = path.iter().position(|n| n == neighbor) { let cycle = path[cycle_start..].to_vec(); cycles.push(cycle); } } _ => {} // Already visited } } } path.pop(); visited.insert(node.to_string(), 1); // 1 = done } /// Topological sort using Kahn's algorithm fn topological_sort(&self, graph: &HashMap>) -> Vec { let mut in_degree: HashMap = HashMap::new(); let mut all_nodes: HashSet = HashSet::new(); // Initialize in-degrees for (from, tos) in graph { all_nodes.insert(from.clone()); in_degree.entry(from.clone()).or_insert(0); for to in tos { all_nodes.insert(to.clone()); *in_degree.entry(to.clone()).or_insert(0) += 1; } } let mut queue: VecDeque = VecDeque::new(); for node in &all_nodes { if in_degree.get(node).copied().unwrap_or(0) == 0 { queue.push_back(node.clone()); } } let mut result: Vec = Vec::new(); while let Some(node) = queue.pop_front() { result.push(node.clone()); if let Some(neighbors) = graph.get(&node) { for neighbor in neighbors { let degree = in_degree.entry(neighbor.clone()).or_insert(0); *degree -= 1; if *degree == 0 { queue.push_back(neighbor.clone()); } } } } result } /// Build execution chains: execution → decision → proofs → claims fn build_execution_chains(&self) -> Vec { let mut chains: Vec = Vec::new(); for (exec_id, execution) in &self.executions { let mut chain = ExecutionChain { chain_id: format!("chain_{}", exec_id), execution_id: exec_id.clone(), decision_id: execution.decision_id.clone(), proofs: Vec::new(), claims: Vec::new(), is_valid_chain: true, }; if let Some(decision) = self.decisions.get(&execution.decision_id) { chain.proofs = decision.supporting_proofs.clone(); for proof_id in &chain.proofs { if let Some(proof) = self.proofs.get(proof_id) { for claim_id in &proof.proof_chain { chain.claims.push(claim_id.clone()); } } } // Validate chain chain.is_valid_chain = decision.authorized && chain.proofs.iter().all(|p| { self.proofs .get(p) .map(|proof| proof.is_valid) .unwrap_or(false) }) && chain.claims.iter().all(|c| { self.claims .get(c) .map(|claim| claim.state == ClaimState::Verified) .unwrap_or(false) }); } chains.push(chain); } chains } /// Perform complete validation pub fn validate(&mut self) -> ValidationReport { self.violations.clear(); // Rule validations self.validate_i3_unknown_claims(); self.validate_i4_contradicted_claims(); self.validate_i5_execution_chain(); let claim_analysis = self.validate_i9_claim_consistency(); let dag_structure = self.validate_acyclicity(); // Check acyclicity rules if !dag_structure.is_acyclic { for cycle in &dag_structure.cycles { self.violations.push(ProtocolViolation { violation_type: "I2_I10_VIOLATION".to_string(), description: format!("Cycle detected in DAG: {:?}", cycle), affected_entity: cycle.join(" -> "), rule_id: "I2_I10".to_string(), }); } } let execution_chains = self.build_execution_chains(); let is_valid = self.violations.is_empty(); ValidationReport { violations: self.violations.clone(), is_valid, dag_structure, claim_analysis, execution_chains, } } } impl fmt::Display for ValidationReport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "=== ICP-DAG Governance Protocol Validation Report ===")?; writeln!(f)?; writeln!(f, "Overall Status: {}", if self.is_valid { "VALID" } else { "INVALID" })?; writeln!(f, "Total Violations: {}", self.violations.len())?; writeln!(f)?; if !self.violations.is_empty() { writeln!(f, "--- Protocol Violations ---")?; for (idx, violation) in self.violations.iter().enumerate() { writeln!(f, "{}. [{}] {}", idx + 1, violation.rule_id, violation.violation_type)?; writeln!(f, " Entity: {}", violation.affected_entity)?; writeln!(f, " Desc: {}", violation.description)?; } writeln!(f)?; } writeln!(f, "--- Claim Analysis ---")?; writeln!(f, "Total Claims: {}", self.claim_analysis.total_claims)?; writeln!(f, "Verified: {}", self.claim_analysis.verified_claims)?; writeln!(f, "Unknown: {}", self.claim_analysis.unknown_claims)?; writeln!(f, "Contradicted: {}", self.claim_analysis.contradicted_claims)?; writeln!(f, "State Inconsistencies: {}", self.claim_analysis.state_inconsistencies.len())?; writeln!(f)?; writeln!(f, "--- DAG Structure ---")?; writeln!(f, "Nodes: {}", self.dag_structure.nodes.len())?; writeln!(f, "Edges: {}", self.dag_structure.edges.len())?; writeln!(f, "Acyclic: {}", self.dag_structure.is_acyclic)?; if !self.dag_structure.cycles.is_empty() { writeln!(f, "Cycles Found: {}", self.dag_structure.cycles.len())?; } writeln!(f)?; writeln!(f, "--- Execution Chains ---")?; writeln!(f, "Total Chains: {}", self.execution_chains.len())?; for chain in &self.execution_chains { writeln!(f, " {}: {} proofs, {} claims, Valid: {}", chain.chain_id, chain.proofs.len(), chain.claims.len(), chain.is_valid_chain)?; } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_i3_unknown_claims() { let mut validator = ICPDAGValidator::new(); let unknown_claim = Claim { id: "claim_1".to_string(), agent: "agent_a".to_string(), content: "test".to_string(), state: ClaimState::Unknown, timestamp: 0, }; validator.register_claim(unknown_claim); let proof = Proof { claim_id: "proof_1".to_string(), proof_chain: vec!["claim_1".to_string()], verifier_agent: "agent_b".to_string(), is_valid: true, }; validator.register_proof(proof); let decision = Decision { decision_id: "dec_1".to_string(), agent: "agent_c".to_string(), supporting_proofs: vec!["proof_1".to_string()], conflicting_proofs: vec![], authorized: true, timestamp: 0, }; validator.register_decision(decision); let report = validator.validate(); assert!(!report.is_valid); assert!(report.violations.iter().any(|v| v.rule_id == "I3")); } #[test] fn test_i9_state_consistency() { let mut validator = ICPDAGValidator::new(); let claim = Claim { id: "claim_1".to_string(), agent: "agent_a".to_string(), content: "test".to_string(), state: ClaimState::Verified, timestamp: 0, }; validator.register_claim(claim); let report = validator.validate(); assert!(report.is_valid); assert_eq!(report.claim_analysis.verified_claims, 1); } } fn main() { let mut validator = ICPDAGValidator::new(); // Example: Register verified claim let verified_claim = Claim { id: "claim_verified_1".to_string(), agent: "agent_governance".to_string(), content: "Governance protocol v1.0 active".to_string(), state: ClaimState::Verified, timestamp: 1000, }; validator.register_claim(verified_claim); // Example: Register proof let proof = Proof { claim_id: "proof_1".to_string(), proof_chain: vec!["claim_verified_1".to_string()], verifier_agent: "agent_validator".to_string(), is_valid: true, }; validator.register_proof(proof); // Example: Register authorized decision let decision = Decision { decision_id: "decision_1".to_string(), agent: "agent_consensus".to_string(), supporting_proofs: vec!["proof_1".to_string()], conflicting_proofs: vec![], authorized: true, timestamp: 1001, }; validator.register_decision(decision); // Example: Register execution let execution = Execution { exec_id: "exec_1".to_string(), agent: "agent_executor".to_string(), decision_id: "decision_1".to_string(), executed: true, timestamp: 1002, }; validator.register_execution(execution); // Validate let report = validator.validate(); println!("{}", report); println!("\nDAG as JSON:"); println!("{}", serde_json::to_string_pretty(&report.dag_structure).unwrap()); }