1use std::collections::HashSet;
11use std::fmt;
12use std::time::SystemTime;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum DataClassification {
17 Public,
19 Internal,
21 Confidential,
23 Sovereign,
25}
26
27impl fmt::Display for DataClassification {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::Public => write!(f, "PUBLIC"),
31 Self::Internal => write!(f, "INTERNAL"),
32 Self::Confidential => write!(f, "CONFIDENTIAL"),
33 Self::Sovereign => write!(f, "SOVEREIGN"),
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct ResidencyConfig {
41 pub allowed_regions: Vec<String>,
43 pub network_isolation: bool,
45 pub enforce_at_runtime: bool,
47}
48
49impl Default for ResidencyConfig {
50 fn default() -> Self {
51 Self {
52 allowed_regions: vec!["local".to_string()],
53 network_isolation: true,
54 enforce_at_runtime: true,
55 }
56 }
57}
58
59impl ResidencyConfig {
60 pub fn sovereign_local() -> Self {
62 Self {
63 allowed_regions: vec!["local".to_string()],
64 network_isolation: true,
65 enforce_at_runtime: true,
66 }
67 }
68
69 pub fn is_region_allowed(&self, region: &str) -> bool {
71 self.allowed_regions.iter().any(|r| r == region)
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct ApiAllowlist {
78 pub allowed_endpoints: HashSet<String>,
80 pub offline_mode: bool,
82}
83
84impl Default for ApiAllowlist {
85 fn default() -> Self {
86 Self { allowed_endpoints: HashSet::new(), offline_mode: true }
87 }
88}
89
90impl ApiAllowlist {
91 pub fn is_allowed(&self, endpoint: &str) -> bool {
93 if self.offline_mode {
94 return false;
95 }
96 self.allowed_endpoints.contains(endpoint)
97 }
98
99 pub fn offline() -> Self {
101 Self { allowed_endpoints: HashSet::new(), offline_mode: true }
102 }
103}
104
105#[derive(Debug, Clone)]
109pub struct AuditEntry {
110 pub sequence: u64,
112 pub timestamp: SystemTime,
114 pub event_type: String,
116 pub data: String,
118 pub classification: DataClassification,
120 pub prev_hash: String,
122 pub hash: String,
124}
125
126#[derive(Debug)]
131pub struct AuditTrail {
132 entries: Vec<AuditEntry>,
133 next_sequence: u64,
134}
135
136impl AuditTrail {
137 pub fn new() -> Self {
139 Self { entries: Vec::new(), next_sequence: 0 }
140 }
141
142 pub fn append(
144 &mut self,
145 event_type: &str,
146 data: &str,
147 classification: DataClassification,
148 ) -> &AuditEntry {
149 let prev_hash =
150 self.entries.last().map_or_else(|| "genesis".to_string(), |e| e.hash.clone());
151
152 let sequence = self.next_sequence;
153 self.next_sequence += 1;
154
155 let hash_input = format!("{sequence}:{event_type}:{data}:{prev_hash}");
157 let hash = format!("{:x}", simple_hash(hash_input.as_bytes()));
158
159 let entry = AuditEntry {
160 sequence,
161 timestamp: SystemTime::now(),
162 event_type: event_type.to_string(),
163 data: data.to_string(),
164 classification,
165 prev_hash,
166 hash,
167 };
168
169 self.entries.push(entry);
170 self.entries.last().unwrap()
171 }
172
173 pub fn verify_integrity(&self) -> bool {
175 for (i, entry) in self.entries.iter().enumerate() {
176 if i == 0 {
177 if entry.prev_hash != "genesis" {
178 return false;
179 }
180 } else if entry.prev_hash != self.entries[i - 1].hash {
181 return false;
182 }
183
184 let hash_input = format!(
186 "{}:{}:{}:{}",
187 entry.sequence, entry.event_type, entry.data, entry.prev_hash
188 );
189 let expected_hash = format!("{:x}", simple_hash(hash_input.as_bytes()));
190 if entry.hash != expected_hash {
191 return false;
192 }
193 }
194 true
195 }
196
197 pub fn len(&self) -> usize {
199 self.entries.len()
200 }
201
202 pub fn is_empty(&self) -> bool {
204 self.entries.is_empty()
205 }
206
207 pub fn entries(&self) -> &[AuditEntry] {
209 &self.entries
210 }
211}
212
213impl Default for AuditTrail {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219fn simple_hash(data: &[u8]) -> u64 {
221 let mut hash: u64 = 0xcbf29ce484222325;
222 for &byte in data {
223 hash ^= u64::from(byte);
224 hash = hash.wrapping_mul(0x100000001b3);
225 }
226 hash
227}
228
229#[derive(Debug, Clone)]
231pub struct WeightSovereigntyConfig {
232 pub encryption_required: bool,
234 pub access_control: bool,
236 pub key_source: KeySource,
238}
239
240#[derive(Debug, Clone)]
242pub enum KeySource {
243 File(String),
245 EnvVar(String),
247 None,
249}
250
251impl Default for WeightSovereigntyConfig {
252 fn default() -> Self {
253 Self {
254 encryption_required: false,
255 access_control: true,
256 key_source: KeySource::EnvVar("ALBOR_ENCRYPT_KEY".to_string()),
257 }
258 }
259}
260
261pub fn inherit_classification(
265 input_class: DataClassification,
266 _model_class: DataClassification,
267) -> DataClassification {
268 input_class
270}
271
272#[derive(Debug)]
274pub struct DeletionCascade {
275 pub targets: Vec<String>,
277 pub requires_unlearning: bool,
279}
280
281impl DeletionCascade {
282 pub fn full(targets: Vec<String>) -> Self {
284 Self { targets, requires_unlearning: true }
285 }
286
287 pub fn plan(&self) -> Vec<String> {
289 let mut actions = Vec::new();
290 for target in &self.targets {
291 actions.push(format!("DELETE data from {target}"));
292 }
293 if self.requires_unlearning {
294 actions.push("TRIGGER model unlearning procedure".to_string());
295 }
296 actions
297 }
298}
299
300#[derive(Debug)]
304pub struct SecureAggregator {
305 pub num_clients: usize,
307 pub encrypted: bool,
309}
310
311impl SecureAggregator {
312 pub fn new(num_clients: usize) -> Self {
314 Self { num_clients, encrypted: true }
315 }
316
317 pub fn aggregate(&self, _encrypted_gradient: &[Vec<f32>]) -> Vec<f32> {
319 Vec::new()
321 }
322}
323
324pub fn check_classification(
328 data_class: DataClassification,
329 required_level: DataClassification,
330) -> bool {
331 validate_access_level(data_class, required_level)
332}
333
334pub fn enforce_tier(data_class: DataClassification) -> bool {
336 matches!(
337 data_class,
338 DataClassification::Public
339 | DataClassification::Internal
340 | DataClassification::Confidential
341 | DataClassification::Sovereign
342 )
343}
344
345pub fn validate_access_level(actual: DataClassification, required: DataClassification) -> bool {
347 let level = |c: &DataClassification| match c {
348 DataClassification::Public => 0,
349 DataClassification::Internal => 1,
350 DataClassification::Confidential => 2,
351 DataClassification::Sovereign => 3,
352 };
353 level(&actual) >= level(&required)
354}
355
356#[derive(Debug, Clone)]
358pub struct ConsentRecord {
359 pub purpose_id: String,
361 pub usage_scope: String,
363 pub purpose_limitation: bool,
365 pub consent_scope: String,
367}
368
369#[derive(Debug)]
371pub struct DataUsageAgreement {
372 pub records: Vec<ConsentRecord>,
374}
375
376impl DataUsageAgreement {
377 pub fn new() -> Self {
379 Self { records: Vec::new() }
380 }
381
382 pub fn add_consent(&mut self, record: ConsentRecord) {
384 self.records.push(record);
385 }
386
387 pub fn has_consent(&self, purpose_id: &str) -> bool {
389 self.records.iter().any(|r| r.purpose_id == purpose_id)
390 }
391}
392
393impl Default for DataUsageAgreement {
394 fn default() -> Self {
395 Self::new()
396 }
397}
398
399pub fn delete_user(user_id: &str, cascade: &DeletionCascade) -> Vec<String> {
403 let mut actions = Vec::new();
404 actions.push(format!("erasure: removing data for user {user_id}"));
405 actions.push(format!("rtbf: right to be forgotten for {user_id}"));
406 actions.extend(cascade_delete(cascade));
407 actions
408}
409
410pub fn cascade_delete(cascade: &DeletionCascade) -> Vec<String> {
412 cascade.plan()
413}
414
415#[derive(Debug)]
417pub struct TransferLog {
418 entries: Vec<TransferLogEntry>,
419}
420
421#[derive(Debug)]
423pub struct TransferLogEntry {
424 pub from: String,
426 pub to: String,
428 pub legal_basis: String,
430 pub transfer_agreement: String,
432}
433
434impl TransferLog {
435 pub fn new() -> Self {
437 Self { entries: Vec::new() }
438 }
439
440 pub fn log_transfer(&mut self, from: &str, to: &str, legal_basis: &str) {
442 self.entries.push(TransferLogEntry {
443 from: from.to_string(),
444 to: to.to_string(),
445 legal_basis: legal_basis.to_string(),
446 transfer_agreement: format!("adequacy_decision:{from}->{to}"),
447 });
448 }
449
450 pub fn entries(&self) -> &[TransferLogEntry] {
452 &self.entries
453 }
454}
455
456impl Default for TransferLog {
457 fn default() -> Self {
458 Self::new()
459 }
460}
461
462pub fn weight_access(model_acl: &[String], requester: &str) -> bool {
467 model_acl.iter().any(|allowed| allowed == requester)
468}
469
470pub fn encrypt_weights(weights: &[f32], _key: &[u8]) -> Vec<u8> {
472 let bytes: Vec<u8> = weights.iter().flat_map(|w| w.to_le_bytes()).collect();
474 bytes
475}
476
477#[derive(Debug)]
479pub struct KeyManagement {
480 pub key_rotation_interval: u64,
482 pub kms_provider: String,
484}
485
486impl Default for KeyManagement {
487 fn default() -> Self {
488 Self { key_rotation_interval: 86400, kms_provider: "local".to_string() }
489 }
490}
491
492#[derive(Debug)]
496pub struct DataLineage {
497 pub steps: Vec<LineageStep>,
499}
500
501#[derive(Debug)]
503pub struct LineageStep {
504 pub id: String,
506 pub input: String,
508 pub output: String,
510 pub transform: String,
512}
513
514impl DataLineage {
515 pub fn new() -> Self {
517 Self { steps: Vec::new() }
518 }
519
520 pub fn add_step(&mut self, id: &str, input: &str, output: &str, transform: &str) {
522 self.steps.push(LineageStep {
523 id: id.to_string(),
524 input: input.to_string(),
525 output: output.to_string(),
526 transform: transform.to_string(),
527 });
528 }
529}
530
531impl Default for DataLineage {
532 fn default() -> Self {
533 Self::new()
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540
541 #[test]
542 fn test_residency_config_sovereign_local() {
543 let config = ResidencyConfig::sovereign_local();
544 assert!(config.is_region_allowed("local"));
545 assert!(!config.is_region_allowed("us-east-1"));
546 assert!(config.network_isolation);
547 }
548
549 #[test]
550 fn test_api_allowlist_offline() {
551 let allowlist = ApiAllowlist::offline();
552 assert!(!allowlist.is_allowed("https://api.example.com"));
553 assert!(!allowlist.is_allowed("localhost"));
554 assert!(allowlist.offline_mode);
555 }
556
557 #[test]
558 fn test_audit_trail_append_only() {
559 let mut trail = AuditTrail::new();
560
561 trail.append("training_start", "model=350M", DataClassification::Internal);
562 trail.append("checkpoint_save", "step=100", DataClassification::Sovereign);
563 trail.append("training_end", "loss=5.92", DataClassification::Internal);
564
565 assert_eq!(trail.len(), 3);
566 assert!(trail.verify_integrity());
567 }
568
569 #[test]
570 fn test_audit_trail_tamper_detection() {
571 let mut trail = AuditTrail::new();
572
573 trail.append("event_1", "data_1", DataClassification::Public);
574 trail.append("event_2", "data_2", DataClassification::Public);
575
576 assert!(trail.verify_integrity());
577
578 if let Some(entry) = trail.entries.first_mut() {
580 entry.data = "tampered".to_string();
581 }
582
583 assert!(!trail.verify_integrity());
585 }
586
587 #[test]
588 fn test_audit_trail_hash_chain() {
589 let mut trail = AuditTrail::new();
590
591 trail.append("a", "1", DataClassification::Public);
592 trail.append("b", "2", DataClassification::Public);
593 trail.append("c", "3", DataClassification::Public);
594
595 assert_eq!(trail.entries()[0].prev_hash, "genesis");
597 assert_eq!(trail.entries()[1].prev_hash, trail.entries()[0].hash);
598 assert_eq!(trail.entries()[2].prev_hash, trail.entries()[1].hash);
599 }
600
601 #[test]
602 fn test_data_classification_display() {
603 assert_eq!(DataClassification::Sovereign.to_string(), "SOVEREIGN");
604 assert_eq!(DataClassification::Public.to_string(), "PUBLIC");
605 }
606
607 #[test]
608 fn test_classification_inheritance() {
609 let result =
611 inherit_classification(DataClassification::Sovereign, DataClassification::Internal);
612 assert_eq!(result, DataClassification::Sovereign);
613 }
614
615 #[test]
616 fn test_deletion_cascade_plan() {
617 let cascade = DeletionCascade::full(vec!["checkpoints/".to_string(), "logs/".to_string()]);
618 let plan = cascade.plan();
619 assert_eq!(plan.len(), 3); assert!(plan[0].contains("checkpoints"));
621 assert!(plan[2].contains("unlearning"));
622 }
623
624 #[test]
625 fn test_weight_sovereignty_default() {
626 let config = WeightSovereigntyConfig::default();
627 assert!(config.access_control);
628 assert!(matches!(config.key_source, KeySource::EnvVar(_)));
629 }
630
631 #[test]
634 fn test_secure_aggregator_client_isolation_no_raw_data() {
635 let aggregator = SecureAggregator::new(3);
636 assert!(aggregator.encrypted);
637 assert_eq!(aggregator.num_clients, 3);
638 let result = aggregator.aggregate(&[]);
640 assert!(result.is_empty());
641 }
642
643 #[test]
645 fn test_classification_enforcement_runtime() {
646 assert!(check_classification(DataClassification::Sovereign, DataClassification::Public));
647 assert!(!check_classification(DataClassification::Public, DataClassification::Sovereign));
648 assert!(enforce_tier(DataClassification::Confidential));
649 assert!(validate_access_level(DataClassification::Internal, DataClassification::Internal));
650 }
651
652 #[test]
654 fn test_consent_and_purpose_limitation() {
655 let mut agreement = DataUsageAgreement::new();
656 let record = ConsentRecord {
657 purpose_id: "training".to_string(),
658 usage_scope: "model-improvement".to_string(),
659 purpose_limitation: true,
660 consent_scope: "org-internal".to_string(),
661 };
662 agreement.add_consent(record);
663 assert!(agreement.has_consent("training"));
664 assert!(!agreement.has_consent("marketing"));
665 }
666
667 #[test]
669 fn test_delete_user_rtbf() {
670 let cascade = DeletionCascade::full(vec!["checkpoints/".to_string()]);
671 let actions = delete_user("user-123", &cascade);
672 assert!(actions.iter().any(|a| a.contains("erasure")));
673 assert!(actions.iter().any(|a| a.contains("rtbf")));
674 }
675
676 #[test]
678 fn test_cross_border_transfer_log() {
679 let mut log = TransferLog::new();
680 log.log_transfer("us-east-1", "eu-west-1", "SCC");
681 assert_eq!(log.entries().len(), 1);
682 assert!(log.entries()[0].transfer_agreement.contains("adequacy_decision"));
683 }
684
685 #[test]
688 fn test_data_classification_display_all_variants() {
689 assert_eq!(DataClassification::Public.to_string(), "PUBLIC");
690 assert_eq!(DataClassification::Internal.to_string(), "INTERNAL");
691 assert_eq!(DataClassification::Confidential.to_string(), "CONFIDENTIAL");
692 assert_eq!(DataClassification::Sovereign.to_string(), "SOVEREIGN");
693 }
694
695 #[test]
696 fn test_residency_config_default() {
697 let config = ResidencyConfig::default();
698 assert!(config.is_region_allowed("local"));
699 assert!(!config.is_region_allowed("us-west-2"));
700 assert!(config.network_isolation);
701 assert!(config.enforce_at_runtime);
702 }
703
704 #[test]
705 fn test_residency_config_multiple_regions() {
706 let config = ResidencyConfig {
707 allowed_regions: vec![
708 "us-east-1".to_string(),
709 "eu-west-1".to_string(),
710 "local".to_string(),
711 ],
712 network_isolation: false,
713 enforce_at_runtime: false,
714 };
715 assert!(config.is_region_allowed("us-east-1"));
716 assert!(config.is_region_allowed("eu-west-1"));
717 assert!(config.is_region_allowed("local"));
718 assert!(!config.is_region_allowed("ap-southeast-1"));
719 assert!(!config.network_isolation);
720 assert!(!config.enforce_at_runtime);
721 }
722
723 #[test]
724 fn test_api_allowlist_default() {
725 let allowlist = ApiAllowlist::default();
726 assert!(allowlist.offline_mode);
727 assert!(allowlist.allowed_endpoints.is_empty());
728 assert!(!allowlist.is_allowed("any-endpoint"));
729 }
730
731 #[test]
732 fn test_api_allowlist_with_endpoints() {
733 let mut endpoints = HashSet::new();
734 endpoints.insert("https://api.example.com".to_string());
735 endpoints.insert("https://api2.example.com".to_string());
736 let allowlist = ApiAllowlist { allowed_endpoints: endpoints, offline_mode: false };
737 assert!(allowlist.is_allowed("https://api.example.com"));
738 assert!(allowlist.is_allowed("https://api2.example.com"));
739 assert!(!allowlist.is_allowed("https://evil.com"));
740 }
741
742 #[test]
743 fn test_api_allowlist_offline_mode_overrides_endpoints() {
744 let mut endpoints = HashSet::new();
745 endpoints.insert("https://api.example.com".to_string());
746 let allowlist = ApiAllowlist { allowed_endpoints: endpoints, offline_mode: true };
747 assert!(!allowlist.is_allowed("https://api.example.com"));
749 }
750
751 #[test]
752 fn test_audit_trail_empty() {
753 let trail = AuditTrail::new();
754 assert!(trail.is_empty());
755 assert_eq!(trail.len(), 0);
756 assert!(trail.verify_integrity()); assert!(trail.entries().is_empty());
758 }
759
760 #[test]
761 fn test_audit_trail_default() {
762 let trail = AuditTrail::default();
763 assert!(trail.is_empty());
764 assert_eq!(trail.len(), 0);
765 }
766
767 #[test]
768 fn test_audit_trail_single_entry() {
769 let mut trail = AuditTrail::new();
770 let entry = trail.append("test_event", "test_data", DataClassification::Public);
771 assert_eq!(entry.sequence, 0);
772 assert_eq!(entry.event_type, "test_event");
773 assert_eq!(entry.data, "test_data");
774 assert_eq!(entry.classification, DataClassification::Public);
775 assert_eq!(entry.prev_hash, "genesis");
776 assert!(!entry.hash.is_empty());
777 assert_eq!(trail.len(), 1);
778 assert!(!trail.is_empty());
779 assert!(trail.verify_integrity());
780 }
781
782 #[test]
783 fn test_audit_trail_tamper_hash() {
784 let mut trail = AuditTrail::new();
785 trail.append("event_1", "data_1", DataClassification::Public);
786 trail.append("event_2", "data_2", DataClassification::Internal);
787 assert!(trail.verify_integrity());
788
789 trail.entries[0].hash = "tampered_hash".to_string();
791 assert!(!trail.verify_integrity());
792 }
793
794 #[test]
795 fn test_audit_trail_tamper_prev_hash_genesis() {
796 let mut trail = AuditTrail::new();
797 trail.append("event_1", "data_1", DataClassification::Public);
798 assert!(trail.verify_integrity());
799
800 trail.entries[0].prev_hash = "not_genesis".to_string();
802 assert!(!trail.verify_integrity());
803 }
804
805 #[test]
806 fn test_audit_trail_tamper_chain_middle() {
807 let mut trail = AuditTrail::new();
808 trail.append("a", "1", DataClassification::Public);
809 trail.append("b", "2", DataClassification::Internal);
810 trail.append("c", "3", DataClassification::Sovereign);
811 assert!(trail.verify_integrity());
812
813 trail.entries[2].prev_hash = "wrong_hash".to_string();
815 assert!(!trail.verify_integrity());
816 }
817
818 #[test]
819 fn test_audit_trail_classifications() {
820 let mut trail = AuditTrail::new();
821 trail.append("public_op", "pub", DataClassification::Public);
822 trail.append("internal_op", "int", DataClassification::Internal);
823 trail.append("confidential_op", "conf", DataClassification::Confidential);
824 trail.append("sovereign_op", "sov", DataClassification::Sovereign);
825
826 assert_eq!(trail.len(), 4);
827 assert_eq!(trail.entries()[0].classification, DataClassification::Public);
828 assert_eq!(trail.entries()[1].classification, DataClassification::Internal);
829 assert_eq!(trail.entries()[2].classification, DataClassification::Confidential);
830 assert_eq!(trail.entries()[3].classification, DataClassification::Sovereign);
831 assert!(trail.verify_integrity());
832 }
833
834 #[test]
835 fn test_weight_sovereignty_config_fields() {
836 let config = WeightSovereigntyConfig::default();
837 assert!(!config.encryption_required);
838 assert!(config.access_control);
839 match &config.key_source {
840 KeySource::EnvVar(var) => assert_eq!(var, "ALBOR_ENCRYPT_KEY"),
841 _ => panic!("Expected EnvVar key source"),
842 }
843 }
844
845 #[test]
846 fn test_key_source_variants() {
847 let file_key = KeySource::File("/path/to/key".to_string());
848 let env_key = KeySource::EnvVar("MY_KEY".to_string());
849 let none_key = KeySource::None;
850
851 assert!(matches!(file_key, KeySource::File(_)));
852 assert!(matches!(env_key, KeySource::EnvVar(_)));
853 assert!(matches!(none_key, KeySource::None));
854 }
855
856 #[test]
857 fn test_inherit_classification_all_combinations() {
858 assert_eq!(
860 inherit_classification(DataClassification::Public, DataClassification::Public),
861 DataClassification::Public
862 );
863 assert_eq!(
864 inherit_classification(DataClassification::Public, DataClassification::Sovereign),
865 DataClassification::Public
866 );
867 assert_eq!(
868 inherit_classification(DataClassification::Confidential, DataClassification::Public),
869 DataClassification::Confidential
870 );
871 assert_eq!(
872 inherit_classification(DataClassification::Internal, DataClassification::Confidential),
873 DataClassification::Internal
874 );
875 }
876
877 #[test]
878 fn test_deletion_cascade_empty_targets() {
879 let cascade = DeletionCascade::full(vec![]);
880 let plan = cascade.plan();
881 assert_eq!(plan.len(), 1); assert!(plan[0].contains("unlearning"));
883 }
884
885 #[test]
886 fn test_deletion_cascade_no_unlearning() {
887 let cascade =
888 DeletionCascade { targets: vec!["storage/".to_string()], requires_unlearning: false };
889 let plan = cascade.plan();
890 assert_eq!(plan.len(), 1); assert!(plan[0].contains("DELETE"));
892 assert!(plan[0].contains("storage"));
893 }
894
895 #[test]
896 fn test_deletion_cascade_multiple_targets() {
897 let cascade = DeletionCascade::full(vec![
898 "checkpoints/".to_string(),
899 "logs/".to_string(),
900 "cache/".to_string(),
901 ]);
902 let plan = cascade.plan();
903 assert_eq!(plan.len(), 4); assert!(plan[0].contains("checkpoints"));
905 assert!(plan[1].contains("logs"));
906 assert!(plan[2].contains("cache"));
907 assert!(plan[3].contains("unlearning"));
908 }
909
910 #[test]
911 fn test_secure_aggregator_encrypted_flag() {
912 let agg = SecureAggregator::new(5);
913 assert!(agg.encrypted);
914 assert_eq!(agg.num_clients, 5);
915 }
916
917 #[test]
918 fn test_secure_aggregator_aggregate_returns_empty() {
919 let agg = SecureAggregator::new(2);
920 let grads = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
921 let result = agg.aggregate(&grads);
922 assert!(result.is_empty()); }
924
925 #[test]
926 fn test_check_classification_all_levels() {
927 assert!(check_classification(DataClassification::Public, DataClassification::Public));
929 assert!(check_classification(DataClassification::Internal, DataClassification::Internal));
930 assert!(check_classification(
931 DataClassification::Confidential,
932 DataClassification::Confidential
933 ));
934 assert!(check_classification(DataClassification::Sovereign, DataClassification::Sovereign));
935
936 assert!(check_classification(DataClassification::Sovereign, DataClassification::Public));
938 assert!(check_classification(
939 DataClassification::Confidential,
940 DataClassification::Internal
941 ));
942 assert!(check_classification(DataClassification::Internal, DataClassification::Public));
943
944 assert!(!check_classification(DataClassification::Public, DataClassification::Internal));
946 assert!(!check_classification(
947 DataClassification::Internal,
948 DataClassification::Confidential
949 ));
950 assert!(!check_classification(
951 DataClassification::Confidential,
952 DataClassification::Sovereign
953 ));
954 assert!(!check_classification(DataClassification::Public, DataClassification::Sovereign));
955 }
956
957 #[test]
958 fn test_enforce_tier_all_variants() {
959 assert!(enforce_tier(DataClassification::Public));
960 assert!(enforce_tier(DataClassification::Internal));
961 assert!(enforce_tier(DataClassification::Confidential));
962 assert!(enforce_tier(DataClassification::Sovereign));
963 }
964
965 #[test]
966 fn test_validate_access_level_boundary() {
967 assert!(validate_access_level(DataClassification::Internal, DataClassification::Public));
969 assert!(validate_access_level(DataClassification::Internal, DataClassification::Internal));
970 assert!(!validate_access_level(
971 DataClassification::Internal,
972 DataClassification::Confidential
973 ));
974 }
975
976 #[test]
977 fn test_data_usage_agreement_default() {
978 let agreement = DataUsageAgreement::default();
979 assert!(agreement.records.is_empty());
980 assert!(!agreement.has_consent("anything"));
981 }
982
983 #[test]
984 fn test_data_usage_agreement_multiple_consents() {
985 let mut agreement = DataUsageAgreement::new();
986 agreement.add_consent(ConsentRecord {
987 purpose_id: "training".to_string(),
988 usage_scope: "model".to_string(),
989 purpose_limitation: true,
990 consent_scope: "org".to_string(),
991 });
992 agreement.add_consent(ConsentRecord {
993 purpose_id: "evaluation".to_string(),
994 usage_scope: "benchmark".to_string(),
995 purpose_limitation: false,
996 consent_scope: "public".to_string(),
997 });
998 assert!(agreement.has_consent("training"));
999 assert!(agreement.has_consent("evaluation"));
1000 assert!(!agreement.has_consent("marketing"));
1001 assert_eq!(agreement.records.len(), 2);
1002 }
1003
1004 #[test]
1005 fn test_delete_user_full_cascade() {
1006 let cascade = DeletionCascade::full(vec!["checkpoints/".to_string(), "cache/".to_string()]);
1007 let actions = delete_user("user-456", &cascade);
1008 assert!(actions.len() >= 4); assert!(actions.iter().any(|a| a.contains("user-456")));
1010 assert!(actions.iter().any(|a| a.contains("erasure")));
1011 assert!(actions.iter().any(|a| a.contains("rtbf")));
1012 assert!(actions.iter().any(|a| a.contains("checkpoints")));
1013 assert!(actions.iter().any(|a| a.contains("unlearning")));
1014 }
1015
1016 #[test]
1017 fn test_cascade_delete_equals_plan() {
1018 let cascade = DeletionCascade::full(vec!["db/".to_string()]);
1019 let actions = cascade_delete(&cascade);
1020 let plan = cascade.plan();
1021 assert_eq!(actions, plan);
1022 }
1023
1024 #[test]
1025 fn test_transfer_log_default() {
1026 let log = TransferLog::default();
1027 assert!(log.entries().is_empty());
1028 }
1029
1030 #[test]
1031 fn test_transfer_log_multiple_entries() {
1032 let mut log = TransferLog::new();
1033 log.log_transfer("us-east-1", "eu-west-1", "SCC");
1034 log.log_transfer("eu-west-1", "ap-southeast-1", "BCR");
1035 log.log_transfer("ap-southeast-1", "local", "consent");
1036
1037 assert_eq!(log.entries().len(), 3);
1038 assert_eq!(log.entries()[0].from, "us-east-1");
1039 assert_eq!(log.entries()[0].to, "eu-west-1");
1040 assert_eq!(log.entries()[0].legal_basis, "SCC");
1041 assert!(log.entries()[0].transfer_agreement.contains("us-east-1->eu-west-1"));
1042
1043 assert_eq!(log.entries()[1].from, "eu-west-1");
1044 assert_eq!(log.entries()[1].legal_basis, "BCR");
1045
1046 assert_eq!(log.entries()[2].to, "local");
1047 }
1048
1049 #[test]
1050 fn test_weight_access_allowed() {
1051 let acl = vec!["alice".to_string(), "bob".to_string()];
1052 assert!(weight_access(&acl, "alice"));
1053 assert!(weight_access(&acl, "bob"));
1054 assert!(!weight_access(&acl, "charlie"));
1055 }
1056
1057 #[test]
1058 fn test_weight_access_empty_acl() {
1059 let acl: Vec<String> = vec![];
1060 assert!(!weight_access(&acl, "anyone"));
1061 }
1062
1063 #[test]
1064 fn test_encrypt_weights_output_length() {
1065 let weights = vec![1.0_f32, 2.0, 3.0, 4.0];
1066 let key = b"test_key";
1067 let encrypted = encrypt_weights(&weights, key);
1068 assert_eq!(encrypted.len(), weights.len() * 4);
1070 }
1071
1072 #[test]
1073 fn test_encrypt_weights_empty() {
1074 let encrypted = encrypt_weights(&[], b"key");
1075 assert!(encrypted.is_empty());
1076 }
1077
1078 #[test]
1079 fn test_key_management_default() {
1080 let km = KeyManagement::default();
1081 assert_eq!(km.key_rotation_interval, 86400);
1082 assert_eq!(km.kms_provider, "local");
1083 }
1084
1085 #[test]
1086 fn test_data_lineage_default() {
1087 let lineage = DataLineage::default();
1088 assert!(lineage.steps.is_empty());
1089 }
1090
1091 #[test]
1092 fn test_data_lineage_add_steps() {
1093 let mut lineage = DataLineage::new();
1094 lineage.add_step("step-1", "raw_data", "clean_data", "preprocessing");
1095 lineage.add_step("step-2", "clean_data", "features", "feature_extraction");
1096 lineage.add_step("step-3", "features", "model", "training");
1097
1098 assert_eq!(lineage.steps.len(), 3);
1099 assert_eq!(lineage.steps[0].id, "step-1");
1100 assert_eq!(lineage.steps[0].input, "raw_data");
1101 assert_eq!(lineage.steps[0].output, "clean_data");
1102 assert_eq!(lineage.steps[0].transform, "preprocessing");
1103 assert_eq!(lineage.steps[2].output, "model");
1104 }
1105
1106 #[test]
1107 fn test_simple_hash_deterministic() {
1108 let data = b"test data for hashing";
1109 let hash1 = simple_hash(data);
1110 let hash2 = simple_hash(data);
1111 assert_eq!(hash1, hash2);
1112 }
1113
1114 #[test]
1115 fn test_simple_hash_different_inputs() {
1116 let hash1 = simple_hash(b"input_a");
1117 let hash2 = simple_hash(b"input_b");
1118 assert_ne!(hash1, hash2);
1119 }
1120
1121 #[test]
1122 fn test_simple_hash_empty_input() {
1123 let hash = simple_hash(b"");
1124 assert_eq!(hash, 0xcbf29ce484222325);
1126 }
1127
1128 #[test]
1129 fn test_consent_record_fields() {
1130 let record = ConsentRecord {
1131 purpose_id: "research".to_string(),
1132 usage_scope: "internal-only".to_string(),
1133 purpose_limitation: true,
1134 consent_scope: "org-wide".to_string(),
1135 };
1136 assert_eq!(record.purpose_id, "research");
1137 assert!(record.purpose_limitation);
1138 assert_eq!(record.consent_scope, "org-wide");
1139 }
1140
1141 #[test]
1142 fn test_weight_sovereignty_custom() {
1143 let config = WeightSovereigntyConfig {
1144 encryption_required: true,
1145 access_control: false,
1146 key_source: KeySource::File("/etc/keys/model.key".to_string()),
1147 };
1148 assert!(config.encryption_required);
1149 assert!(!config.access_control);
1150 assert!(matches!(config.key_source, KeySource::File(ref p) if p == "/etc/keys/model.key"));
1151 }
1152
1153 #[test]
1154 fn test_transfer_log_entry_fields() {
1155 let mut log = TransferLog::new();
1156 log.log_transfer("src-region", "dst-region", "adequacy");
1157 let entry = &log.entries()[0];
1158 assert_eq!(entry.from, "src-region");
1159 assert_eq!(entry.to, "dst-region");
1160 assert_eq!(entry.legal_basis, "adequacy");
1161 assert_eq!(entry.transfer_agreement, "adequacy_decision:src-region->dst-region");
1162 }
1163}