use std::collections::HashSet;
use std::fmt;
use std::time::SystemTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DataClassification {
Public,
Internal,
Confidential,
Sovereign,
}
impl fmt::Display for DataClassification {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Public => write!(f, "PUBLIC"),
Self::Internal => write!(f, "INTERNAL"),
Self::Confidential => write!(f, "CONFIDENTIAL"),
Self::Sovereign => write!(f, "SOVEREIGN"),
}
}
}
#[derive(Debug, Clone)]
pub struct ResidencyConfig {
pub allowed_regions: Vec<String>,
pub network_isolation: bool,
pub enforce_at_runtime: bool,
}
impl Default for ResidencyConfig {
fn default() -> Self {
Self {
allowed_regions: vec!["local".to_string()],
network_isolation: true,
enforce_at_runtime: true,
}
}
}
impl ResidencyConfig {
pub fn sovereign_local() -> Self {
Self {
allowed_regions: vec!["local".to_string()],
network_isolation: true,
enforce_at_runtime: true,
}
}
pub fn is_region_allowed(&self, region: &str) -> bool {
self.allowed_regions.iter().any(|r| r == region)
}
}
#[derive(Debug, Clone)]
pub struct ApiAllowlist {
pub allowed_endpoints: HashSet<String>,
pub offline_mode: bool,
}
impl Default for ApiAllowlist {
fn default() -> Self {
Self { allowed_endpoints: HashSet::new(), offline_mode: true }
}
}
impl ApiAllowlist {
pub fn is_allowed(&self, endpoint: &str) -> bool {
if self.offline_mode {
return false;
}
self.allowed_endpoints.contains(endpoint)
}
pub fn offline() -> Self {
Self { allowed_endpoints: HashSet::new(), offline_mode: true }
}
}
#[derive(Debug, Clone)]
pub struct AuditEntry {
pub sequence: u64,
pub timestamp: SystemTime,
pub event_type: String,
pub data: String,
pub classification: DataClassification,
pub prev_hash: String,
pub hash: String,
}
#[derive(Debug)]
pub struct AuditTrail {
entries: Vec<AuditEntry>,
next_sequence: u64,
}
impl AuditTrail {
pub fn new() -> Self {
Self { entries: Vec::new(), next_sequence: 0 }
}
pub fn append(
&mut self,
event_type: &str,
data: &str,
classification: DataClassification,
) -> &AuditEntry {
let prev_hash =
self.entries.last().map_or_else(|| "genesis".to_string(), |e| e.hash.clone());
let sequence = self.next_sequence;
self.next_sequence += 1;
let hash_input = format!("{sequence}:{event_type}:{data}:{prev_hash}");
let hash = format!("{:x}", simple_hash(hash_input.as_bytes()));
let entry = AuditEntry {
sequence,
timestamp: SystemTime::now(),
event_type: event_type.to_string(),
data: data.to_string(),
classification,
prev_hash,
hash,
};
self.entries.push(entry);
self.entries.last().unwrap()
}
pub fn verify_integrity(&self) -> bool {
for (i, entry) in self.entries.iter().enumerate() {
if i == 0 {
if entry.prev_hash != "genesis" {
return false;
}
} else if entry.prev_hash != self.entries[i - 1].hash {
return false;
}
let hash_input = format!(
"{}:{}:{}:{}",
entry.sequence, entry.event_type, entry.data, entry.prev_hash
);
let expected_hash = format!("{:x}", simple_hash(hash_input.as_bytes()));
if entry.hash != expected_hash {
return false;
}
}
true
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn entries(&self) -> &[AuditEntry] {
&self.entries
}
}
impl Default for AuditTrail {
fn default() -> Self {
Self::new()
}
}
fn simple_hash(data: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &byte in data {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
#[derive(Debug, Clone)]
pub struct WeightSovereigntyConfig {
pub encryption_required: bool,
pub access_control: bool,
pub key_source: KeySource,
}
#[derive(Debug, Clone)]
pub enum KeySource {
File(String),
EnvVar(String),
None,
}
impl Default for WeightSovereigntyConfig {
fn default() -> Self {
Self {
encryption_required: false,
access_control: true,
key_source: KeySource::EnvVar("ALBOR_ENCRYPT_KEY".to_string()),
}
}
}
pub fn inherit_classification(
input_class: DataClassification,
_model_class: DataClassification,
) -> DataClassification {
input_class
}
#[derive(Debug)]
pub struct DeletionCascade {
pub targets: Vec<String>,
pub requires_unlearning: bool,
}
impl DeletionCascade {
pub fn full(targets: Vec<String>) -> Self {
Self { targets, requires_unlearning: true }
}
pub fn plan(&self) -> Vec<String> {
let mut actions = Vec::new();
for target in &self.targets {
actions.push(format!("DELETE data from {target}"));
}
if self.requires_unlearning {
actions.push("TRIGGER model unlearning procedure".to_string());
}
actions
}
}
#[derive(Debug)]
pub struct SecureAggregator {
pub num_clients: usize,
pub encrypted: bool,
}
impl SecureAggregator {
pub fn new(num_clients: usize) -> Self {
Self { num_clients, encrypted: true }
}
pub fn aggregate(&self, _encrypted_gradient: &[Vec<f32>]) -> Vec<f32> {
Vec::new()
}
}
pub fn check_classification(
data_class: DataClassification,
required_level: DataClassification,
) -> bool {
validate_access_level(data_class, required_level)
}
pub fn enforce_tier(data_class: DataClassification) -> bool {
matches!(
data_class,
DataClassification::Public
| DataClassification::Internal
| DataClassification::Confidential
| DataClassification::Sovereign
)
}
pub fn validate_access_level(actual: DataClassification, required: DataClassification) -> bool {
let level = |c: &DataClassification| match c {
DataClassification::Public => 0,
DataClassification::Internal => 1,
DataClassification::Confidential => 2,
DataClassification::Sovereign => 3,
};
level(&actual) >= level(&required)
}
#[derive(Debug, Clone)]
pub struct ConsentRecord {
pub purpose_id: String,
pub usage_scope: String,
pub purpose_limitation: bool,
pub consent_scope: String,
}
#[derive(Debug)]
pub struct DataUsageAgreement {
pub records: Vec<ConsentRecord>,
}
impl DataUsageAgreement {
pub fn new() -> Self {
Self { records: Vec::new() }
}
pub fn add_consent(&mut self, record: ConsentRecord) {
self.records.push(record);
}
pub fn has_consent(&self, purpose_id: &str) -> bool {
self.records.iter().any(|r| r.purpose_id == purpose_id)
}
}
impl Default for DataUsageAgreement {
fn default() -> Self {
Self::new()
}
}
pub fn delete_user(user_id: &str, cascade: &DeletionCascade) -> Vec<String> {
let mut actions = Vec::new();
actions.push(format!("erasure: removing data for user {user_id}"));
actions.push(format!("rtbf: right to be forgotten for {user_id}"));
actions.extend(cascade_delete(cascade));
actions
}
pub fn cascade_delete(cascade: &DeletionCascade) -> Vec<String> {
cascade.plan()
}
#[derive(Debug)]
pub struct TransferLog {
entries: Vec<TransferLogEntry>,
}
#[derive(Debug)]
pub struct TransferLogEntry {
pub from: String,
pub to: String,
pub legal_basis: String,
pub transfer_agreement: String,
}
impl TransferLog {
pub fn new() -> Self {
Self { entries: Vec::new() }
}
pub fn log_transfer(&mut self, from: &str, to: &str, legal_basis: &str) {
self.entries.push(TransferLogEntry {
from: from.to_string(),
to: to.to_string(),
legal_basis: legal_basis.to_string(),
transfer_agreement: format!("adequacy_decision:{from}->{to}"),
});
}
pub fn entries(&self) -> &[TransferLogEntry] {
&self.entries
}
}
impl Default for TransferLog {
fn default() -> Self {
Self::new()
}
}
pub fn weight_access(model_acl: &[String], requester: &str) -> bool {
model_acl.iter().any(|allowed| allowed == requester)
}
pub fn encrypt_weights(weights: &[f32], _key: &[u8]) -> Vec<u8> {
let bytes: Vec<u8> = weights.iter().flat_map(|w| w.to_le_bytes()).collect();
bytes
}
#[derive(Debug)]
pub struct KeyManagement {
pub key_rotation_interval: u64,
pub kms_provider: String,
}
impl Default for KeyManagement {
fn default() -> Self {
Self { key_rotation_interval: 86400, kms_provider: "local".to_string() }
}
}
#[derive(Debug)]
pub struct DataLineage {
pub steps: Vec<LineageStep>,
}
#[derive(Debug)]
pub struct LineageStep {
pub id: String,
pub input: String,
pub output: String,
pub transform: String,
}
impl DataLineage {
pub fn new() -> Self {
Self { steps: Vec::new() }
}
pub fn add_step(&mut self, id: &str, input: &str, output: &str, transform: &str) {
self.steps.push(LineageStep {
id: id.to_string(),
input: input.to_string(),
output: output.to_string(),
transform: transform.to_string(),
});
}
}
impl Default for DataLineage {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_residency_config_sovereign_local() {
let config = ResidencyConfig::sovereign_local();
assert!(config.is_region_allowed("local"));
assert!(!config.is_region_allowed("us-east-1"));
assert!(config.network_isolation);
}
#[test]
fn test_api_allowlist_offline() {
let allowlist = ApiAllowlist::offline();
assert!(!allowlist.is_allowed("https://api.example.com"));
assert!(!allowlist.is_allowed("localhost"));
assert!(allowlist.offline_mode);
}
#[test]
fn test_audit_trail_append_only() {
let mut trail = AuditTrail::new();
trail.append("training_start", "model=350M", DataClassification::Internal);
trail.append("checkpoint_save", "step=100", DataClassification::Sovereign);
trail.append("training_end", "loss=5.92", DataClassification::Internal);
assert_eq!(trail.len(), 3);
assert!(trail.verify_integrity());
}
#[test]
fn test_audit_trail_tamper_detection() {
let mut trail = AuditTrail::new();
trail.append("event_1", "data_1", DataClassification::Public);
trail.append("event_2", "data_2", DataClassification::Public);
assert!(trail.verify_integrity());
if let Some(entry) = trail.entries.first_mut() {
entry.data = "tampered".to_string();
}
assert!(!trail.verify_integrity());
}
#[test]
fn test_audit_trail_hash_chain() {
let mut trail = AuditTrail::new();
trail.append("a", "1", DataClassification::Public);
trail.append("b", "2", DataClassification::Public);
trail.append("c", "3", DataClassification::Public);
assert_eq!(trail.entries()[0].prev_hash, "genesis");
assert_eq!(trail.entries()[1].prev_hash, trail.entries()[0].hash);
assert_eq!(trail.entries()[2].prev_hash, trail.entries()[1].hash);
}
#[test]
fn test_data_classification_display() {
assert_eq!(DataClassification::Sovereign.to_string(), "SOVEREIGN");
assert_eq!(DataClassification::Public.to_string(), "PUBLIC");
}
#[test]
fn test_classification_inheritance() {
let result =
inherit_classification(DataClassification::Sovereign, DataClassification::Internal);
assert_eq!(result, DataClassification::Sovereign);
}
#[test]
fn test_deletion_cascade_plan() {
let cascade = DeletionCascade::full(vec!["checkpoints/".to_string(), "logs/".to_string()]);
let plan = cascade.plan();
assert_eq!(plan.len(), 3); assert!(plan[0].contains("checkpoints"));
assert!(plan[2].contains("unlearning"));
}
#[test]
fn test_weight_sovereignty_default() {
let config = WeightSovereigntyConfig::default();
assert!(config.access_control);
assert!(matches!(config.key_source, KeySource::EnvVar(_)));
}
#[test]
fn test_secure_aggregator_client_isolation_no_raw_data() {
let aggregator = SecureAggregator::new(3);
assert!(aggregator.encrypted);
assert_eq!(aggregator.num_clients, 3);
let result = aggregator.aggregate(&[]);
assert!(result.is_empty());
}
#[test]
fn test_classification_enforcement_runtime() {
assert!(check_classification(DataClassification::Sovereign, DataClassification::Public));
assert!(!check_classification(DataClassification::Public, DataClassification::Sovereign));
assert!(enforce_tier(DataClassification::Confidential));
assert!(validate_access_level(DataClassification::Internal, DataClassification::Internal));
}
#[test]
fn test_consent_and_purpose_limitation() {
let mut agreement = DataUsageAgreement::new();
let record = ConsentRecord {
purpose_id: "training".to_string(),
usage_scope: "model-improvement".to_string(),
purpose_limitation: true,
consent_scope: "org-internal".to_string(),
};
agreement.add_consent(record);
assert!(agreement.has_consent("training"));
assert!(!agreement.has_consent("marketing"));
}
#[test]
fn test_delete_user_rtbf() {
let cascade = DeletionCascade::full(vec!["checkpoints/".to_string()]);
let actions = delete_user("user-123", &cascade);
assert!(actions.iter().any(|a| a.contains("erasure")));
assert!(actions.iter().any(|a| a.contains("rtbf")));
}
#[test]
fn test_cross_border_transfer_log() {
let mut log = TransferLog::new();
log.log_transfer("us-east-1", "eu-west-1", "SCC");
assert_eq!(log.entries().len(), 1);
assert!(log.entries()[0].transfer_agreement.contains("adequacy_decision"));
}
#[test]
fn test_data_classification_display_all_variants() {
assert_eq!(DataClassification::Public.to_string(), "PUBLIC");
assert_eq!(DataClassification::Internal.to_string(), "INTERNAL");
assert_eq!(DataClassification::Confidential.to_string(), "CONFIDENTIAL");
assert_eq!(DataClassification::Sovereign.to_string(), "SOVEREIGN");
}
#[test]
fn test_residency_config_default() {
let config = ResidencyConfig::default();
assert!(config.is_region_allowed("local"));
assert!(!config.is_region_allowed("us-west-2"));
assert!(config.network_isolation);
assert!(config.enforce_at_runtime);
}
#[test]
fn test_residency_config_multiple_regions() {
let config = ResidencyConfig {
allowed_regions: vec![
"us-east-1".to_string(),
"eu-west-1".to_string(),
"local".to_string(),
],
network_isolation: false,
enforce_at_runtime: false,
};
assert!(config.is_region_allowed("us-east-1"));
assert!(config.is_region_allowed("eu-west-1"));
assert!(config.is_region_allowed("local"));
assert!(!config.is_region_allowed("ap-southeast-1"));
assert!(!config.network_isolation);
assert!(!config.enforce_at_runtime);
}
#[test]
fn test_api_allowlist_default() {
let allowlist = ApiAllowlist::default();
assert!(allowlist.offline_mode);
assert!(allowlist.allowed_endpoints.is_empty());
assert!(!allowlist.is_allowed("any-endpoint"));
}
#[test]
fn test_api_allowlist_with_endpoints() {
let mut endpoints = HashSet::new();
endpoints.insert("https://api.example.com".to_string());
endpoints.insert("https://api2.example.com".to_string());
let allowlist = ApiAllowlist { allowed_endpoints: endpoints, offline_mode: false };
assert!(allowlist.is_allowed("https://api.example.com"));
assert!(allowlist.is_allowed("https://api2.example.com"));
assert!(!allowlist.is_allowed("https://evil.com"));
}
#[test]
fn test_api_allowlist_offline_mode_overrides_endpoints() {
let mut endpoints = HashSet::new();
endpoints.insert("https://api.example.com".to_string());
let allowlist = ApiAllowlist { allowed_endpoints: endpoints, offline_mode: true };
assert!(!allowlist.is_allowed("https://api.example.com"));
}
#[test]
fn test_audit_trail_empty() {
let trail = AuditTrail::new();
assert!(trail.is_empty());
assert_eq!(trail.len(), 0);
assert!(trail.verify_integrity()); assert!(trail.entries().is_empty());
}
#[test]
fn test_audit_trail_default() {
let trail = AuditTrail::default();
assert!(trail.is_empty());
assert_eq!(trail.len(), 0);
}
#[test]
fn test_audit_trail_single_entry() {
let mut trail = AuditTrail::new();
let entry = trail.append("test_event", "test_data", DataClassification::Public);
assert_eq!(entry.sequence, 0);
assert_eq!(entry.event_type, "test_event");
assert_eq!(entry.data, "test_data");
assert_eq!(entry.classification, DataClassification::Public);
assert_eq!(entry.prev_hash, "genesis");
assert!(!entry.hash.is_empty());
assert_eq!(trail.len(), 1);
assert!(!trail.is_empty());
assert!(trail.verify_integrity());
}
#[test]
fn test_audit_trail_tamper_hash() {
let mut trail = AuditTrail::new();
trail.append("event_1", "data_1", DataClassification::Public);
trail.append("event_2", "data_2", DataClassification::Internal);
assert!(trail.verify_integrity());
trail.entries[0].hash = "tampered_hash".to_string();
assert!(!trail.verify_integrity());
}
#[test]
fn test_audit_trail_tamper_prev_hash_genesis() {
let mut trail = AuditTrail::new();
trail.append("event_1", "data_1", DataClassification::Public);
assert!(trail.verify_integrity());
trail.entries[0].prev_hash = "not_genesis".to_string();
assert!(!trail.verify_integrity());
}
#[test]
fn test_audit_trail_tamper_chain_middle() {
let mut trail = AuditTrail::new();
trail.append("a", "1", DataClassification::Public);
trail.append("b", "2", DataClassification::Internal);
trail.append("c", "3", DataClassification::Sovereign);
assert!(trail.verify_integrity());
trail.entries[2].prev_hash = "wrong_hash".to_string();
assert!(!trail.verify_integrity());
}
#[test]
fn test_audit_trail_classifications() {
let mut trail = AuditTrail::new();
trail.append("public_op", "pub", DataClassification::Public);
trail.append("internal_op", "int", DataClassification::Internal);
trail.append("confidential_op", "conf", DataClassification::Confidential);
trail.append("sovereign_op", "sov", DataClassification::Sovereign);
assert_eq!(trail.len(), 4);
assert_eq!(trail.entries()[0].classification, DataClassification::Public);
assert_eq!(trail.entries()[1].classification, DataClassification::Internal);
assert_eq!(trail.entries()[2].classification, DataClassification::Confidential);
assert_eq!(trail.entries()[3].classification, DataClassification::Sovereign);
assert!(trail.verify_integrity());
}
#[test]
fn test_weight_sovereignty_config_fields() {
let config = WeightSovereigntyConfig::default();
assert!(!config.encryption_required);
assert!(config.access_control);
match &config.key_source {
KeySource::EnvVar(var) => assert_eq!(var, "ALBOR_ENCRYPT_KEY"),
_ => panic!("Expected EnvVar key source"),
}
}
#[test]
fn test_key_source_variants() {
let file_key = KeySource::File("/path/to/key".to_string());
let env_key = KeySource::EnvVar("MY_KEY".to_string());
let none_key = KeySource::None;
assert!(matches!(file_key, KeySource::File(_)));
assert!(matches!(env_key, KeySource::EnvVar(_)));
assert!(matches!(none_key, KeySource::None));
}
#[test]
fn test_inherit_classification_all_combinations() {
assert_eq!(
inherit_classification(DataClassification::Public, DataClassification::Public),
DataClassification::Public
);
assert_eq!(
inherit_classification(DataClassification::Public, DataClassification::Sovereign),
DataClassification::Public
);
assert_eq!(
inherit_classification(DataClassification::Confidential, DataClassification::Public),
DataClassification::Confidential
);
assert_eq!(
inherit_classification(DataClassification::Internal, DataClassification::Confidential),
DataClassification::Internal
);
}
#[test]
fn test_deletion_cascade_empty_targets() {
let cascade = DeletionCascade::full(vec![]);
let plan = cascade.plan();
assert_eq!(plan.len(), 1); assert!(plan[0].contains("unlearning"));
}
#[test]
fn test_deletion_cascade_no_unlearning() {
let cascade =
DeletionCascade { targets: vec!["storage/".to_string()], requires_unlearning: false };
let plan = cascade.plan();
assert_eq!(plan.len(), 1); assert!(plan[0].contains("DELETE"));
assert!(plan[0].contains("storage"));
}
#[test]
fn test_deletion_cascade_multiple_targets() {
let cascade = DeletionCascade::full(vec![
"checkpoints/".to_string(),
"logs/".to_string(),
"cache/".to_string(),
]);
let plan = cascade.plan();
assert_eq!(plan.len(), 4); assert!(plan[0].contains("checkpoints"));
assert!(plan[1].contains("logs"));
assert!(plan[2].contains("cache"));
assert!(plan[3].contains("unlearning"));
}
#[test]
fn test_secure_aggregator_encrypted_flag() {
let agg = SecureAggregator::new(5);
assert!(agg.encrypted);
assert_eq!(agg.num_clients, 5);
}
#[test]
fn test_secure_aggregator_aggregate_returns_empty() {
let agg = SecureAggregator::new(2);
let grads = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
let result = agg.aggregate(&grads);
assert!(result.is_empty()); }
#[test]
fn test_check_classification_all_levels() {
assert!(check_classification(DataClassification::Public, DataClassification::Public));
assert!(check_classification(DataClassification::Internal, DataClassification::Internal));
assert!(check_classification(
DataClassification::Confidential,
DataClassification::Confidential
));
assert!(check_classification(DataClassification::Sovereign, DataClassification::Sovereign));
assert!(check_classification(DataClassification::Sovereign, DataClassification::Public));
assert!(check_classification(
DataClassification::Confidential,
DataClassification::Internal
));
assert!(check_classification(DataClassification::Internal, DataClassification::Public));
assert!(!check_classification(DataClassification::Public, DataClassification::Internal));
assert!(!check_classification(
DataClassification::Internal,
DataClassification::Confidential
));
assert!(!check_classification(
DataClassification::Confidential,
DataClassification::Sovereign
));
assert!(!check_classification(DataClassification::Public, DataClassification::Sovereign));
}
#[test]
fn test_enforce_tier_all_variants() {
assert!(enforce_tier(DataClassification::Public));
assert!(enforce_tier(DataClassification::Internal));
assert!(enforce_tier(DataClassification::Confidential));
assert!(enforce_tier(DataClassification::Sovereign));
}
#[test]
fn test_validate_access_level_boundary() {
assert!(validate_access_level(DataClassification::Internal, DataClassification::Public));
assert!(validate_access_level(DataClassification::Internal, DataClassification::Internal));
assert!(!validate_access_level(
DataClassification::Internal,
DataClassification::Confidential
));
}
#[test]
fn test_data_usage_agreement_default() {
let agreement = DataUsageAgreement::default();
assert!(agreement.records.is_empty());
assert!(!agreement.has_consent("anything"));
}
#[test]
fn test_data_usage_agreement_multiple_consents() {
let mut agreement = DataUsageAgreement::new();
agreement.add_consent(ConsentRecord {
purpose_id: "training".to_string(),
usage_scope: "model".to_string(),
purpose_limitation: true,
consent_scope: "org".to_string(),
});
agreement.add_consent(ConsentRecord {
purpose_id: "evaluation".to_string(),
usage_scope: "benchmark".to_string(),
purpose_limitation: false,
consent_scope: "public".to_string(),
});
assert!(agreement.has_consent("training"));
assert!(agreement.has_consent("evaluation"));
assert!(!agreement.has_consent("marketing"));
assert_eq!(agreement.records.len(), 2);
}
#[test]
fn test_delete_user_full_cascade() {
let cascade = DeletionCascade::full(vec!["checkpoints/".to_string(), "cache/".to_string()]);
let actions = delete_user("user-456", &cascade);
assert!(actions.len() >= 4); assert!(actions.iter().any(|a| a.contains("user-456")));
assert!(actions.iter().any(|a| a.contains("erasure")));
assert!(actions.iter().any(|a| a.contains("rtbf")));
assert!(actions.iter().any(|a| a.contains("checkpoints")));
assert!(actions.iter().any(|a| a.contains("unlearning")));
}
#[test]
fn test_cascade_delete_equals_plan() {
let cascade = DeletionCascade::full(vec!["db/".to_string()]);
let actions = cascade_delete(&cascade);
let plan = cascade.plan();
assert_eq!(actions, plan);
}
#[test]
fn test_transfer_log_default() {
let log = TransferLog::default();
assert!(log.entries().is_empty());
}
#[test]
fn test_transfer_log_multiple_entries() {
let mut log = TransferLog::new();
log.log_transfer("us-east-1", "eu-west-1", "SCC");
log.log_transfer("eu-west-1", "ap-southeast-1", "BCR");
log.log_transfer("ap-southeast-1", "local", "consent");
assert_eq!(log.entries().len(), 3);
assert_eq!(log.entries()[0].from, "us-east-1");
assert_eq!(log.entries()[0].to, "eu-west-1");
assert_eq!(log.entries()[0].legal_basis, "SCC");
assert!(log.entries()[0].transfer_agreement.contains("us-east-1->eu-west-1"));
assert_eq!(log.entries()[1].from, "eu-west-1");
assert_eq!(log.entries()[1].legal_basis, "BCR");
assert_eq!(log.entries()[2].to, "local");
}
#[test]
fn test_weight_access_allowed() {
let acl = vec!["alice".to_string(), "bob".to_string()];
assert!(weight_access(&acl, "alice"));
assert!(weight_access(&acl, "bob"));
assert!(!weight_access(&acl, "charlie"));
}
#[test]
fn test_weight_access_empty_acl() {
let acl: Vec<String> = vec![];
assert!(!weight_access(&acl, "anyone"));
}
#[test]
fn test_encrypt_weights_output_length() {
let weights = vec![1.0_f32, 2.0, 3.0, 4.0];
let key = b"test_key";
let encrypted = encrypt_weights(&weights, key);
assert_eq!(encrypted.len(), weights.len() * 4);
}
#[test]
fn test_encrypt_weights_empty() {
let encrypted = encrypt_weights(&[], b"key");
assert!(encrypted.is_empty());
}
#[test]
fn test_key_management_default() {
let km = KeyManagement::default();
assert_eq!(km.key_rotation_interval, 86400);
assert_eq!(km.kms_provider, "local");
}
#[test]
fn test_data_lineage_default() {
let lineage = DataLineage::default();
assert!(lineage.steps.is_empty());
}
#[test]
fn test_data_lineage_add_steps() {
let mut lineage = DataLineage::new();
lineage.add_step("step-1", "raw_data", "clean_data", "preprocessing");
lineage.add_step("step-2", "clean_data", "features", "feature_extraction");
lineage.add_step("step-3", "features", "model", "training");
assert_eq!(lineage.steps.len(), 3);
assert_eq!(lineage.steps[0].id, "step-1");
assert_eq!(lineage.steps[0].input, "raw_data");
assert_eq!(lineage.steps[0].output, "clean_data");
assert_eq!(lineage.steps[0].transform, "preprocessing");
assert_eq!(lineage.steps[2].output, "model");
}
#[test]
fn test_simple_hash_deterministic() {
let data = b"test data for hashing";
let hash1 = simple_hash(data);
let hash2 = simple_hash(data);
assert_eq!(hash1, hash2);
}
#[test]
fn test_simple_hash_different_inputs() {
let hash1 = simple_hash(b"input_a");
let hash2 = simple_hash(b"input_b");
assert_ne!(hash1, hash2);
}
#[test]
fn test_simple_hash_empty_input() {
let hash = simple_hash(b"");
assert_eq!(hash, 0xcbf29ce484222325);
}
#[test]
fn test_consent_record_fields() {
let record = ConsentRecord {
purpose_id: "research".to_string(),
usage_scope: "internal-only".to_string(),
purpose_limitation: true,
consent_scope: "org-wide".to_string(),
};
assert_eq!(record.purpose_id, "research");
assert!(record.purpose_limitation);
assert_eq!(record.consent_scope, "org-wide");
}
#[test]
fn test_weight_sovereignty_custom() {
let config = WeightSovereigntyConfig {
encryption_required: true,
access_control: false,
key_source: KeySource::File("/etc/keys/model.key".to_string()),
};
assert!(config.encryption_required);
assert!(!config.access_control);
assert!(matches!(config.key_source, KeySource::File(ref p) if p == "/etc/keys/model.key"));
}
#[test]
fn test_transfer_log_entry_fields() {
let mut log = TransferLog::new();
log.log_transfer("src-region", "dst-region", "adequacy");
let entry = &log.entries()[0];
assert_eq!(entry.from, "src-region");
assert_eq!(entry.to, "dst-region");
assert_eq!(entry.legal_basis, "adequacy");
assert_eq!(entry.transfer_agreement, "adequacy_decision:src-region->dst-region");
}
}