1use libverify_core::control::{Control, ControlStatus};
10use libverify_core::controls::codeowners_coverage::CodeownersCoverageControl;
11use libverify_core::controls::secret_scanning::SecretScanningControl;
12use libverify_core::controls::security_policy::SecurityPolicyControl;
13use libverify_core::controls::vulnerability_scanning::VulnerabilityScanningControl;
14use libverify_core::evidence::{CodeownersEntry, EvidenceBundle, EvidenceState, RepositoryPosture};
15use libverify_core::profile::{ControlProfile, GateDecision};
16use libverify_policy::OpaProfile;
17
18fn default_posture() -> RepositoryPosture {
23 RepositoryPosture {
24 codeowners_entries: vec![],
25 secret_scanning_enabled: false,
26 secret_push_protection_enabled: false,
27 vulnerability_scanning_enabled: false,
28 code_scanning_enabled: false,
29 security_policy_present: false,
30 security_policy_has_disclosure: false,
31 default_branch_protected: false,
32 }
33}
34
35fn bundle_from_posture(posture: RepositoryPosture) -> EvidenceBundle {
36 EvidenceBundle {
37 repository_posture: EvidenceState::complete(posture),
38 ..Default::default()
39 }
40}
41
42fn entry(pattern: &str, owners: &[&str]) -> CodeownersEntry {
43 CodeownersEntry {
44 pattern: pattern.to_string(),
45 owners: owners.iter().map(|s| s.to_string()).collect(),
46 }
47}
48
49struct ScenarioResult {
54 name: &'static str,
55 expected_status: ControlStatus,
56 actual_status: ControlStatus,
57 rationale: String,
58 subjects: Vec<String>,
59 oss_decision: GateDecision,
60 soc2_decision: GateDecision,
61}
62
63fn run_scenario(
64 name: &'static str,
65 control: &dyn Control,
66 bundle: &EvidenceBundle,
67 expected_status: ControlStatus,
68 oss: &dyn ControlProfile,
69 soc2: &dyn ControlProfile,
70) -> ScenarioResult {
71 let findings = control.evaluate(bundle);
72 let finding = &findings[0];
73 let oss_outcome = oss.map(finding);
74 let soc2_outcome = soc2.map(finding);
75
76 ScenarioResult {
77 name,
78 expected_status,
79 actual_status: finding.status,
80 rationale: finding.rationale.clone(),
81 subjects: finding.subjects.clone(),
82 oss_decision: oss_outcome.decision,
83 soc2_decision: soc2_outcome.decision,
84 }
85}
86
87fn main() {
92 let oss = OpaProfile::oss_preset().expect("oss preset");
93 let soc2 = OpaProfile::soc2_preset().expect("soc2 preset");
94
95 let secret_ctrl = SecretScanningControl;
96 let codeowners_ctrl = CodeownersCoverageControl;
97 let vuln_ctrl = VulnerabilityScanningControl;
98 let secpol_ctrl = SecurityPolicyControl;
99
100 let mut posture_a = default_posture();
103 posture_a.secret_scanning_enabled = true;
104 posture_a.secret_push_protection_enabled = false;
105 let bundle_a = bundle_from_posture(posture_a);
106
107 let mut posture_b = default_posture();
110 posture_b.codeowners_entries = vec![
111 entry("/src/auth/", &["@org/security"]),
112 entry("/infra/", &["@org/platform"]),
113 ];
114 let bundle_b = bundle_from_posture(posture_b);
115
116 let mut posture_c = default_posture();
119 posture_c.codeowners_entries = vec![
120 entry("/src/auth/", &["@org/security"]),
121 entry("/infra/", &["@org/platform"]),
122 entry("/.github/", &["@org/devops"]),
123 ];
124 let bundle_c = bundle_from_posture(posture_c);
125
126 let mut posture_d = default_posture();
129 posture_d.vulnerability_scanning_enabled = true;
130 posture_d.code_scanning_enabled = false;
131 let bundle_d = bundle_from_posture(posture_d);
132
133 let mut posture_e = default_posture();
136 posture_e.security_policy_present = true;
137 posture_e.security_policy_has_disclosure = false;
138 let bundle_e = bundle_from_posture(posture_e);
139
140 let results = vec![
141 run_scenario(
142 "A: Secret scanning ON, push protection OFF",
143 &secret_ctrl,
144 &bundle_a,
145 ControlStatus::Satisfied,
146 &oss,
147 &soc2,
148 ),
149 run_scenario(
150 "B: CODEOWNERS 2 entries (below threshold)",
151 &codeowners_ctrl,
152 &bundle_b,
153 ControlStatus::Violated,
154 &oss,
155 &soc2,
156 ),
157 run_scenario(
158 "C: CODEOWNERS 3 entries (at threshold)",
159 &codeowners_ctrl,
160 &bundle_c,
161 ControlStatus::Satisfied,
162 &oss,
163 &soc2,
164 ),
165 run_scenario(
166 "D: Vuln scanning ON, code scanning OFF",
167 &vuln_ctrl,
168 &bundle_d,
169 ControlStatus::Satisfied,
170 &oss,
171 &soc2,
172 ),
173 run_scenario(
174 "E: SECURITY.md present, no disclosure",
175 &secpol_ctrl,
176 &bundle_e,
177 ControlStatus::Violated,
178 &oss,
179 &soc2,
180 ),
181 ];
182
183 println!("=== SOC2 Type II Audit Edge-Case Scenarios ===\n");
185
186 let mut all_pass = true;
187 for r in &results {
188 let status_ok = r.actual_status == r.expected_status;
189 let marker = if status_ok { "PASS" } else { "FAIL" };
190 if !status_ok {
191 all_pass = false;
192 }
193
194 println!("--- {} ---", r.name);
195 println!(
196 " Status: {} (expected: {}) [{}]",
197 r.actual_status, r.expected_status, marker
198 );
199 println!(" Rationale: {}", r.rationale);
200 println!(" Subjects: {:?}", r.subjects);
201 println!(
202 " OSS decision: {:?} SOC2 decision: {:?}",
203 r.oss_decision, r.soc2_decision
204 );
205 println!();
206 }
207
208 println!("=== Policy Differentiation Summary ===\n");
210 for r in &results {
211 let diff = if r.oss_decision != r.soc2_decision {
212 format!(
213 "DIFFER (OSS={:?}, SOC2={:?})",
214 r.oss_decision, r.soc2_decision
215 )
216 } else {
217 format!("SAME ({:?})", r.oss_decision)
218 };
219 println!(" {}: {}", r.name, diff);
220 }
221
222 println!("\n=== Tiered Subject Verification ===\n");
224 let tier_checks: &[(&str, &str, usize)] = &[
225 ("A: detection tier", "detection", 0),
226 ("D: sca-only tier", "sca-only", 3),
227 ];
228 for (label, needle, idx) in tier_checks {
229 let found = results[*idx].subjects.iter().any(|s| s.contains(needle));
230 let mark = if found { "PRESENT" } else { "MISSING" };
231 println!(" {}: '{}' -> [{}]", label, needle, mark);
232 }
233
234 println!();
235 if all_pass {
236 println!("All scenarios produced the expected control status.");
237 } else {
238 println!("WARNING: Some scenarios did NOT match expected status.");
239 std::process::exit(1);
240 }
241}