libertaria-stack/membrane-agent/src/policy_enforcer.rs

152 lines
4.9 KiB
Rust

//! 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<QvlClient>,
// 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<QvlClient>) -> 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<QvlClient>,
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<f64> {
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);
}
}