Skip to main content

made_core/entities/ceremony_definition/
guards.rs

1use std::collections::BTreeMap;
2
3use crate::value_objects::{
4    CeremonyContext, CeremonyGuard, CeremonyStep, CeremonyTransition, GuardCondition, StateId,
5    StepExecutionRecord, StepId,
6};
7
8use super::CeremonyDefinition;
9
10impl CeremonyDefinition {
11    #[must_use]
12    pub fn guards_are_satisfied(
13        &self,
14        transition: &CeremonyTransition,
15        records: &BTreeMap<StepId, StepExecutionRecord>,
16        context: &CeremonyContext,
17    ) -> bool {
18        self.repeat_requirements_are_satisfied_for_transition(transition, records)
19            && transition.required_guards().iter().all(|guard_name| {
20                self.guards.get(guard_name).is_some_and(|guard| {
21                    self.guard_is_satisfied_for_transition(guard, transition, records, context)
22                })
23            })
24    }
25
26    /// Evaluate one guard with definition-owned step repetition semantics.
27    ///
28    /// A raw `COMPLETED` record is not enough for a repeating step: its
29    /// structured stop condition must also hold. Keeping that rule here makes
30    /// transition selection, instance projections and aggregate enforcement
31    /// agree on what completion means.
32    #[must_use]
33    pub fn guard_is_satisfied_for_transition(
34        &self,
35        guard: &CeremonyGuard,
36        transition: &CeremonyTransition,
37        records: &BTreeMap<StepId, StepExecutionRecord>,
38        context: &CeremonyContext,
39    ) -> bool {
40        match guard.condition() {
41            GuardCondition::StepRepeatExhausted(condition) => self
42                .step_repeat_is_exhausted_on_transition(transition, condition.step_id(), records),
43            GuardCondition::StepStatus { step_id, status } if status.is_success() => {
44                guard.is_satisfied(records, context)
45                    && self.repeat_requirement_is_satisfied_or_waived(transition, step_id, records)
46            }
47            GuardCondition::AllStepsCompleted => {
48                guard.is_satisfied(records, context)
49                    && self.steps.keys().all(|step_id| {
50                        self.repeat_requirement_is_satisfied_or_waived(transition, step_id, records)
51                    })
52            }
53            GuardCondition::AnyStepCompleted => {
54                self.steps_for_state(transition.from()).any(|step| {
55                    records
56                        .get(step.id())
57                        .is_some_and(|record| record.status().is_success())
58                })
59            }
60            GuardCondition::StepsCompleted(required) => {
61                self.steps_for_state(transition.from())
62                    .filter(|step| {
63                        records
64                            .get(step.id())
65                            .is_some_and(|record| record.status().is_success())
66                    })
67                    .count()
68                    >= required.get() as usize
69            }
70            _ => guard.is_satisfied(records, context),
71        }
72    }
73
74    /// Whether every repeating step in `state_id` has reached its declared
75    /// structured stop condition.
76    #[must_use]
77    pub fn repeat_requirements_are_satisfied(
78        &self,
79        state_id: &StateId,
80        records: &BTreeMap<StepId, StepExecutionRecord>,
81    ) -> bool {
82        self.steps_for_state(state_id)
83            .all(|step| self.repeat_requirement_is_satisfied(step.id(), records))
84    }
85
86    /// Whether source-state repeats either reached their stop condition or
87    /// have an exact exhaustion guard on this transition.
88    #[must_use]
89    pub fn repeat_requirements_are_satisfied_for_transition(
90        &self,
91        transition: &CeremonyTransition,
92        records: &BTreeMap<StepId, StepExecutionRecord>,
93    ) -> bool {
94        self.steps_for_state(transition.from()).all(|step| {
95            self.repeat_requirement_is_satisfied_or_waived(transition, step.id(), records)
96        })
97    }
98
99    fn repeat_requirement_is_satisfied_or_waived(
100        &self,
101        transition: &CeremonyTransition,
102        step_id: &StepId,
103        records: &BTreeMap<StepId, StepExecutionRecord>,
104    ) -> bool {
105        self.repeat_requirement_is_satisfied(step_id, records)
106            || self.transition_waives_exhausted_repeat(transition, step_id, records)
107    }
108
109    fn transition_waives_exhausted_repeat(
110        &self,
111        transition: &CeremonyTransition,
112        step_id: &StepId,
113        records: &BTreeMap<StepId, StepExecutionRecord>,
114    ) -> bool {
115        transition.required_guards().iter().any(|guard_name| {
116            self.guards.get(guard_name).is_some_and(|guard| {
117                matches!(
118                    guard.condition(),
119                    GuardCondition::StepRepeatExhausted(condition)
120                        if condition.step_id() == step_id
121                            && self.step_repeat_is_exhausted_on_transition(
122                                transition,
123                                step_id,
124                                records,
125                            )
126                )
127            })
128        })
129    }
130
131    fn step_repeat_is_exhausted_on_transition(
132        &self,
133        transition: &CeremonyTransition,
134        step_id: &StepId,
135        records: &BTreeMap<StepId, StepExecutionRecord>,
136    ) -> bool {
137        let Some(step) = self.step(step_id) else {
138            return false;
139        };
140        if step.state_id() != transition.from() {
141            return false;
142        }
143        let Some(policy) = step.repeat_policy() else {
144            return false;
145        };
146        records.get(step_id).is_some_and(|record| {
147            record.status().is_success()
148                && !policy.is_satisfied(record.output())
149                && !policy.permits_another_iteration(record.iteration())
150        })
151    }
152
153    fn repeat_requirement_is_satisfied(
154        &self,
155        step_id: &StepId,
156        records: &BTreeMap<StepId, StepExecutionRecord>,
157    ) -> bool {
158        let Some(policy) = self.step(step_id).and_then(CeremonyStep::repeat_policy) else {
159            return true;
160        };
161        records.get(step_id).is_some_and(|record| {
162            record.status().is_success() && policy.is_satisfied(record.output())
163        })
164    }
165}