libverify_core/controls/
default_branch_settings_baseline.rs1use crate::control::{Control, ControlFinding, ControlId, builtin};
2use crate::evidence::EvidenceBundle;
3
4pub struct DefaultBranchSettingsBaselineControl;
17
18impl Control for DefaultBranchSettingsBaselineControl {
19 fn id(&self) -> ControlId {
20 builtin::id(builtin::DEFAULT_BRANCH_SETTINGS_BASELINE)
21 }
22
23 fn description(&self) -> &'static str {
24 "Default branch must have protection baseline: protection enabled, admin enforcement, stale review dismissal"
25 }
26
27 fn evaluate(&self, evidence: &EvidenceBundle) -> Vec<ControlFinding> {
28 let posture = match ControlFinding::extract_posture(self.id(), evidence) {
29 Ok(p) => p,
30 Err(findings) => return findings,
31 };
32
33 let mut missing = Vec::new();
34
35 if !posture.default_branch_protected {
36 missing.push("branch protection not enabled");
37 }
38 if !posture.enforce_admins {
39 missing.push("admin enforcement not enabled");
40 }
41 if !posture.dismiss_stale_reviews {
42 missing.push("stale review dismissal not enabled");
43 }
44
45 if missing.is_empty() {
46 vec![ControlFinding::satisfied(
47 self.id(),
48 "Default branch meets security baseline: protection, admin enforcement, stale review dismissal all enabled",
49 vec!["repository:branch-protection:baseline".into()],
50 )]
51 } else {
52 vec![ControlFinding::violated(
53 self.id(),
54 format!(
55 "Default branch does not meet security baseline: {}",
56 missing.join(", ")
57 ),
58 vec!["repository:branch-protection:baseline".into()],
59 )]
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::control::ControlStatus;
68 use crate::evidence::{EvidenceState, RepositoryPosture};
69
70 fn bundle_with(protected: bool, enforce: bool, dismiss: bool) -> EvidenceBundle {
71 EvidenceBundle {
72 repository_posture: EvidenceState::complete(RepositoryPosture {
73 default_branch_protected: protected,
74 enforce_admins: enforce,
75 dismiss_stale_reviews: dismiss,
76 ..Default::default()
77 }),
78 ..Default::default()
79 }
80 }
81
82 #[test]
83 fn satisfied_when_all_enabled() {
84 let findings =
85 DefaultBranchSettingsBaselineControl.evaluate(&bundle_with(true, true, true));
86 assert_eq!(findings[0].status, ControlStatus::Satisfied);
87 }
88
89 #[test]
90 fn violated_when_no_protection() {
91 let findings =
92 DefaultBranchSettingsBaselineControl.evaluate(&bundle_with(false, false, false));
93 assert_eq!(findings[0].status, ControlStatus::Violated);
94 assert!(
95 findings[0]
96 .rationale
97 .contains("branch protection not enabled")
98 );
99 }
100
101 #[test]
102 fn violated_when_no_admin_enforcement() {
103 let findings =
104 DefaultBranchSettingsBaselineControl.evaluate(&bundle_with(true, false, true));
105 assert_eq!(findings[0].status, ControlStatus::Violated);
106 assert!(findings[0].rationale.contains("admin enforcement"));
107 }
108
109 #[test]
110 fn violated_when_no_stale_dismissal() {
111 let findings =
112 DefaultBranchSettingsBaselineControl.evaluate(&bundle_with(true, true, false));
113 assert_eq!(findings[0].status, ControlStatus::Violated);
114 assert!(findings[0].rationale.contains("stale review dismissal"));
115 }
116
117 #[test]
118 fn violated_lists_all_missing() {
119 let findings =
120 DefaultBranchSettingsBaselineControl.evaluate(&bundle_with(false, false, false));
121 assert_eq!(findings[0].status, ControlStatus::Violated);
122 assert!(
123 findings[0]
124 .rationale
125 .contains("branch protection not enabled")
126 );
127 assert!(findings[0].rationale.contains("admin enforcement"));
128 assert!(findings[0].rationale.contains("stale review dismissal"));
129 }
130
131 #[test]
132 fn indeterminate_when_posture_missing() {
133 let findings = DefaultBranchSettingsBaselineControl.evaluate(&EvidenceBundle {
134 repository_posture: EvidenceState::missing(vec![]),
135 ..Default::default()
136 });
137 assert_eq!(findings[0].status, ControlStatus::Indeterminate);
138 }
139}