Files
wifi-densepose/vendor/ruvector/crates/rvf/rvf-federation/src/policy.rs

194 lines
5.8 KiB
Rust

//! Federation policy for selective sharing.
//!
//! Controls what learning is exported, quality gates, rate limits,
//! and privacy budget constraints.
use std::collections::HashSet;
/// Controls what a user shares in federated exports.
#[derive(Clone, Debug)]
pub struct FederationPolicy {
/// Segment types allowed for export (empty = all allowed).
pub allowed_segments: HashSet<u8>,
/// Segment types explicitly denied for export.
pub denied_segments: HashSet<u8>,
/// Domain IDs allowed for export (empty = all allowed).
pub allowed_domains: HashSet<String>,
/// Domain IDs denied for export.
pub denied_domains: HashSet<String>,
/// Minimum quality score for exported trajectories (0.0 - 1.0).
pub quality_threshold: f64,
/// Minimum observations per prior entry for export.
pub min_observations: u64,
/// Maximum exports per hour.
pub max_exports_per_hour: u32,
/// Maximum cumulative privacy budget (epsilon).
pub privacy_budget_limit: f64,
/// Whether to include policy kernel snapshots.
pub export_kernels: bool,
/// Whether to include cost curve data.
pub export_cost_curves: bool,
}
impl Default for FederationPolicy {
fn default() -> Self {
Self {
allowed_segments: HashSet::new(),
denied_segments: HashSet::new(),
allowed_domains: HashSet::new(),
denied_domains: HashSet::new(),
quality_threshold: 0.5,
min_observations: 12,
max_exports_per_hour: 100,
privacy_budget_limit: 10.0,
export_kernels: true,
export_cost_curves: true,
}
}
}
impl FederationPolicy {
/// Create a restrictive policy (deny all by default).
pub fn restrictive() -> Self {
Self {
quality_threshold: 0.8,
min_observations: 50,
max_exports_per_hour: 10,
privacy_budget_limit: 5.0,
export_kernels: false,
export_cost_curves: false,
..Default::default()
}
}
/// Create a permissive policy (share everything).
pub fn permissive() -> Self {
Self {
quality_threshold: 0.0,
min_observations: 1,
max_exports_per_hour: 1000,
privacy_budget_limit: 100.0,
export_kernels: true,
export_cost_curves: true,
..Default::default()
}
}
/// Check if a segment type is allowed for export.
pub fn is_segment_allowed(&self, seg_type: u8) -> bool {
if self.denied_segments.contains(&seg_type) {
return false;
}
if self.allowed_segments.is_empty() {
return true;
}
self.allowed_segments.contains(&seg_type)
}
/// Check if a domain is allowed for export.
pub fn is_domain_allowed(&self, domain_id: &str) -> bool {
if self.denied_domains.contains(domain_id) {
return false;
}
if self.allowed_domains.is_empty() {
return true;
}
self.allowed_domains.contains(domain_id)
}
/// Allow a specific segment type.
pub fn allow_segment(mut self, seg_type: u8) -> Self {
self.allowed_segments.insert(seg_type);
self
}
/// Deny a specific segment type.
pub fn deny_segment(mut self, seg_type: u8) -> Self {
self.denied_segments.insert(seg_type);
self
}
/// Allow a specific domain.
pub fn allow_domain(mut self, domain_id: &str) -> Self {
self.allowed_domains.insert(domain_id.to_string());
self
}
/// Deny a specific domain.
pub fn deny_domain(mut self, domain_id: &str) -> Self {
self.denied_domains.insert(domain_id.to_string());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_policy() {
let p = FederationPolicy::default();
assert_eq!(p.quality_threshold, 0.5);
assert_eq!(p.min_observations, 12);
assert!(p.is_segment_allowed(0x33));
assert!(p.is_domain_allowed("anything"));
}
#[test]
fn restrictive_policy() {
let p = FederationPolicy::restrictive();
assert_eq!(p.quality_threshold, 0.8);
assert_eq!(p.min_observations, 50);
assert!(!p.export_kernels);
assert!(!p.export_cost_curves);
}
#[test]
fn permissive_policy() {
let p = FederationPolicy::permissive();
assert_eq!(p.quality_threshold, 0.0);
assert_eq!(p.min_observations, 1);
}
#[test]
fn segment_allowlist() {
let p = FederationPolicy::default().allow_segment(0x33).allow_segment(0x34);
assert!(p.is_segment_allowed(0x33));
assert!(p.is_segment_allowed(0x34));
assert!(!p.is_segment_allowed(0x35)); // not in allowlist
}
#[test]
fn segment_denylist() {
let p = FederationPolicy::default().deny_segment(0x36);
assert!(p.is_segment_allowed(0x33));
assert!(!p.is_segment_allowed(0x36)); // denied
}
#[test]
fn deny_takes_precedence() {
let p = FederationPolicy::default()
.allow_segment(0x33)
.deny_segment(0x33);
assert!(!p.is_segment_allowed(0x33)); // deny wins
}
#[test]
fn domain_filtering() {
let p = FederationPolicy::default()
.allow_domain("genomics")
.deny_domain("secret_project");
assert!(p.is_domain_allowed("genomics"));
assert!(!p.is_domain_allowed("secret_project"));
assert!(!p.is_domain_allowed("trading")); // not in allowlist
}
#[test]
fn empty_allowlist_allows_all() {
let p = FederationPolicy::default();
assert!(p.is_segment_allowed(0x33));
assert!(p.is_segment_allowed(0xFF));
assert!(p.is_domain_allowed("any_domain"));
}
}