Skip to main content

made_core/value_objects/
ceremony_outcome.rs

1//! [`CeremonyOutcome`] — the terminal result of a ceremony run.
2//!
3//! A ceremony driven to a stop ends in exactly one of these. Unlike a
4//! step's [`StepStatus`](super::StepStatus), this names *why the whole
5//! ceremony stopped*: it completed, a step failed, no transition could
6//! fire, the transition safety cap was hit, a step exhausted its semantic
7//! repeat limit, or the instance already existed. Adapter-shaped aborts
8//! (persistence/transport errors) are not
9//! outcomes — they surface as errors, not as a recorded end-state.
10
11/// How a ceremony run terminated.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum CeremonyOutcome {
14    /// Reached a terminal state — the ceremony finished.
15    Completed,
16    /// A step did not complete successfully and aborted the run.
17    StepFailed,
18    /// No transition out of the current state was satisfiable (a guard
19    /// deadlock or a missing event).
20    NoTransition,
21    /// The transition safety cap was hit without reaching a terminal
22    /// state.
23    IterationLimit,
24    /// A bounded semantic step repeat used every permitted iteration without
25    /// satisfying its declared stop condition.
26    RepeatLimit,
27    /// An instance with the same id already existed; the run was rejected.
28    AlreadyExists,
29}
30
31impl CeremonyOutcome {
32    /// Stable, low-cardinality label value for metrics exposition. Part of
33    /// the metric contract; dashboards and alerts match on it.
34    #[must_use]
35    pub const fn as_label(self) -> &'static str {
36        match self {
37            Self::Completed => "completed",
38            Self::StepFailed => "step_failed",
39            Self::NoTransition => "no_transition",
40            Self::IterationLimit => "iteration_limit",
41            Self::RepeatLimit => "repeat_limit",
42            Self::AlreadyExists => "already_exists",
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn labels_are_distinct_and_stable() {
53        let all = [
54            CeremonyOutcome::Completed,
55            CeremonyOutcome::StepFailed,
56            CeremonyOutcome::NoTransition,
57            CeremonyOutcome::IterationLimit,
58            CeremonyOutcome::RepeatLimit,
59            CeremonyOutcome::AlreadyExists,
60        ];
61        let labels: std::collections::BTreeSet<&str> =
62            all.iter().map(|outcome| outcome.as_label()).collect();
63        assert_eq!(labels.len(), all.len());
64    }
65}