//! Policy Enforcer - Trust-based routing and access control //! //! Queries QVL for trust scores and makes policy decisions. use crate::qvl_ffi::{QvlClient, QvlError}; use std::sync::Arc; /// Policy decision for packet handling #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PolicyDecision { /// Accept packet for normal processing/relay Accept, /// Deprioritize packet (low-priority queue) Deprioritize, /// Drop packet silently Drop, /// Treat as neutral (no trust data available) Neutral, } /// Trust-based policy enforcer pub struct PolicyEnforcer { qvl: Arc, // Policy thresholds drop_threshold: f64, // Below this: drop untrusted_threshold: f64, // Below this: deprioritize } impl PolicyEnforcer { /// Create new policy enforcer pub fn new(qvl: Arc) -> Self { Self { qvl, drop_threshold: 0.1, // Drop if trust < 0.1 untrusted_threshold: 0.5, // Deprioritize if trust < 0.5 } } /// Create with custom thresholds pub fn with_thresholds( qvl: Arc, drop_threshold: f64, untrusted_threshold: f64, ) -> Self { Self { qvl, drop_threshold, untrusted_threshold, } } /// Decide whether to accept a packet from a DID pub fn should_accept_packet(&self, sender_did: &[u8; 32]) -> PolicyDecision { match self.qvl.get_trust_score(sender_did) { Ok(score) if score < self.drop_threshold => PolicyDecision::Drop, Ok(score) if score < self.untrusted_threshold => PolicyDecision::Deprioritize, Ok(_) => PolicyDecision::Accept, Err(QvlError::TrustScoreFailed) | Err(QvlError::InvalidDid) => PolicyDecision::Neutral, Err(_) => PolicyDecision::Neutral, } } /// Check if a node should be flagged for betrayal pub fn check_for_betrayal(&self, node_id: u32) -> Option { match self.qvl.detect_betrayal(node_id) { Ok(anomaly) if anomaly.score > 0.7 => Some(anomaly.score), _ => None, } } /// Batch check multiple nodes for betrayal pub fn batch_check_betrayal(&self, node_ids: &[u32]) -> Vec<(u32, f64)> { node_ids .iter() .filter_map(|&node_id| { self.check_for_betrayal(node_id) .map(|score| (node_id, score)) }) .collect() } /// Check a node for betrayal and issue slash signal if guilty /// Returns the signed SlashSignal bytes if a punishment was issued pub fn punish_if_guilty(&self, node_id: u32) -> Option<[u8; 82]> { match self.qvl.detect_betrayal(node_id) { Ok(anomaly) if anomaly.score > 0.9 => { // High confidence betrayal if let Some(did) = self.qvl.get_did(anomaly.node) { // 1. Get Evidence let evidence_hash = if let Ok(evidence) = self.qvl.get_betrayal_evidence(anomaly.node) { // TODO: Calculate real Blake3 hash of evidence. For now use first 32 bytes or dummy. let mut hash = [0xEEu8; 32]; if evidence.len() >= 32 { hash.copy_from_slice(&evidence[0..32]); } hash } else { [0u8; 32] }; // 2. Issue slash if let Ok(signal) = self.qvl.issue_slash_signal(&did, anomaly.reason as u8, &evidence_hash) { return Some(signal); } } None }, _ => None } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_policy_enforcer_neutral() { let qvl = Arc::new(QvlClient::new().unwrap()); let enforcer = PolicyEnforcer::new(qvl); let unknown_did = [0u8; 32]; let decision = enforcer.should_accept_packet(&unknown_did); // Unknown DIDs should be treated as Accept (neutral trust default) assert_eq!(decision, PolicyDecision::Accept); } #[test] fn test_betrayal_check_clean_graph() { let qvl = Arc::new(QvlClient::new().unwrap()); let enforcer = PolicyEnforcer::new(qvl); // Empty graph should have no betrayal let result = enforcer.check_for_betrayal(0); assert_eq!(result, None); } #[test] fn test_batch_check() { let qvl = Arc::new(QvlClient::new().unwrap()); let enforcer = PolicyEnforcer::new(qvl); let nodes = vec![0, 1, 2, 3, 4]; let betrayals = enforcer.batch_check_betrayal(&nodes); // Clean graph should have no betrayals assert_eq!(betrayals.len(), 0); } }