policy.rs (9169B)
1 //! Transport-neutral delivery satisfaction policy. 2 3 use crate::{ 4 Error, 5 outcome::DeliveryOutcome, 6 target::{TargetFingerprint, TargetSet}, 7 }; 8 use alloc::{collections::BTreeMap, vec::Vec}; 9 10 /// Current result of evaluating delivery evidence against one exact policy. 11 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 12 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 13 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 14 pub enum SatisfactionState { 15 /// The evidence already satisfies the policy. 16 Satisfied, 17 /// The policy is not satisfied, but unattempted or retryable work can satisfy it. 18 Pending, 19 /// The available evidence proves that the policy can no longer be satisfied. 20 Exhausted, 21 } 22 23 /// Success level a caller requires from selected targets. 24 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 25 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 26 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 27 pub enum SatisfactionClass { 28 /// The target accepted responsibility for the event. 29 Accepted, 30 /// The target confirmed final delivery. 31 Delivered, 32 } 33 34 /// Which requested targets must reach the satisfaction class. 35 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 36 #[cfg_attr(feature = "serde", serde(transparent))] 37 #[derive(Clone, Debug, Eq, PartialEq)] 38 pub struct TargetPolicy { 39 kind: TargetPolicyKind, 40 } 41 42 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 43 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 44 #[derive(Clone, Debug, Eq, PartialEq)] 45 enum TargetPolicyKind { 46 Any, 47 All, 48 Quorum(u16), 49 Required(Vec<TargetFingerprint>), 50 } 51 52 impl TargetPolicy { 53 /// Any one requested target must satisfy the class. 54 pub const fn any() -> Self { 55 Self { 56 kind: TargetPolicyKind::Any, 57 } 58 } 59 60 /// Every requested target must satisfy the class. 61 pub const fn all() -> Self { 62 Self { 63 kind: TargetPolicyKind::All, 64 } 65 } 66 67 /// At least this non-zero count of requested targets must satisfy the class. 68 pub const fn quorum(threshold: u16) -> Result<Self, Error> { 69 if threshold == 0 { 70 return Err(Error::InvalidSatisfactionPolicy); 71 } 72 Ok(Self { 73 kind: TargetPolicyKind::Quorum(threshold), 74 }) 75 } 76 77 /// These exact, unique target fingerprints must satisfy the class. 78 pub fn required(mut targets: Vec<TargetFingerprint>) -> Result<Self, Error> { 79 if targets.is_empty() { 80 return Err(Error::EmptyRequiredTargetSet); 81 } 82 targets.sort(); 83 if targets.windows(2).any(|pair| pair[0] == pair[1]) { 84 return Err(Error::DuplicateRequiredTargetFingerprint); 85 } 86 Ok(Self { 87 kind: TargetPolicyKind::Required(targets), 88 }) 89 } 90 91 /// Returns exact required fingerprints for a required-target policy. 92 pub fn required_targets(&self) -> Option<&[TargetFingerprint]> { 93 match &self.kind { 94 TargetPolicyKind::Required(targets) => Some(targets.as_slice()), 95 TargetPolicyKind::Any | TargetPolicyKind::All | TargetPolicyKind::Quorum(_) => None, 96 } 97 } 98 99 /// Returns whether any requested target may satisfy this policy. 100 pub const fn is_any(&self) -> bool { 101 matches!(self.kind, TargetPolicyKind::Any) 102 } 103 104 /// Returns whether every requested target must satisfy this policy. 105 pub const fn is_all(&self) -> bool { 106 matches!(self.kind, TargetPolicyKind::All) 107 } 108 109 /// Returns the threshold for a quorum policy. 110 pub const fn quorum_threshold(&self) -> Option<u16> { 111 match self.kind { 112 TargetPolicyKind::Quorum(threshold) => Some(threshold), 113 TargetPolicyKind::Any | TargetPolicyKind::All | TargetPolicyKind::Required(_) => None, 114 } 115 } 116 117 pub(crate) fn validate_for(&self, targets: &TargetSet) -> Result<(), Error> { 118 match &self.kind { 119 TargetPolicyKind::Any | TargetPolicyKind::All => Ok(()), 120 TargetPolicyKind::Quorum(threshold) => { 121 if usize::from(*threshold) > targets.len() { 122 Err(Error::InvalidSatisfactionPolicy) 123 } else { 124 Ok(()) 125 } 126 } 127 TargetPolicyKind::Required(required) => { 128 if required 129 .iter() 130 .any(|fingerprint| !targets.contains(fingerprint)) 131 { 132 Err(Error::RequiredTargetNotRequested) 133 } else { 134 Ok(()) 135 } 136 } 137 } 138 } 139 140 fn is_satisfied(&self, total_targets: usize, satisfied: usize, fingerprints: &[&str]) -> bool { 141 match &self.kind { 142 TargetPolicyKind::Any => satisfied != 0, 143 TargetPolicyKind::All => satisfied == total_targets, 144 TargetPolicyKind::Quorum(threshold) => satisfied >= usize::from(*threshold), 145 TargetPolicyKind::Required(required) => required 146 .iter() 147 .all(|fingerprint| fingerprints.contains(&fingerprint.as_str())), 148 } 149 } 150 } 151 152 /// Required success level and target selection for one delivery request. 153 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 154 #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 155 #[derive(Clone, Debug, Eq, PartialEq)] 156 pub struct SatisfactionPolicy { 157 class: SatisfactionClass, 158 targets: TargetPolicy, 159 } 160 161 impl SatisfactionPolicy { 162 /// Creates an explicit satisfaction policy. 163 pub const fn new(class: SatisfactionClass, targets: TargetPolicy) -> Self { 164 Self { class, targets } 165 } 166 167 /// Returns the accepted or delivered success level. 168 pub const fn class(&self) -> SatisfactionClass { 169 self.class 170 } 171 172 /// Returns the selected target policy. 173 pub const fn targets(&self) -> &TargetPolicy { 174 &self.targets 175 } 176 177 /// Validates this policy against an exact bounded target set. 178 /// 179 /// This permits higher-level composition layers to reject impossible 180 /// quorum and required-target profiles before constructing a delivery 181 /// request, without reproducing transport policy law. 182 pub fn validate_for(&self, targets: &TargetSet) -> Result<(), Error> { 183 self.targets.validate_for(targets) 184 } 185 } 186 187 /// Evaluates target evidence using the transport-owned satisfaction law. 188 /// 189 /// Evidence is ordered from oldest to newest when a target occurs more than 190 /// once. A prior success remains authoritative; otherwise the newest outcome 191 /// determines whether the target can be retried. Targets without evidence are 192 /// pending. Evidence for a target outside `targets` is rejected. 193 pub fn evaluate_satisfaction<'a, I>( 194 policy: &SatisfactionPolicy, 195 targets: &TargetSet, 196 evidence: I, 197 ) -> Result<SatisfactionState, Error> 198 where 199 I: IntoIterator<Item = (&'a TargetFingerprint, &'a DeliveryOutcome)>, 200 { 201 policy.validate_for(targets)?; 202 let mut states: BTreeMap<&str, (bool, bool)> = targets 203 .targets() 204 .iter() 205 .map(|target| (target.fingerprint().as_str(), (false, true))) 206 .collect(); 207 208 for (target, outcome) in evidence { 209 outcome.validate()?; 210 let Some((satisfied, retryable)) = states.get_mut(target.as_str()) else { 211 return Err(Error::UnexpectedDeliveryTargetReceipt); 212 }; 213 if outcome.satisfies(policy.class()) { 214 *satisfied = true; 215 *retryable = false; 216 } else if !*satisfied { 217 *retryable = outcome.is_retryable(); 218 } 219 } 220 221 let satisfied_targets: Vec<&str> = states 222 .iter() 223 .filter_map(|(target, (satisfied, _))| satisfied.then_some(*target)) 224 .collect(); 225 if policy.targets.is_satisfied( 226 targets.len(), 227 satisfied_targets.len(), 228 satisfied_targets.as_slice(), 229 ) { 230 return Ok(SatisfactionState::Satisfied); 231 } 232 233 let possible_targets: Vec<&str> = states 234 .iter() 235 .filter_map(|(target, (satisfied, retryable))| { 236 (*satisfied || *retryable).then_some(*target) 237 }) 238 .collect(); 239 if policy.targets.is_satisfied( 240 targets.len(), 241 possible_targets.len(), 242 possible_targets.as_slice(), 243 ) { 244 Ok(SatisfactionState::Pending) 245 } else { 246 Ok(SatisfactionState::Exhausted) 247 } 248 } 249 250 #[cfg(feature = "serde")] 251 impl<'de> serde::Deserialize<'de> for TargetPolicy { 252 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 253 where 254 D: serde::Deserializer<'de>, 255 { 256 match TargetPolicyKind::deserialize(deserializer)? { 257 TargetPolicyKind::Any => Ok(Self::any()), 258 TargetPolicyKind::All => Ok(Self::all()), 259 TargetPolicyKind::Quorum(threshold) => { 260 Self::quorum(threshold).map_err(serde::de::Error::custom) 261 } 262 TargetPolicyKind::Required(targets) => { 263 Self::required(targets).map_err(serde::de::Error::custom) 264 } 265 } 266 } 267 }