libverify_core/
integrity.rs1use crate::verdict::Severity;
7
8pub fn short_sha(sha: &str) -> &str {
10 if sha.len() >= 7 { &sha[..7] } else { sha }
11}
12
13pub fn is_approver_independent(is_commit_author: bool, is_pr_author: bool) -> bool {
25 !is_commit_author && !is_pr_author
26}
27
28pub fn signature_severity(unsigned_count: usize) -> Severity {
31 if unsigned_count == 0 {
32 Severity::Pass
33 } else {
34 Severity::Error
35 }
36}
37
38pub fn build_provenance_severity(unverified_count: usize) -> Severity {
41 if unverified_count == 0 {
42 Severity::Pass
43 } else {
44 Severity::Error
45 }
46}
47
48pub fn required_status_checks_severity(fail_count: usize) -> Severity {
53 if fail_count == 0 {
54 Severity::Pass
55 } else {
56 Severity::Error
57 }
58}
59
60pub fn branch_history_severity(unprotected_count: usize) -> Severity {
65 if unprotected_count == 0 {
66 Severity::Pass
67 } else {
68 Severity::Error
69 }
70}
71
72pub fn branch_protection_enforcement_severity(non_enforced_count: usize) -> Severity {
77 if non_enforced_count == 0 {
78 Severity::Pass
79 } else {
80 Severity::Error
81 }
82}
83
84pub fn two_party_review_severity(independent_count: usize) -> Severity {
89 if independent_count >= 2 {
90 Severity::Pass
91 } else {
92 Severity::Error
93 }
94}
95
96pub fn hosted_build_severity(non_hosted_count: usize) -> Severity {
101 if non_hosted_count == 0 {
102 Severity::Pass
103 } else {
104 Severity::Error
105 }
106}
107
108pub fn provenance_authenticity_severity(unauthenticated_count: usize) -> Severity {
113 if unauthenticated_count == 0 {
114 Severity::Pass
115 } else {
116 Severity::Error
117 }
118}
119
120pub fn build_isolation_severity(non_isolated_count: usize) -> Severity {
125 if non_isolated_count == 0 {
126 Severity::Pass
127 } else {
128 Severity::Error
129 }
130}
131
132pub fn dependency_signature_severity(unsigned_count: usize) -> Severity {
137 if unsigned_count == 0 {
138 Severity::Pass
139 } else {
140 Severity::Error
141 }
142}
143
144pub fn stale_review_severity(stale_count: usize) -> Severity {
151 if stale_count == 0 {
152 Severity::Pass
153 } else {
154 Severity::Error
155 }
156}
157
158pub fn description_quality_severity(body_length: usize, min_length: usize) -> Severity {
163 if body_length >= min_length {
164 Severity::Pass
165 } else {
166 Severity::Error
167 }
168}
169
170pub fn merge_commit_policy_severity(merge_count: usize) -> Severity {
175 if merge_count == 0 {
176 Severity::Pass
177 } else {
178 Severity::Error
179 }
180}
181
182pub fn conventional_title_severity(is_conventional: bool) -> Severity {
187 if is_conventional {
188 Severity::Pass
189 } else {
190 Severity::Error
191 }
192}
193
194pub fn security_file_change_severity(sensitive_count: usize) -> Severity {
199 if sensitive_count == 0 {
200 Severity::Pass
201 } else {
202 Severity::Error
203 }
204}
205
206pub fn release_traceability_severity(linked_cr_count: usize) -> Severity {
211 if linked_cr_count > 0 {
212 Severity::Pass
213 } else {
214 Severity::Error
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
225 fn is_approver_independent_exhaustive_equivalence() {
226 for &ca in &[false, true] {
228 for &pa in &[false, true] {
229 let result = is_approver_independent(ca, pa);
230 let spec = !ca && !pa;
231 assert_eq!(
232 result, spec,
233 "is_approver_independent({ca}, {pa}): got {result}, spec {spec}"
234 );
235 }
236 }
237 }
238
239 #[test]
240 fn signature_severity_equivalence() {
241 assert_eq!(signature_severity(0), Severity::Pass);
242 for count in 1..=100 {
243 assert_eq!(
244 signature_severity(count),
245 Severity::Error,
246 "signature_severity({count}) should be Error"
247 );
248 }
249 }
250
251 #[test]
252 fn build_provenance_severity_equivalence() {
253 assert_eq!(build_provenance_severity(0), Severity::Pass);
254 for count in 1..=100 {
255 assert_eq!(
256 build_provenance_severity(count),
257 Severity::Error,
258 "build_provenance_severity({count}) should be Error"
259 );
260 }
261 }
262
263 #[test]
265 fn classify_scope_exhaustive_equivalence() {
266 use crate::scope::classify_scope;
267 for files in 0..=20usize {
268 for comps in 0..=20usize {
269 let result = classify_scope(files, comps);
270 let spec = if files <= 1 {
271 Severity::Pass
272 } else if comps <= 1 {
273 Severity::Pass
274 } else if comps == 2 {
275 Severity::Warning
276 } else {
277 Severity::Error
278 };
279 assert_eq!(
280 result, spec,
281 "classify_scope({files}, {comps}): got {result:?}, spec {spec:?}"
282 );
283 }
284 }
285 }
286
287 #[test]
288 fn required_status_checks_severity_equivalence() {
289 assert_eq!(required_status_checks_severity(0), Severity::Pass);
290 for count in 1..=10 {
291 assert_eq!(
292 required_status_checks_severity(count),
293 Severity::Error,
294 "required_status_checks_severity({count}) should be Error"
295 );
296 }
297 }
298
299 #[test]
300 fn branch_history_severity_equivalence() {
301 assert_eq!(branch_history_severity(0), Severity::Pass);
302 for count in 1..=10 {
303 assert_eq!(
304 branch_history_severity(count),
305 Severity::Error,
306 "branch_history_severity({count}) should be Error"
307 );
308 }
309 }
310
311 #[test]
312 fn branch_protection_enforcement_severity_equivalence() {
313 assert_eq!(branch_protection_enforcement_severity(0), Severity::Pass);
314 for count in 1..=10 {
315 assert_eq!(
316 branch_protection_enforcement_severity(count),
317 Severity::Error,
318 "branch_protection_enforcement_severity({count}) should be Error"
319 );
320 }
321 }
322
323 #[test]
324 fn two_party_review_severity_equivalence() {
325 assert_eq!(two_party_review_severity(0), Severity::Error);
326 assert_eq!(two_party_review_severity(1), Severity::Error);
327 assert_eq!(two_party_review_severity(2), Severity::Pass);
328 for count in 3..=10 {
329 assert_eq!(
330 two_party_review_severity(count),
331 Severity::Pass,
332 "two_party_review_severity({count}) should be Pass"
333 );
334 }
335 }
336
337 #[test]
338 fn hosted_build_severity_equivalence() {
339 assert_eq!(hosted_build_severity(0), Severity::Pass);
340 for count in 1..=10 {
341 assert_eq!(
342 hosted_build_severity(count),
343 Severity::Error,
344 "hosted_build_severity({count}) should be Error"
345 );
346 }
347 }
348
349 #[test]
350 fn provenance_authenticity_severity_equivalence() {
351 assert_eq!(provenance_authenticity_severity(0), Severity::Pass);
352 for count in 1..=10 {
353 assert_eq!(
354 provenance_authenticity_severity(count),
355 Severity::Error,
356 "provenance_authenticity_severity({count}) should be Error"
357 );
358 }
359 }
360
361 #[test]
362 fn build_isolation_severity_equivalence() {
363 assert_eq!(build_isolation_severity(0), Severity::Pass);
364 for count in 1..=10 {
365 assert_eq!(
366 build_isolation_severity(count),
367 Severity::Error,
368 "build_isolation_severity({count}) should be Error"
369 );
370 }
371 }
372
373 #[test]
376 fn stale_review_severity_equivalence() {
377 assert_eq!(stale_review_severity(0), Severity::Pass);
378 for count in 1..=10 {
379 assert_eq!(
380 stale_review_severity(count),
381 Severity::Error,
382 "stale_review_severity({count}) should be Error"
383 );
384 }
385 }
386
387 #[test]
388 fn description_quality_severity_equivalence() {
389 let min = 10;
390 for len in 0..min {
391 assert_eq!(
392 description_quality_severity(len, min),
393 Severity::Error,
394 "description_quality_severity({len}, {min}) should be Error"
395 );
396 }
397 for len in min..=50 {
398 assert_eq!(
399 description_quality_severity(len, min),
400 Severity::Pass,
401 "description_quality_severity({len}, {min}) should be Pass"
402 );
403 }
404 }
405
406 #[test]
407 fn merge_commit_policy_severity_equivalence() {
408 assert_eq!(merge_commit_policy_severity(0), Severity::Pass);
409 for count in 1..=10 {
410 assert_eq!(
411 merge_commit_policy_severity(count),
412 Severity::Error,
413 "merge_commit_policy_severity({count}) should be Error"
414 );
415 }
416 }
417
418 #[test]
419 fn conventional_title_severity_equivalence() {
420 assert_eq!(conventional_title_severity(true), Severity::Pass);
421 assert_eq!(conventional_title_severity(false), Severity::Error);
422 }
423
424 #[test]
425 fn security_file_change_severity_equivalence() {
426 assert_eq!(security_file_change_severity(0), Severity::Pass);
427 for count in 1..=10 {
428 assert_eq!(
429 security_file_change_severity(count),
430 Severity::Error,
431 "security_file_change_severity({count}) should be Error"
432 );
433 }
434 }
435
436 #[test]
437 fn release_traceability_severity_equivalence() {
438 assert_eq!(release_traceability_severity(0), Severity::Error);
439 for count in 1..=10 {
440 assert_eq!(
441 release_traceability_severity(count),
442 Severity::Pass,
443 "release_traceability_severity({count}) should be Pass"
444 );
445 }
446 }
447
448 #[test]
449 fn dependency_signature_severity_equivalence() {
450 assert_eq!(dependency_signature_severity(0), Severity::Pass);
451 for count in 1..=100 {
452 assert_eq!(
453 dependency_signature_severity(count),
454 Severity::Error,
455 "dependency_signature_severity({count}) should be Error"
456 );
457 }
458 }
459}