use super::{
BTreeSet, CeremonyDefinitionParts, CeremonyTransition, CeremonyValidationFinding,
CeremonyValidationLocus, DomainError, GuardCondition, StateId,
};
impl CeremonyDefinitionParts<'_> {
pub(super) fn collect_global_completion_findings(
&self,
findings: &mut Vec<CeremonyValidationFinding>,
) {
let Some(initial) = self.sole_initial_state_id() else {
return;
};
if !self.transition_endpoints_resolve() || !self.transition_guards_resolve() {
return;
}
let before_barrier = self.states_before_global_completion(initial);
for transition in self.transitions {
if !before_barrier.contains(transition.from())
|| !self.requires_global_completion(transition)
{
continue;
}
let downstream = self.states_reachable_from(transition.to());
let can_reach_source = self.states_leading_to(transition.from());
if self.steps.values().any(|step| {
downstream.contains(step.state_id())
&& !(before_barrier.contains(step.state_id())
&& can_reach_source.contains(step.state_id()))
}) {
findings.push(CeremonyValidationFinding::warning(
CeremonyValidationLocus::transition(
transition.from().clone(),
transition.trigger().clone(),
),
DomainError::InvariantViolated {
reason: "all_steps_completed is global and waits for downstream steps; use explicit source step_status guards or a state-scoped steps_completed join",
},
));
}
}
}
fn requires_global_completion(&self, transition: &CeremonyTransition) -> bool {
transition.required_guards().iter().any(|name| {
self.guards
.get(name)
.is_some_and(|guard| matches!(guard.condition(), GuardCondition::AllStepsCompleted))
})
}
fn states_leading_to(&self, destination: &StateId) -> BTreeSet<StateId> {
let mut reached = BTreeSet::new();
let mut pending = vec![destination.clone()];
while let Some(current) = pending.pop() {
if !reached.insert(current.clone()) {
continue;
}
for transition in self.transitions.iter().filter(|edge| edge.to() == ¤t) {
pending.push(transition.from().clone());
}
}
reached
}
fn states_before_global_completion(&self, initial: &StateId) -> BTreeSet<StateId> {
let mut reached = BTreeSet::new();
let mut pending = vec![initial.clone()];
while let Some(current) = pending.pop() {
if !reached.insert(current.clone()) {
continue;
}
for transition in self.transitions.iter().filter(|transition| {
transition.from() == ¤t && !self.requires_global_completion(transition)
}) {
pending.push(transition.to().clone());
}
}
reached
}
}