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    /// A repeated state consumed every permitted complete pass.
28    StateRepeatLimit,
29    /// An instance with the same id already existed; the run was rejected.
30    AlreadyExists,
31}
32
33impl CeremonyOutcome {
34    /// Stable, low-cardinality label value for metrics exposition. Part of
35    /// the metric contract; dashboards and alerts match on it.
36    #[must_use]
37    pub const fn as_label(self) -> &'static str {
38        match self {
39            Self::Completed => "completed",
40            Self::StepFailed => "step_failed",
41            Self::NoTransition => "no_transition",
42            Self::IterationLimit => "iteration_limit",
43            Self::RepeatLimit => "repeat_limit",
44            Self::StateRepeatLimit => "state_repeat_limit",
45            Self::AlreadyExists => "already_exists",
46        }
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn labels_are_distinct_and_stable() {
56        let all = [
57            CeremonyOutcome::Completed,
58            CeremonyOutcome::StepFailed,
59            CeremonyOutcome::NoTransition,
60            CeremonyOutcome::IterationLimit,
61            CeremonyOutcome::RepeatLimit,
62            CeremonyOutcome::StateRepeatLimit,
63            CeremonyOutcome::AlreadyExists,
64        ];
65        let labels: std::collections::BTreeSet<&str> =
66            all.iter().map(|outcome| outcome.as_label()).collect();
67        assert_eq!(labels.len(), all.len());
68    }
69}