| use hyperkitty_core::Glyph; |
|
|
| |
| |
| #[repr(C)] |
| #[derive(Debug, Clone, Copy)] |
| pub struct RouteDecision { |
| |
| pub abi_version: u32, |
|
|
| |
| pub current_state: u8, |
| pub previous_state: u8, |
| pub next_state: u8, |
| pub padding_0: u8, |
|
|
| |
| pub accepted: u32, |
| pub failed_gate: u32, |
|
|
| |
| pub entropy: f64, |
| pub entropy_ok: u32, |
| pub padding_1: u32, |
|
|
| |
| pub reconciliation_ok: u32, |
| pub route_valid: u32, |
| pub invariant_preserved: u32, |
|
|
| |
| pub trace_id: u64, |
|
|
| |
| pub proof_ok: u32, |
| pub padding_2: u32, |
| } |
|
|
| impl RouteDecision { |
| pub const ABI_VERSION: u32 = 1; |
| pub const SIZE: usize = std::mem::size_of::<RouteDecision>(); |
|
|
| pub fn new( |
| current: Glyph, |
| previous: Glyph, |
| next: Glyph, |
| entropy: f64, |
| accepted: bool, |
| failed_gate: u32, |
| entropy_ok: bool, |
| reconciliation_ok: bool, |
| route_valid: bool, |
| invariant_ok: bool, |
| trace_id: u64, |
| proof_ok: bool, |
| ) -> Self { |
| RouteDecision { |
| abi_version: Self::ABI_VERSION, |
| current_state: current.to_byte(), |
| previous_state: previous.to_byte(), |
| next_state: next.to_byte(), |
| padding_0: 0, |
| accepted: if accepted { 1 } else { 0 }, |
| failed_gate, |
| entropy, |
| entropy_ok: if entropy_ok { 1 } else { 0 }, |
| padding_1: 0, |
| reconciliation_ok: if reconciliation_ok { 1 } else { 0 }, |
| route_valid: if route_valid { 1 } else { 0 }, |
| invariant_preserved: if invariant_ok { 1 } else { 0 }, |
| trace_id, |
| proof_ok: if proof_ok { 1 } else { 0 }, |
| padding_2: 0, |
| } |
| } |
|
|
| pub fn from_reconciliation( |
| decision: &crate::reconciliation::ReconciliationDecision, |
| ) -> Self { |
| let next = decision.next_state.unwrap_or(Glyph::Lambda); |
| let failed_gate_code = match decision.failed_gate { |
| None => 0, |
| Some(crate::validity::ValidityGate::Balance) => 1, |
| Some(crate::validity::ValidityGate::Invariant) => 2, |
| Some(crate::validity::ValidityGate::Entropy) => 3, |
| Some(crate::validity::ValidityGate::Proof) => 4, |
| Some(crate::validity::ValidityGate::Reconciliation) => 5, |
| Some(crate::validity::ValidityGate::Route) => 6, |
| }; |
|
|
| RouteDecision::new( |
| decision.current_state, |
| decision.previous_state, |
| next, |
| decision.entropy, |
| decision.accepted, |
| failed_gate_code, |
| decision.entropy <= crate::hyperkitty_core::MAX_ENTROPY, |
| decision.validity_decision.reconciliation_ok, |
| decision.route_valid, |
| decision.invariant_preserved, |
| decision.trace_id, |
| decision.validity_decision.proof_ok, |
| ) |
| } |
|
|
| |
| pub fn validate_header(&self) -> Result<(), String> { |
| if self.abi_version != Self::ABI_VERSION { |
| return Err(format!( |
| "ABI version mismatch: expected {}, got {}", |
| Self::ABI_VERSION, |
| self.abi_version |
| )); |
| } |
| Ok(()) |
| } |
|
|
| |
| pub fn validate_glyphs(&self) -> Result<(), String> { |
| if self.current_state > 5 { |
| return Err(format!("invalid current_state: {}", self.current_state)); |
| } |
| if self.previous_state > 5 { |
| return Err(format!("invalid previous_state: {}", self.previous_state)); |
| } |
| if self.next_state > 5 { |
| return Err(format!("invalid next_state: {}", self.next_state)); |
| } |
| Ok(()) |
| } |
|
|
| |
| pub fn validate_entropy(&self) -> Result<(), String> { |
| if !self.entropy.is_finite() { |
| return Err(format!("nonfinite entropy: {}", self.entropy)); |
| } |
| if self.entropy < 0.0 { |
| return Err(format!("negative entropy: {}", self.entropy)); |
| } |
| if self.entropy > crate::hyperkitty_core::MAX_ENTROPY { |
| return Err(format!("entropy exceeds bound: {}", self.entropy)); |
| } |
| Ok(()) |
| } |
|
|
| |
| pub fn validate_all(&self) -> Result<(), String> { |
| self.validate_header()?; |
| self.validate_glyphs()?; |
| self.validate_entropy()?; |
|
|
| |
| if self.accepted == 1 { |
| if self.entropy_ok == 0 { |
| return Err("accepted but entropy_ok=0".to_string()); |
| } |
| if self.reconciliation_ok == 0 { |
| return Err("accepted but reconciliation_ok=0".to_string()); |
| } |
| if self.route_valid == 0 { |
| return Err("accepted but route_valid=0".to_string()); |
| } |
| if self.proof_ok == 0 { |
| return Err("accepted but proof_ok=0".to_string()); |
| } |
| } |
|
|
| Ok(()) |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_route_decision_abi_size() { |
| |
| assert_eq!(RouteDecision::SIZE, 88); |
| } |
|
|
| #[test] |
| fn test_route_decision_construction() { |
| let decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
|
|
| assert_eq!(decision.abi_version, RouteDecision::ABI_VERSION); |
| assert_eq!(decision.accepted, 1); |
| assert_eq!(decision.entropy, 0.15); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_header_success() { |
| let decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| assert!(decision.validate_header().is_ok()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_header_fail() { |
| let mut decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| decision.abi_version = 999; |
| assert!(decision.validate_header().is_err()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_glyphs_success() { |
| let decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| assert!(decision.validate_glyphs().is_ok()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_glyphs_fail() { |
| let mut decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| decision.current_state = 99; |
| assert!(decision.validate_glyphs().is_err()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_entropy_success() { |
| let decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| assert!(decision.validate_entropy().is_ok()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_entropy_high() { |
| let mut decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| decision.entropy = 0.25; |
| assert!(decision.validate_entropy().is_err()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_entropy_nonfinite() { |
| let mut decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| decision.entropy = f64::NAN; |
| assert!(decision.validate_entropy().is_err()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_all_success() { |
| let decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| true, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| assert!(decision.validate_all().is_ok()); |
| } |
|
|
| #[test] |
| fn test_route_decision_validate_all_consistency_fail() { |
| let decision = RouteDecision::new( |
| Glyph::Pi, |
| Glyph::Gamma, |
| Glyph::Delta, |
| 0.15, |
| true, |
| 0, |
| false, |
| true, |
| true, |
| true, |
| 42, |
| true, |
| ); |
| assert!(decision.validate_all().is_err()); |
| } |
| } |
|
|