lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }