1use super::types::{CheckItem, Evidence, EvidenceType, Severity};
13use std::path::Path;
14use std::time::Instant;
15
16pub fn evaluate_all(project_path: &Path) -> Vec<CheckItem> {
18 vec![
19 check_data_residency_boundary(project_path),
20 check_data_inventory_completeness(project_path),
21 check_privacy_preserving_computation(project_path),
22 check_federated_learning_isolation(project_path),
23 check_supply_chain_provenance(project_path),
24 check_vpc_isolation(project_path),
25 check_data_classification_enforcement(project_path),
26 check_consent_purpose_limitation(project_path),
27 check_rtbf_compliance(project_path),
28 check_cross_border_logging(project_path),
29 check_model_weight_sovereignty(project_path),
30 check_inference_classification(project_path),
31 check_audit_log_immutability(project_path),
32 check_third_party_isolation(project_path),
33 check_secure_computation(project_path),
34 ]
35}
36
37pub fn check_data_residency_boundary(project_path: &Path) -> CheckItem {
44 let start = Instant::now();
45 let mut item = CheckItem::new(
46 "SDG-01",
47 "Data Residency Boundary Enforcement",
48 "Sovereign-critical data never crosses geographic boundaries",
49 )
50 .with_severity(Severity::Critical)
51 .with_tps("Jidoka — automatic boundary violation detection");
52
53 let config_files = [
55 project_path.join("config/residency.yaml"),
56 project_path.join("config/residency.toml"),
57 project_path.join(".sovereignty/residency.yaml"),
58 ];
59
60 let has_residency_config = config_files.iter().any(|p| p.exists());
61
62 let infra_files = [
64 project_path.join("infrastructure/vpc.tf"),
65 project_path.join("terraform/vpc.tf"),
66 project_path.join("infra/network.yaml"),
67 ];
68
69 let has_network_isolation = infra_files.iter().any(|p| p.exists());
70
71 let has_residency_checks = check_for_pattern(
73 project_path,
74 &["residency", "region_check", "boundary_enforce", "geo_fence"],
75 );
76
77 item = item.with_evidence(Evidence {
78 evidence_type: EvidenceType::StaticAnalysis,
79 description: format!(
80 "Residency: config={}, network_isolation={}, code_checks={}",
81 has_residency_config, has_network_isolation, has_residency_checks
82 ),
83 data: None,
84 files: Vec::new(),
85 });
86
87 let is_cli_only = !has_network_code(project_path);
88 item = apply_check_outcome(
89 item,
90 &[
91 (has_residency_config && has_network_isolation, None),
92 (
93 has_residency_config || has_residency_checks,
94 Some("Partial residency enforcement (missing network isolation)"),
95 ),
96 (is_cli_only, None),
97 (true, Some("No explicit residency configuration")),
98 ],
99 );
100
101 item.finish_timed(start)
102}
103
104pub fn check_data_inventory_completeness(project_path: &Path) -> CheckItem {
111 let start = Instant::now();
112 let mut item = CheckItem::new(
113 "SDG-02",
114 "Data Inventory Completeness",
115 "All data has documented purpose, classification, and lifecycle",
116 )
117 .with_severity(Severity::Major)
118 .with_tps("Muda (Inventory waste) — prevent data hoarding");
119
120 let inventory_files = [
122 project_path.join("data/inventory.yaml"),
123 project_path.join("docs/data-catalog.md"),
124 project_path.join(".datasheets/"),
125 project_path.join("data/schema/"),
126 ];
127
128 let has_inventory = inventory_files.iter().any(|p| p.exists());
129
130 let has_classification = check_for_pattern(
132 project_path,
133 &["DataClassification", "data_class", "classification_level"],
134 );
135
136 let has_lifecycle = check_for_pattern(
138 project_path,
139 &["lifecycle_policy", "retention_days", "data_ttl", "expiration"],
140 );
141
142 item = item.with_evidence(Evidence {
143 evidence_type: EvidenceType::StaticAnalysis,
144 description: format!(
145 "Data inventory: catalog={}, classification={}, lifecycle={}",
146 has_inventory, has_classification, has_lifecycle
147 ),
148 data: None,
149 files: Vec::new(),
150 });
151
152 let handles_data = check_for_pattern(project_path, &["DataFrame", "Dataset", "DataLoader"]);
153 item = apply_check_outcome(
154 item,
155 &[
156 (has_inventory && has_classification && has_lifecycle, None),
157 (
158 has_inventory || has_classification,
159 Some("Partial data inventory (missing lifecycle or classification)"),
160 ),
161 (!handles_data, None),
162 (true, Some("No data inventory documentation")),
163 ],
164 );
165
166 item.finish_timed(start)
167}
168
169pub fn check_privacy_preserving_computation(project_path: &Path) -> CheckItem {
176 let start = Instant::now();
177 let mut item = CheckItem::new(
178 "SDG-03",
179 "Privacy-Preserving Computation",
180 "Differential privacy correctly implemented",
181 )
182 .with_severity(Severity::Major)
183 .with_tps("Poka-Yoke — privacy by design");
184
185 let has_dp = check_for_pattern(
187 project_path,
188 &[
189 "differential_privacy",
190 "epsilon",
191 "privacy_budget",
192 "laplace_noise",
193 "gaussian_mechanism",
194 ],
195 );
196
197 let has_budget_tracking =
199 check_for_pattern(project_path, &["privacy_accountant", "budget_consumed", "composition"]);
200
201 let has_privacy_tests = check_for_test_pattern(project_path, &["privacy", "dp_test"]);
203
204 item = item.with_evidence(Evidence {
205 evidence_type: EvidenceType::StaticAnalysis,
206 description: format!(
207 "Privacy: dp_impl={}, budget_tracking={}, tests={}",
208 has_dp, has_budget_tracking, has_privacy_tests
209 ),
210 data: None,
211 files: Vec::new(),
212 });
213
214 let needs_privacy = check_for_pattern(project_path, &["pii", "personal_data", "gdpr"]);
215 item = apply_check_outcome(
216 item,
217 &[
218 (has_dp && has_budget_tracking && has_privacy_tests, None),
219 (has_dp, Some("DP implemented but missing budget tracking or tests")),
220 (!needs_privacy, None),
221 (true, Some("PII handling without differential privacy")),
222 ],
223 );
224
225 item.finish_timed(start)
226}
227
228pub fn check_federated_learning_isolation(project_path: &Path) -> CheckItem {
235 let start = Instant::now();
236 let mut item = CheckItem::new(
237 "SDG-04",
238 "Federated Learning Client Isolation",
239 "FL clients send only model updates, never raw data",
240 )
241 .with_severity(Severity::Critical)
242 .with_tps("Jidoka — client-side enforcement");
243
244 let has_fl = check_for_pattern(
246 project_path,
247 &["federated", "FederatedClient", "model_update", "gradient_only"],
248 );
249
250 let has_secure_agg = check_for_pattern(
252 project_path,
253 &["secure_aggregation", "SecureAggregator", "encrypted_gradient"],
254 );
255
256 let has_isolation_tests =
258 check_for_test_pattern(project_path, &["client_isolation", "no_raw_data"]);
259
260 item = item.with_evidence(Evidence {
261 evidence_type: EvidenceType::StaticAnalysis,
262 description: format!(
263 "FL isolation: fl_impl={}, secure_agg={}, tests={}",
264 has_fl, has_secure_agg, has_isolation_tests
265 ),
266 data: None,
267 files: Vec::new(),
268 });
269
270 item = apply_check_outcome(
271 item,
272 &[
273 (!has_fl, None),
274 (has_fl && has_secure_agg && has_isolation_tests, None),
275 (
276 has_fl && has_secure_agg,
277 Some("FL with secure aggregation (missing isolation tests)"),
278 ),
279 (true, Some("FL without secure aggregation")),
280 ],
281 );
282
283 item.finish_timed(start)
284}
285
286pub fn check_supply_chain_provenance(project_path: &Path) -> CheckItem {
293 let start = Instant::now();
294 let mut item = CheckItem::new(
295 "SDG-05",
296 "Supply Chain Provenance (AI BOM)",
297 "All model weights and data have verified provenance",
298 )
299 .with_severity(Severity::Critical)
300 .with_tps("Jidoka — supply chain circuit breaker");
301
302 let bom_files = [
304 project_path.join("ai-bom.json"),
305 project_path.join("sbom.json"),
306 project_path.join("bom.xml"),
307 project_path.join(".sbom/"),
308 ];
309
310 let has_bom = bom_files.iter().any(|p| p.exists());
311
312 let cargo_toml = project_path.join("Cargo.toml");
314 let has_audit = cargo_toml
315 .exists()
316 .then(|| std::fs::read_to_string(&cargo_toml).ok())
317 .flatten()
318 .map(|c| c.contains("cargo-deny") || c.contains("cargo-audit"))
319 .unwrap_or(false);
320
321 let deny_toml = project_path.join("deny.toml");
323 let has_deny_config = deny_toml.exists();
324
325 let has_signature_check = check_for_pattern(
327 project_path,
328 &["verify_signature", "Ed25519", "sign_model", "provenance"],
329 );
330
331 item = item.with_evidence(Evidence {
332 evidence_type: EvidenceType::StaticAnalysis,
333 description: format!(
334 "Supply chain: bom={}, audit={}, deny={}, signatures={}",
335 has_bom, has_audit, has_deny_config, has_signature_check
336 ),
337 data: None,
338 files: Vec::new(),
339 });
340
341 item = apply_check_outcome(
342 item,
343 &[
344 (has_deny_config && (has_bom || has_signature_check), None),
345 (has_deny_config || has_audit, Some("Dependency audit configured (missing AI BOM)")),
346 (true, Some("No supply chain verification configured")),
347 ],
348 );
349
350 item.finish_timed(start)
351}
352
353pub fn check_vpc_isolation(project_path: &Path) -> CheckItem {
360 let start = Instant::now();
361 let mut item = CheckItem::new(
362 "SDG-06",
363 "VPC Isolation Verification",
364 "Compute only in compliant sovereign regions",
365 )
366 .with_severity(Severity::Major)
367 .with_tps("Genchi Genbutsu — verify physical location");
368
369 let iac_files = [
371 project_path.join("terraform/"),
372 project_path.join("infrastructure/"),
373 project_path.join("pulumi/"),
374 project_path.join("cloudformation/"),
375 ];
376
377 let has_iac = iac_files.iter().any(|p| p.exists());
378
379 let has_region_policy = check_for_pattern(
381 project_path,
382 &["allowed_regions", "region_whitelist", "sovereign_region"],
383 );
384
385 item = item.with_evidence(Evidence {
386 evidence_type: EvidenceType::StaticAnalysis,
387 description: format!("VPC: iac={}, region_policy={}", has_iac, has_region_policy),
388 data: None,
389 files: Vec::new(),
390 });
391
392 item = apply_check_outcome(
393 item,
394 &[
395 (!has_iac, None),
396 (has_iac && has_region_policy, None),
397 (true, Some("IaC without explicit region constraints")),
398 ],
399 );
400
401 item.finish_timed(start)
402}
403
404pub fn check_data_classification_enforcement(project_path: &Path) -> CheckItem {
411 let start = Instant::now();
412 let mut item = CheckItem::new(
413 "SDG-07",
414 "Data Classification Enforcement",
415 "Code paths enforce data classification levels",
416 )
417 .with_severity(Severity::Major)
418 .with_tps("Poka-Yoke — classification-based routing");
419
420 let has_type_classification = check_for_pattern(
422 project_path,
423 &["Classification", "DataTier", "SecurityLevel", "Sovereign<", "Confidential<"],
424 );
425
426 let has_runtime_check = check_for_pattern(
428 project_path,
429 &["check_classification", "enforce_tier", "validate_access_level"],
430 );
431
432 item = item.with_evidence(Evidence {
433 evidence_type: EvidenceType::StaticAnalysis,
434 description: format!(
435 "Classification: type_level={}, runtime={}",
436 has_type_classification, has_runtime_check
437 ),
438 data: None,
439 files: Vec::new(),
440 });
441
442 let handles_classified =
443 check_for_pattern(project_path, &["sovereign", "confidential", "restricted"]);
444 item = apply_check_outcome(
445 item,
446 &[
447 (has_type_classification && has_runtime_check, None),
448 (
449 has_type_classification || has_runtime_check,
450 Some("Partial classification enforcement"),
451 ),
452 (!handles_classified, None),
453 (true, Some("Classified data without enforcement")),
454 ],
455 );
456
457 item.finish_timed(start)
458}
459
460pub fn check_consent_purpose_limitation(project_path: &Path) -> CheckItem {
467 let start = Instant::now();
468 let mut item = CheckItem::new(
469 "SDG-08",
470 "Consent and Purpose Limitation",
471 "Data usage matches documented consent scope",
472 )
473 .with_severity(Severity::Major)
474 .with_tps("Legal controls pillar");
475
476 let has_consent =
478 check_for_pattern(project_path, &["consent", "purpose_limitation", "data_usage_agreement"]);
479
480 let has_purpose_binding =
482 check_for_pattern(project_path, &["purpose_id", "usage_scope", "consent_scope"]);
483
484 item = item.with_evidence(Evidence {
485 evidence_type: EvidenceType::StaticAnalysis,
486 description: format!(
487 "Consent: management={}, purpose_binding={}",
488 has_consent, has_purpose_binding
489 ),
490 data: None,
491 files: Vec::new(),
492 });
493
494 let handles_user_data = check_for_pattern(project_path, &["user_data", "personal", "pii"]);
495 item = apply_check_outcome(
496 item,
497 &[
498 (!handles_user_data, None),
499 (has_consent && has_purpose_binding, None),
500 (has_consent, Some("Consent tracking without purpose binding")),
501 (true, Some("User data handling without consent management")),
502 ],
503 );
504
505 item.finish_timed(start)
506}
507
508pub fn check_rtbf_compliance(project_path: &Path) -> CheckItem {
515 let start = Instant::now();
516 let mut item = CheckItem::new(
517 "SDG-09",
518 "Right to Erasure (RTBF) Compliance",
519 "Deletion requests propagate through all storage",
520 )
521 .with_severity(Severity::Major)
522 .with_tps("Muda — data inventory hygiene");
523
524 let has_deletion = check_for_pattern(
526 project_path,
527 &["delete_user", "erasure", "rtbf", "forget_user", "cascade_delete"],
528 );
529
530 let has_unlearning =
532 check_for_pattern(project_path, &["unlearn", "model_forget", "data_removal"]);
533
534 item = item.with_evidence(Evidence {
535 evidence_type: EvidenceType::StaticAnalysis,
536 description: format!(
537 "RTBF: deletion_cascade={}, model_unlearning={}",
538 has_deletion, has_unlearning
539 ),
540 data: None,
541 files: Vec::new(),
542 });
543
544 let stores_user_data = check_for_pattern(project_path, &["user_store", "persist_user", "save"]);
545 item = apply_check_outcome(
546 item,
547 &[
548 (!stores_user_data, None),
549 (has_deletion && has_unlearning, None),
550 (has_deletion, Some("Deletion without model unlearning")),
551 (true, Some("Data persistence without erasure mechanism")),
552 ],
553 );
554
555 item.finish_timed(start)
556}
557
558pub fn check_cross_border_logging(project_path: &Path) -> CheckItem {
565 let start = Instant::now();
566 let mut item = CheckItem::new(
567 "SDG-10",
568 "Cross-Border Transfer Logging",
569 "All cross-border transfers logged and justified",
570 )
571 .with_severity(Severity::Major)
572 .with_tps("Auditability requirement");
573
574 let has_transfer_log = check_for_pattern(
576 project_path,
577 &["transfer_log", "cross_border", "data_export", "international_transfer"],
578 );
579
580 let has_legal_basis = check_for_pattern(
582 project_path,
583 &["legal_basis", "transfer_agreement", "adequacy_decision"],
584 );
585
586 item = item.with_evidence(Evidence {
587 evidence_type: EvidenceType::StaticAnalysis,
588 description: format!(
589 "Cross-border: logging={}, legal_basis={}",
590 has_transfer_log, has_legal_basis
591 ),
592 data: None,
593 files: Vec::new(),
594 });
595
596 let does_transfers = has_network_code(project_path);
597 item = apply_check_outcome(
598 item,
599 &[
600 (!does_transfers, None),
601 (has_transfer_log && has_legal_basis, None),
602 (has_transfer_log, Some("Transfer logging without legal basis")),
603 (true, Some("Network operations without transfer logging")),
604 ],
605 );
606
607 item.finish_timed(start)
608}
609
610pub fn check_model_weight_sovereignty(project_path: &Path) -> CheckItem {
617 let start = Instant::now();
618 let mut item = CheckItem::new(
619 "SDG-11",
620 "Model Weight Sovereignty",
621 "Weights from sovereign data remain under sovereign control",
622 )
623 .with_severity(Severity::Critical)
624 .with_tps("Jidoka — weight exfiltration prevention");
625
626 let has_access_control = check_for_pattern(
628 project_path,
629 &["weight_access", "model_acl", "sovereign_model", "protected_weights"],
630 );
631
632 let has_encryption =
634 check_for_pattern(project_path, &["encrypt_weights", "sealed_model", "encrypted_model"]);
635
636 let has_key_mgmt = check_for_pattern(project_path, &["key_management", "kms", "key_rotation"]);
638
639 item = item.with_evidence(Evidence {
640 evidence_type: EvidenceType::StaticAnalysis,
641 description: format!(
642 "Weight sovereignty: access_control={}, encryption={}, key_mgmt={}",
643 has_access_control, has_encryption, has_key_mgmt
644 ),
645 data: None,
646 files: Vec::new(),
647 });
648
649 let handles_weights =
650 check_for_pattern(project_path, &["model_weights", "load_model", "save_model"]);
651 item = apply_check_outcome(
652 item,
653 &[
654 (!handles_weights, None),
655 (has_access_control && has_encryption, None),
656 (has_access_control || has_encryption, Some("Partial weight protection")),
657 (true, Some("Model weights without sovereignty controls")),
658 ],
659 );
660
661 item.finish_timed(start)
662}
663
664pub fn check_inference_classification(project_path: &Path) -> CheckItem {
671 let start = Instant::now();
672 let mut item = CheckItem::new(
673 "SDG-12",
674 "Inference Result Classification",
675 "Outputs inherit classification from inputs",
676 )
677 .with_severity(Severity::Major)
678 .with_tps("Poka-Yoke — automatic classification propagation");
679
680 let has_inheritance = check_for_pattern(
682 project_path,
683 &["inherit_classification", "propagate_tier", "output_classification"],
684 );
685
686 let has_output_tagging =
688 check_for_pattern(project_path, &["tag_output", "classify_result", "result_tier"]);
689
690 item = item.with_evidence(Evidence {
691 evidence_type: EvidenceType::StaticAnalysis,
692 description: format!(
693 "Inference classification: inheritance={}, tagging={}",
694 has_inheritance, has_output_tagging
695 ),
696 data: None,
697 files: Vec::new(),
698 });
699
700 let does_inference = check_for_pattern(project_path, &["inference", "predict", "infer"]);
701 item = apply_check_outcome(
702 item,
703 &[
704 (!does_inference, None),
705 (has_inheritance && has_output_tagging, None),
706 (has_inheritance || has_output_tagging, Some("Partial classification propagation")),
707 (true, Some("Inference without classification propagation")),
708 ],
709 );
710
711 item.finish_timed(start)
712}
713
714pub fn check_audit_log_immutability(project_path: &Path) -> CheckItem {
721 let start = Instant::now();
722 let mut item = CheckItem::new(
723 "SDG-13",
724 "Audit Log Immutability",
725 "Audit logs cannot be modified or deleted",
726 )
727 .with_severity(Severity::Critical)
728 .with_tps("Governance layer integrity");
729
730 let has_append_only =
732 check_for_pattern(project_path, &["append_only", "immutable_log", "write_once"]);
733
734 let has_chaining = check_for_pattern(
736 project_path,
737 &["merkle", "hash_chain", "log_signature", "tamper_evident"],
738 );
739
740 let has_audit_trail =
742 check_for_pattern(project_path, &["audit_trail", "audit_log", "AuditEntry"]);
743
744 item = item.with_evidence(Evidence {
745 evidence_type: EvidenceType::StaticAnalysis,
746 description: format!(
747 "Audit immutability: append_only={}, chaining={}, trail={}",
748 has_append_only, has_chaining, has_audit_trail
749 ),
750 data: None,
751 files: Vec::new(),
752 });
753
754 item = apply_check_outcome(
755 item,
756 &[
757 (has_audit_trail && has_chaining, None),
758 (has_audit_trail, Some("Audit trail without cryptographic verification")),
759 (true, Some("No immutable audit logging")),
760 ],
761 );
762
763 item.finish_timed(start)
764}
765
766pub fn check_third_party_isolation(project_path: &Path) -> CheckItem {
773 let start = Instant::now();
774 let mut item = CheckItem::new(
775 "SDG-14",
776 "Third-Party API Isolation",
777 "No third-party API calls during sovereign operations",
778 )
779 .with_severity(Severity::Critical)
780 .with_tps("Jidoka — network isolation");
781
782 let has_allowlist = check_for_pattern(
784 project_path,
785 &["allowlist", "whitelist", "approved_endpoints", "sovereign_endpoints"],
786 );
787
788 let has_offline_mode =
790 check_for_pattern(project_path, &["offline_mode", "airgap", "no_network", "local_only"]);
791
792 let has_network_guard =
794 check_for_pattern(project_path, &["network_guard", "egress_filter", "outbound_check"]);
795
796 item = item.with_evidence(Evidence {
797 evidence_type: EvidenceType::StaticAnalysis,
798 description: format!(
799 "Third-party isolation: allowlist={}, offline={}, guard={}",
800 has_allowlist, has_offline_mode, has_network_guard
801 ),
802 data: None,
803 files: Vec::new(),
804 });
805
806 let does_network = has_network_code(project_path);
807 item = apply_check_outcome(
808 item,
809 &[
810 (!does_network || has_offline_mode, None),
811 (has_allowlist && has_network_guard, None),
812 (has_allowlist, Some("Allowlist without runtime guard")),
813 (true, Some("Network operations without isolation controls")),
814 ],
815 );
816
817 item.finish_timed(start)
818}
819
820pub fn check_secure_computation(project_path: &Path) -> CheckItem {
827 let start = Instant::now();
828 let mut item = CheckItem::new(
829 "SDG-15",
830 "Secure Computation Verification",
831 "Cryptographic primitives correctly implemented",
832 )
833 .with_severity(Severity::Major)
834 .with_tps("Formal verification requirement");
835
836 let has_he = check_for_pattern(project_path, &["homomorphic", "fhe", "seal", "paillier"]);
838
839 let has_mpc = check_for_pattern(project_path, &["mpc", "secure_multiparty", "secret_sharing"]);
841
842 let has_tee = check_for_pattern(project_path, &["sgx", "enclave", "trusted_execution", "tee"]);
844
845 let has_crypto_tests =
847 check_for_test_pattern(project_path, &["crypto", "encryption", "secure_compute"]);
848
849 item = item.with_evidence(Evidence {
850 evidence_type: EvidenceType::StaticAnalysis,
851 description: format!(
852 "Secure compute: he={}, mpc={}, tee={}, tests={}",
853 has_he, has_mpc, has_tee, has_crypto_tests
854 ),
855 data: None,
856 files: Vec::new(),
857 });
858
859 let needs_secure_compute = has_he || has_mpc || has_tee;
860 item = apply_check_outcome(
861 item,
862 &[
863 (!needs_secure_compute, None),
864 (needs_secure_compute && has_crypto_tests, None),
865 (true, Some("Secure computation without comprehensive tests")),
866 ],
867 );
868
869 item.finish_timed(start)
870}
871
872fn apply_check_outcome(item: CheckItem, checks: &[(bool, Option<&str>)]) -> CheckItem {
882 for &(condition, msg) in checks {
883 if condition {
884 return match msg {
885 Some(m) => item.partial(m),
886 None => item.pass(),
887 };
888 }
889 }
890 item
891}
892
893fn check_for_pattern(project_path: &Path, patterns: &[&str]) -> bool {
895 super::helpers::source_or_config_contains_pattern(project_path, patterns)
896}
897
898fn check_for_test_pattern(project_path: &Path, patterns: &[&str]) -> bool {
900 super::helpers::test_contains_pattern(project_path, patterns)
901}
902
903fn has_network_code(project_path: &Path) -> bool {
905 check_for_pattern(
906 project_path,
907 &["reqwest", "hyper", "tonic", "TcpStream", "HttpClient", "fetch", "grpc"],
908 )
909}
910
911#[cfg(test)]
916mod tests {
917 use super::*;
918 use std::path::PathBuf;
919
920 #[test]
921 fn test_evaluate_all_returns_15_items() {
922 let path = PathBuf::from(".");
923 let items = evaluate_all(&path);
924 assert_eq!(items.len(), 15);
925 }
926
927 #[test]
928 fn test_all_items_have_tps_principle() {
929 let path = PathBuf::from(".");
930 let items = evaluate_all(&path);
931 for item in items {
932 assert!(!item.tps_principle.is_empty(), "Item {} missing TPS principle", item.id);
933 }
934 }
935
936 #[test]
937 fn test_all_items_have_evidence() {
938 let path = PathBuf::from(".");
939 let items = evaluate_all(&path);
940 for item in items {
941 assert!(!item.evidence.is_empty(), "Item {} missing evidence", item.id);
942 }
943 }
944
945 #[test]
946 fn test_sdg_01_data_residency() {
947 let path = PathBuf::from(".");
948 let item = check_data_residency_boundary(&path);
949 assert_eq!(item.id, "SDG-01");
950 assert_eq!(item.severity, Severity::Critical);
951 }
952
953 #[test]
954 fn test_sdg_05_supply_chain() {
955 let path = PathBuf::from(".");
956 let item = check_supply_chain_provenance(&path);
957 assert_eq!(item.id, "SDG-05");
958 assert_eq!(item.severity, Severity::Critical);
959 }
960
961 #[test]
962 fn test_sdg_13_audit_log() {
963 let path = PathBuf::from(".");
964 let item = check_audit_log_immutability(&path);
965 assert_eq!(item.id, "SDG-13");
966 assert_eq!(item.severity, Severity::Critical);
967 }
968
969 #[test]
974 fn test_sdg_02_data_inventory() {
975 let path = PathBuf::from(".");
976 let item = check_data_inventory_completeness(&path);
977 assert_eq!(item.id, "SDG-02");
978 }
979
980 #[test]
981 fn test_sdg_03_privacy_preserving() {
982 let path = PathBuf::from(".");
983 let item = check_privacy_preserving_computation(&path);
984 assert_eq!(item.id, "SDG-03");
985 }
986
987 #[test]
988 fn test_sdg_04_federated_learning() {
989 let path = PathBuf::from(".");
990 let item = check_federated_learning_isolation(&path);
991 assert_eq!(item.id, "SDG-04");
992 }
993
994 #[test]
995 fn test_sdg_06_vpc_isolation() {
996 let path = PathBuf::from(".");
997 let item = check_vpc_isolation(&path);
998 assert_eq!(item.id, "SDG-06");
999 }
1000
1001 #[test]
1002 fn test_sdg_07_data_classification() {
1003 let path = PathBuf::from(".");
1004 let item = check_data_classification_enforcement(&path);
1005 assert_eq!(item.id, "SDG-07");
1006 }
1007
1008 #[test]
1009 fn test_sdg_08_consent_purpose() {
1010 let path = PathBuf::from(".");
1011 let item = check_consent_purpose_limitation(&path);
1012 assert_eq!(item.id, "SDG-08");
1013 }
1014
1015 #[test]
1016 fn test_sdg_09_rtbf_compliance() {
1017 let path = PathBuf::from(".");
1018 let item = check_rtbf_compliance(&path);
1019 assert_eq!(item.id, "SDG-09");
1020 }
1021
1022 #[test]
1023 fn test_sdg_10_cross_border() {
1024 let path = PathBuf::from(".");
1025 let item = check_cross_border_logging(&path);
1026 assert_eq!(item.id, "SDG-10");
1027 }
1028
1029 #[test]
1030 fn test_sdg_11_model_weight() {
1031 let path = PathBuf::from(".");
1032 let item = check_model_weight_sovereignty(&path);
1033 assert_eq!(item.id, "SDG-11");
1034 }
1035
1036 #[test]
1037 fn test_sdg_12_inference_classification() {
1038 let path = PathBuf::from(".");
1039 let item = check_inference_classification(&path);
1040 assert_eq!(item.id, "SDG-12");
1041 }
1042
1043 #[test]
1044 fn test_sdg_14_third_party() {
1045 let path = PathBuf::from(".");
1046 let item = check_third_party_isolation(&path);
1047 assert_eq!(item.id, "SDG-14");
1048 }
1049
1050 #[test]
1051 fn test_sdg_15_secure_computation() {
1052 let path = PathBuf::from(".");
1053 let item = check_secure_computation(&path);
1054 assert_eq!(item.id, "SDG-15");
1055 }
1056
1057 #[test]
1058 fn test_nonexistent_path() {
1059 let path = PathBuf::from("/nonexistent/path");
1060 let items = evaluate_all(&path);
1061 assert_eq!(items.len(), 15);
1062 }
1063
1064 #[test]
1065 fn test_temp_dir_with_privacy_lib() {
1066 let temp_dir = std::env::temp_dir().join("test_privacy_lib");
1067 let _ = std::fs::remove_dir_all(&temp_dir);
1068 std::fs::create_dir_all(&temp_dir).expect("mkdir failed");
1069
1070 std::fs::write(
1071 temp_dir.join("Cargo.toml"),
1072 r#"
1073[package]
1074name = "test"
1075version = "0.1.0"
1076
1077[dependencies]
1078differential-privacy = "0.1"
1079"#,
1080 )
1081 .expect("unexpected failure");
1082
1083 let item = check_privacy_preserving_computation(&temp_dir);
1084 assert_eq!(item.id, "SDG-03");
1085
1086 let _ = std::fs::remove_dir_all(&temp_dir);
1087 }
1088
1089 #[test]
1090 fn test_temp_dir_with_data_types() {
1091 let temp_dir = std::env::temp_dir().join("test_data_types");
1092 let _ = std::fs::remove_dir_all(&temp_dir);
1093 std::fs::create_dir_all(temp_dir.join("src")).expect("mkdir failed");
1094
1095 std::fs::write(
1096 temp_dir.join("src/lib.rs"),
1097 r#"
1098pub struct DataInventory {
1099 pub pii: bool,
1100 pub classification: String,
1101}
1102"#,
1103 )
1104 .expect("unexpected failure");
1105
1106 let item = check_data_inventory_completeness(&temp_dir);
1107 assert_eq!(item.id, "SDG-02");
1108
1109 let _ = std::fs::remove_dir_all(&temp_dir);
1110 }
1111
1112 #[test]
1113 fn test_temp_dir_with_audit_logging() {
1114 let temp_dir = std::env::temp_dir().join("test_audit_log");
1115 let _ = std::fs::remove_dir_all(&temp_dir);
1116 std::fs::create_dir_all(temp_dir.join("src")).expect("mkdir failed");
1117
1118 std::fs::write(
1119 temp_dir.join("src/lib.rs"),
1120 r#"
1121pub struct AuditLog {
1122 pub append_only: bool,
1123 pub immutable: bool,
1124}
1125"#,
1126 )
1127 .expect("unexpected failure");
1128
1129 let item = check_audit_log_immutability(&temp_dir);
1130 assert_eq!(item.id, "SDG-13");
1131
1132 let _ = std::fs::remove_dir_all(&temp_dir);
1133 }
1134
1135 #[test]
1136 fn test_all_have_severity() {
1137 let path = PathBuf::from(".");
1138 let items = evaluate_all(&path);
1139 for item in items {
1140 assert!(
1142 item.severity == Severity::Critical
1143 || item.severity == Severity::Major
1144 || item.severity == Severity::Minor
1145 );
1146 }
1147 }
1148}