release_preflight.rs (9427B)
1 //! Closed, non-short-circuit aggregation for the release preflight. 2 3 use std::collections::{BTreeMap, BTreeSet}; 4 use std::fmt; 5 6 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 7 pub(crate) enum LaneId { 8 Catalog, 9 ServiceSourceLockContract, 10 ServiceBuildQualificationContract, 11 ServiceReleaseArtifactsContract, 12 PublicNativeGroup, 13 PreviewGroup, 14 ToolsGroup, 15 DtoRoots, 16 ProtocolFreshness, 17 ArtifactContracts, 18 ReleaseContracts, 19 } 20 21 impl LaneId { 22 pub(crate) const fn as_str(self) -> &'static str { 23 match self { 24 Self::Catalog => "catalog", 25 Self::ServiceSourceLockContract => "service_source_lock_contract", 26 Self::ServiceBuildQualificationContract => "service_build_qualification_contract", 27 Self::ServiceReleaseArtifactsContract => "service_release_artifacts_contract", 28 Self::PublicNativeGroup => "public_native_group", 29 Self::PreviewGroup => "preview_group", 30 Self::ToolsGroup => "tools_group", 31 Self::DtoRoots => "dto_roots", 32 Self::ProtocolFreshness => "protocol_freshness", 33 Self::ArtifactContracts => "artifact_contracts", 34 Self::ReleaseContracts => "release_contracts", 35 } 36 } 37 } 38 39 pub(crate) const REQUIRED_LANES: [LaneId; 11] = [ 40 LaneId::Catalog, 41 LaneId::ServiceSourceLockContract, 42 LaneId::ServiceBuildQualificationContract, 43 LaneId::ServiceReleaseArtifactsContract, 44 LaneId::PublicNativeGroup, 45 LaneId::PreviewGroup, 46 LaneId::ToolsGroup, 47 LaneId::DtoRoots, 48 LaneId::ProtocolFreshness, 49 LaneId::ArtifactContracts, 50 LaneId::ReleaseContracts, 51 ]; 52 53 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 54 pub(crate) enum LaneState { 55 Failed, 56 Interrupted, 57 Pass, 58 Skipped, 59 Unavailable, 60 } 61 62 #[derive(Clone, Debug, Eq, PartialEq)] 63 pub(crate) struct LaneOutcome { 64 id: String, 65 state: LaneState, 66 } 67 68 impl LaneOutcome { 69 pub(crate) fn required(id: LaneId, state: LaneState) -> Self { 70 Self { 71 id: id.as_str().to_owned(), 72 state, 73 } 74 } 75 76 fn named(id: &str, state: LaneState) -> Self { 77 Self { 78 id: id.to_owned(), 79 state, 80 } 81 } 82 } 83 84 #[derive(Clone, Debug, Eq, PartialEq)] 85 pub(crate) struct PreflightReport { 86 outcomes: Vec<LaneOutcome>, 87 missing: Vec<&'static str>, 88 duplicate: Vec<String>, 89 unexpected: Vec<String>, 90 ordered: bool, 91 } 92 93 impl PreflightReport { 94 pub(crate) fn outcomes(&self) -> &[LaneOutcome] { 95 &self.outcomes 96 } 97 98 pub(crate) fn is_pass(&self) -> bool { 99 self.missing.is_empty() 100 && self.duplicate.is_empty() 101 && self.unexpected.is_empty() 102 && self.ordered 103 && self.outcomes.len() == REQUIRED_LANES.len() 104 && self 105 .outcomes 106 .iter() 107 .all(|outcome| outcome.state == LaneState::Pass) 108 } 109 } 110 111 #[derive(Debug)] 112 pub(crate) struct PreflightError { 113 report: PreflightReport, 114 } 115 116 impl PreflightError { 117 pub(crate) fn report(&self) -> &PreflightReport { 118 &self.report 119 } 120 } 121 122 impl fmt::Display for PreflightError { 123 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 124 formatter.write_str("release preflight required lanes did not all pass") 125 } 126 } 127 128 impl std::error::Error for PreflightError {} 129 130 pub(crate) fn execute_all<F>(mut execute: F) -> Result<PreflightReport, PreflightError> 131 where 132 F: FnMut(LaneId) -> LaneState, 133 { 134 let outcomes = REQUIRED_LANES 135 .into_iter() 136 .map(|lane| LaneOutcome::required(lane, execute(lane))) 137 .collect(); 138 close(outcomes) 139 } 140 141 pub(crate) fn close(outcomes: Vec<LaneOutcome>) -> Result<PreflightReport, PreflightError> { 142 let required = REQUIRED_LANES 143 .into_iter() 144 .map(LaneId::as_str) 145 .collect::<BTreeSet<_>>(); 146 let mut counts = BTreeMap::<String, usize>::new(); 147 for outcome in &outcomes { 148 *counts.entry(outcome.id.clone()).or_default() += 1; 149 } 150 let missing = required 151 .iter() 152 .copied() 153 .filter(|lane| !counts.contains_key(*lane)) 154 .collect::<Vec<_>>(); 155 let duplicate = counts 156 .iter() 157 .filter(|(_, count)| **count > 1) 158 .map(|(lane, _)| lane.clone()) 159 .collect::<Vec<_>>(); 160 let unexpected = counts 161 .keys() 162 .filter(|lane| !required.contains(lane.as_str())) 163 .cloned() 164 .collect::<Vec<_>>(); 165 let ordered = outcomes 166 .iter() 167 .map(|outcome| outcome.id.as_str()) 168 .eq(REQUIRED_LANES.into_iter().map(LaneId::as_str)); 169 let report = PreflightReport { 170 outcomes, 171 missing, 172 duplicate, 173 unexpected, 174 ordered, 175 }; 176 if report.is_pass() { 177 Ok(report) 178 } else { 179 Err(PreflightError { report }) 180 } 181 } 182 183 pub(crate) fn self_test() -> Result<(), String> { 184 let mut attempted = Vec::new(); 185 execute_all(|lane| { 186 attempted.push(lane); 187 LaneState::Pass 188 }) 189 .map_err(|error| error.to_string())?; 190 if attempted != REQUIRED_LANES { 191 return Err("release preflight lane inventory self-test failed".to_owned()); 192 } 193 194 for state in [ 195 LaneState::Failed, 196 LaneState::Interrupted, 197 LaneState::Skipped, 198 LaneState::Unavailable, 199 ] { 200 let mut attempted = Vec::new(); 201 let result = execute_all(|lane| { 202 attempted.push(lane); 203 if lane == LaneId::Catalog { 204 state 205 } else { 206 LaneState::Pass 207 } 208 }); 209 let error = result 210 .err() 211 .ok_or_else(|| "release preflight exhaustion self-test failed".to_owned())?; 212 if attempted != REQUIRED_LANES || error.report().outcomes().len() != REQUIRED_LANES.len() { 213 return Err("release preflight exhaustion self-test failed".to_owned()); 214 } 215 } 216 217 let all_pass = || { 218 REQUIRED_LANES 219 .into_iter() 220 .map(|lane| LaneOutcome::required(lane, LaneState::Pass)) 221 .collect::<Vec<_>>() 222 }; 223 let mut missing = all_pass(); 224 missing.pop(); 225 let mut duplicate = all_pass(); 226 duplicate.push(LaneOutcome::required(LaneId::Catalog, LaneState::Pass)); 227 let mut unexpected = all_pass(); 228 unexpected.push(LaneOutcome::named("not_governed", LaneState::Pass)); 229 let mut reordered = all_pass(); 230 reordered.swap(0, 1); 231 if [missing, duplicate, unexpected, reordered] 232 .into_iter() 233 .any(|outcomes| close(outcomes).is_ok()) 234 { 235 return Err("release preflight closure self-test failed".to_owned()); 236 } 237 Ok(()) 238 } 239 240 #[cfg(test)] 241 mod tests { 242 use super::*; 243 244 fn all_pass() -> Vec<LaneOutcome> { 245 REQUIRED_LANES 246 .into_iter() 247 .map(|lane| LaneOutcome::required(lane, LaneState::Pass)) 248 .collect() 249 } 250 251 #[test] 252 fn exact_inventory_and_order_are_closed() { 253 assert_eq!( 254 REQUIRED_LANES.map(LaneId::as_str), 255 [ 256 "catalog", 257 "service_source_lock_contract", 258 "service_build_qualification_contract", 259 "service_release_artifacts_contract", 260 "public_native_group", 261 "preview_group", 262 "tools_group", 263 "dto_roots", 264 "protocol_freshness", 265 "artifact_contracts", 266 "release_contracts", 267 ] 268 ); 269 assert!(close(all_pass()).expect("all pass").is_pass()); 270 } 271 272 #[test] 273 fn every_nonpass_state_fails_closed_without_short_circuiting() { 274 for state in [ 275 LaneState::Failed, 276 LaneState::Interrupted, 277 LaneState::Skipped, 278 LaneState::Unavailable, 279 ] { 280 let mut attempted = Vec::new(); 281 let error = execute_all(|lane| { 282 attempted.push(lane); 283 if lane == LaneId::Catalog { 284 state 285 } else { 286 LaneState::Pass 287 } 288 }) 289 .expect_err("nonpass lane"); 290 assert_eq!(attempted, REQUIRED_LANES); 291 assert_eq!(error.report().outcomes().len(), REQUIRED_LANES.len()); 292 } 293 } 294 295 #[test] 296 fn missing_duplicate_and_unexpected_lanes_fail_closed() { 297 let mut missing = all_pass(); 298 missing.pop(); 299 assert!(close(missing).is_err()); 300 301 let mut duplicate = all_pass(); 302 duplicate.push(LaneOutcome::required(LaneId::Catalog, LaneState::Pass)); 303 assert!(close(duplicate).is_err()); 304 305 let mut unexpected = all_pass(); 306 unexpected.push(LaneOutcome::named("not_governed", LaneState::Pass)); 307 assert!(close(unexpected).is_err()); 308 309 let mut reordered = all_pass(); 310 reordered.swap(0, 1); 311 assert!(close(reordered).is_err()); 312 } 313 314 #[test] 315 fn aggregate_diagnostics_are_static() { 316 let error = close(vec![LaneOutcome::named( 317 "secret-path-or-command", 318 LaneState::Failed, 319 )]) 320 .expect_err("invalid aggregate"); 321 let diagnostic = error.to_string(); 322 assert_eq!( 323 diagnostic, 324 "release preflight required lanes did not all pass" 325 ); 326 assert!(!diagnostic.contains("secret")); 327 } 328 }