1use std::error::Error;
8use std::fmt::{self, Display, Formatter};
9
10#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
15pub enum MutationLifecycleAuthority {
16 Catalog,
17 Security,
18 Audit,
19 Wal,
20 Manifest,
21 Task,
22 Portability,
23 ClusterControl,
24 State,
25}
26
27pub const MUTATION_LIFECYCLE_AUTHORITIES: [MutationLifecycleAuthority; 9] = [
28 MutationLifecycleAuthority::Catalog,
29 MutationLifecycleAuthority::Security,
30 MutationLifecycleAuthority::Audit,
31 MutationLifecycleAuthority::Wal,
32 MutationLifecycleAuthority::Manifest,
33 MutationLifecycleAuthority::Task,
34 MutationLifecycleAuthority::Portability,
35 MutationLifecycleAuthority::ClusterControl,
36 MutationLifecycleAuthority::State,
37];
38
39pub trait MutationLifecycleResolver<Locator> {
43 fn mutation_lifecycle_authority(&self, locator: &Locator) -> MutationLifecycleAuthority;
44
45 fn resolve_mutation_lifecycle(
46 &self,
47 locator: &Locator,
48 visible: bool,
49 ) -> Result<MutationLifecycleObservation, MutationLifecycleError>;
50}
51
52#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
54pub struct MutationReplayIdentity {
55 authority_binding: [u8; 32],
56 operation_identity: [u8; 32],
57 semantic_digest: [u8; 32],
58}
59
60impl MutationReplayIdentity {
61 pub fn new(
62 authority_binding: [u8; 32],
63 operation_identity: [u8; 32],
64 semantic_digest: [u8; 32],
65 ) -> Result<Self, MutationLifecycleError> {
66 if authority_binding == [0; 32] {
67 return Err(MutationLifecycleError::ZeroAuthorityBinding);
68 }
69 if operation_identity == [0; 32] {
70 return Err(MutationLifecycleError::ZeroOperationIdentity);
71 }
72 if semantic_digest == [0; 32] {
73 return Err(MutationLifecycleError::ZeroSemanticDigest);
74 }
75 Ok(Self {
76 authority_binding,
77 operation_identity,
78 semantic_digest,
79 })
80 }
81
82 #[must_use]
83 pub const fn authority_binding(self) -> [u8; 32] {
84 self.authority_binding
85 }
86
87 #[must_use]
88 pub const fn operation_identity(self) -> [u8; 32] {
89 self.operation_identity
90 }
91
92 #[must_use]
93 pub const fn semantic_digest(self) -> [u8; 32] {
94 self.semantic_digest
95 }
96}
97
98#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
100pub enum MutationLifecycleStage {
101 NotSubmitted,
102 Prepared,
103 Committed,
104 Visible,
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum PreparedMutationDisposition {
110 SafelyReplayable,
112 StillPending,
114}
115
116#[derive(Clone, Copy, Debug, Eq, PartialEq)]
118pub enum SubmittedIndeterminateResolution {
119 Committed { visible: bool },
120 SafelyReplayable,
121 StillPending,
122 ProvenNotSubmitted,
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126enum MutationLifecycleState {
127 NotSubmitted,
128 Prepared(PreparedMutationDisposition),
129 Committed,
130 Visible,
131}
132
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
135pub struct MutationLifecycleObservation {
136 replay_identity: MutationReplayIdentity,
137 state: MutationLifecycleState,
138}
139
140impl MutationLifecycleObservation {
141 #[must_use]
142 pub const fn proven_not_submitted(replay_identity: MutationReplayIdentity) -> Self {
143 Self {
144 replay_identity,
145 state: MutationLifecycleState::NotSubmitted,
146 }
147 }
148
149 #[must_use]
150 pub const fn prepared(replay_identity: MutationReplayIdentity, disposition: PreparedMutationDisposition) -> Self {
151 Self {
152 replay_identity,
153 state: MutationLifecycleState::Prepared(disposition),
154 }
155 }
156
157 #[must_use]
158 pub const fn committed(replay_identity: MutationReplayIdentity) -> Self {
159 Self {
160 replay_identity,
161 state: MutationLifecycleState::Committed,
162 }
163 }
164
165 #[must_use]
166 pub const fn visible(replay_identity: MutationReplayIdentity) -> Self {
167 Self {
168 replay_identity,
169 state: MutationLifecycleState::Visible,
170 }
171 }
172
173 #[must_use]
174 pub const fn replay_identity(self) -> MutationReplayIdentity {
175 self.replay_identity
176 }
177
178 #[must_use]
179 pub const fn stage(self) -> MutationLifecycleStage {
180 match self.state {
181 MutationLifecycleState::NotSubmitted => MutationLifecycleStage::NotSubmitted,
182 MutationLifecycleState::Prepared(_) => MutationLifecycleStage::Prepared,
183 MutationLifecycleState::Committed => MutationLifecycleStage::Committed,
184 MutationLifecycleState::Visible => MutationLifecycleStage::Visible,
185 }
186 }
187
188 #[must_use]
189 pub const fn prepared_disposition(self) -> Option<PreparedMutationDisposition> {
190 match self.state {
191 MutationLifecycleState::Prepared(disposition) => Some(disposition),
192 MutationLifecycleState::NotSubmitted
193 | MutationLifecycleState::Committed
194 | MutationLifecycleState::Visible => None,
195 }
196 }
197
198 #[must_use]
199 pub const fn resolve_submitted_indeterminate(self) -> SubmittedIndeterminateResolution {
200 match self.state {
201 MutationLifecycleState::NotSubmitted => SubmittedIndeterminateResolution::ProvenNotSubmitted,
202 MutationLifecycleState::Prepared(PreparedMutationDisposition::SafelyReplayable) => {
203 SubmittedIndeterminateResolution::SafelyReplayable
204 }
205 MutationLifecycleState::Prepared(PreparedMutationDisposition::StillPending) => {
206 SubmittedIndeterminateResolution::StillPending
207 }
208 MutationLifecycleState::Committed => SubmittedIndeterminateResolution::Committed { visible: false },
209 MutationLifecycleState::Visible => SubmittedIndeterminateResolution::Committed { visible: true },
210 }
211 }
212
213 pub fn require_monotonic_successor(self, successor: Self) -> Result<(), MutationLifecycleError> {
214 if self.replay_identity != successor.replay_identity {
215 return Err(MutationLifecycleError::ReplayIdentityChanged);
216 }
217 let stage = self.stage();
218 let successor_stage = successor.stage();
219 if successor_stage < stage {
220 return Err(MutationLifecycleError::StageRegressed {
221 from: stage,
222 to: successor_stage,
223 });
224 }
225 Ok(())
226 }
227}
228
229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
230pub enum MutationLifecycleError {
231 ZeroAuthorityBinding,
232 ZeroOperationIdentity,
233 ZeroSemanticDigest,
234 ReplayIdentityChanged,
235 StageRegressed {
236 from: MutationLifecycleStage,
237 to: MutationLifecycleStage,
238 },
239 OutcomeEvidenceRetired,
240}
241
242impl Display for MutationLifecycleError {
243 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
244 match self {
245 Self::ZeroAuthorityBinding => formatter.write_str("mutation authority binding cannot be zero"),
246 Self::ZeroOperationIdentity => formatter.write_str("mutation operation identity cannot be zero"),
247 Self::ZeroSemanticDigest => formatter.write_str("mutation semantic digest cannot be zero"),
248 Self::ReplayIdentityChanged => {
249 formatter.write_str("mutation replay identity changed across lifecycle observations")
250 }
251 Self::StageRegressed { from, to } => {
252 write!(formatter, "mutation lifecycle regressed from {from:?} to {to:?}")
253 }
254 Self::OutcomeEvidenceRetired => {
255 formatter.write_str("mutation outcome evidence retired before deterministic resolution")
256 }
257 }
258 }
259}
260
261impl Error for MutationLifecycleError {}
262
263#[cfg(test)]
264mod tests {
265 use std::collections::BTreeSet;
266
267 use super::*;
268
269 fn replay_identity(value: u8) -> Result<MutationReplayIdentity, MutationLifecycleError> {
270 MutationReplayIdentity::new(
271 [value; 32],
272 [value.saturating_add(1); 32],
273 [value.saturating_add(2); 32],
274 )
275 }
276
277 #[test]
278 fn submitted_indeterminate_has_exactly_four_resolution_classes() -> Result<(), Box<dyn Error>> {
279 let identity = replay_identity(1)?;
280 assert_eq!(
281 MutationLifecycleObservation::proven_not_submitted(identity).resolve_submitted_indeterminate(),
282 SubmittedIndeterminateResolution::ProvenNotSubmitted
283 );
284 assert_eq!(
285 MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::SafelyReplayable)
286 .resolve_submitted_indeterminate(),
287 SubmittedIndeterminateResolution::SafelyReplayable
288 );
289 assert_eq!(
290 MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::StillPending)
291 .resolve_submitted_indeterminate(),
292 SubmittedIndeterminateResolution::StillPending
293 );
294 assert_eq!(
295 MutationLifecycleObservation::committed(identity).resolve_submitted_indeterminate(),
296 SubmittedIndeterminateResolution::Committed { visible: false }
297 );
298 assert_eq!(
299 MutationLifecycleObservation::visible(identity).resolve_submitted_indeterminate(),
300 SubmittedIndeterminateResolution::Committed { visible: true }
301 );
302 Ok(())
303 }
304
305 #[test]
306 fn lifecycle_authority_registry_is_closed_and_duplicate_free() {
307 assert_eq!(MUTATION_LIFECYCLE_AUTHORITIES.len(), 9);
308 assert_eq!(
309 MUTATION_LIFECYCLE_AUTHORITIES
310 .into_iter()
311 .collect::<BTreeSet<_>>()
312 .len(),
313 MUTATION_LIFECYCLE_AUTHORITIES.len()
314 );
315 }
316
317 #[test]
318 fn lifecycle_is_monotonic_and_identity_exact() -> Result<(), Box<dyn Error>> {
319 let identity = replay_identity(7)?;
320 let not_submitted = MutationLifecycleObservation::proven_not_submitted(identity);
321 let replayable =
322 MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::SafelyReplayable);
323 let pending = MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::StillPending);
324 let committed = MutationLifecycleObservation::committed(identity);
325 let visible = MutationLifecycleObservation::visible(identity);
326 not_submitted.require_monotonic_successor(replayable)?;
327 replayable.require_monotonic_successor(pending)?;
328 pending.require_monotonic_successor(replayable)?;
329 pending.require_monotonic_successor(committed)?;
330 committed.require_monotonic_successor(visible)?;
331 assert!(matches!(
332 visible.require_monotonic_successor(committed),
333 Err(MutationLifecycleError::StageRegressed { .. })
334 ));
335 assert_eq!(
336 committed.require_monotonic_successor(MutationLifecycleObservation::visible(replay_identity(8)?,)),
337 Err(MutationLifecycleError::ReplayIdentityChanged)
338 );
339 Ok(())
340 }
341
342 #[test]
343 fn replay_identity_refuses_ambiguous_components() {
344 assert_eq!(
345 MutationReplayIdentity::new([0; 32], [1; 32], [2; 32]),
346 Err(MutationLifecycleError::ZeroAuthorityBinding)
347 );
348 assert_eq!(
349 MutationReplayIdentity::new([1; 32], [0; 32], [2; 32]),
350 Err(MutationLifecycleError::ZeroOperationIdentity)
351 );
352 assert_eq!(
353 MutationReplayIdentity::new([1; 32], [2; 32], [0; 32]),
354 Err(MutationLifecycleError::ZeroSemanticDigest)
355 );
356 }
357}