Skip to main content

made_core/entities/ceremony_instance/fold/
mod.rs

1//! Folding: the events of a session, back into its state.
2//!
3//! [`CeremonyInstance::apply`] writes what one event says and nothing
4//! else: no definition, no clock, no validation. Every rule was held
5//! when the event was decided, and re-checking here would make a
6//! stream that once folded stop folding the day a rule changed. One
7//! file per event family; [`CeremonyInstance::rehydrate`] is the
8//! whole fold from the opening event on.
9
10use crate::entities::{CeremonyEvent, CeremonyInstance};
11use crate::error::DomainError;
12
13mod context;
14mod guard_decisions;
15mod import;
16mod interventions;
17mod participant_bindings;
18mod reasons;
19mod recollection;
20mod start;
21mod step_execution;
22mod transitions;
23
24impl CeremonyInstance {
25    /// Write one event into this session.
26    ///
27    /// Infallible and total: an event the session cannot honour —
28    /// a response to an item it does not hold, a second opening —
29    /// leaves it untouched rather than failing, because a fold is
30    /// not where a bad stream gets refused. `updated_at` becomes the
31    /// event's own timestamp.
32    pub fn apply(&mut self, event: &CeremonyEvent) {
33        match event {
34            // A stream opens once. Applying the opening to a session
35            // that already exists is a programming error in the
36            // caller, and the session is left exactly as it was: a
37            // fold that panicked over it would take a host down over
38            // one bad stream, and one that reopened the session would
39            // silently discard everything after the first opening.
40            CeremonyEvent::CeremonyInstanceStarted(_) => {}
41            CeremonyEvent::ParticipantsBound(bound) => self.apply_participants_bound(bound),
42            CeremonyEvent::StepStarted(started) => self.apply_step_started(started),
43            CeremonyEvent::StepCompleted(completed) => self.apply_step_completed(completed),
44            CeremonyEvent::StepFailed(failed) => self.apply_step_failed(failed),
45            CeremonyEvent::ContextWritten(written) => self.apply_context_written(written),
46            CeremonyEvent::StateIterationStarted(started) => {
47                self.apply_state_iteration_started(started);
48            }
49            CeremonyEvent::TransitionApplied(applied) => self.apply_transition_applied(applied),
50            CeremonyEvent::InterventionRequested(requested) => {
51                self.apply_intervention_requested(requested);
52            }
53            CeremonyEvent::InterventionResponded(responded) => {
54                self.apply_intervention_responded(responded);
55            }
56            CeremonyEvent::InterventionClosed(closed) => self.apply_intervention_closed(closed),
57            CeremonyEvent::EvidenceCollected(collected) => self.apply_evidence_collected(collected),
58            CeremonyEvent::ReasonAsserted(asserted) => self.apply_reason_asserted(asserted),
59            CeremonyEvent::HumanApprovalRecorded(recorded) => {
60                self.apply_human_approval_recorded(recorded);
61            }
62            CeremonyEvent::HumanDeferralRecorded(recorded) => {
63                self.apply_human_deferral_recorded(recorded);
64            }
65            CeremonyEvent::CeremonyCompleted(completed) => self.apply_ceremony_completed(completed),
66            CeremonyEvent::InstanceImported(imported) => self.apply_instance_imported(imported),
67            CeremonyEvent::MemoryRecalled(recalled) => self.apply_memory_recalled(recalled),
68        }
69    }
70
71    /// Fold a whole stream into the session it describes.
72    ///
73    /// The first event opens the session: either it was started here,
74    /// or it was imported from a store written before ceremonies were
75    /// streams (ADR-012). A stream that opens with anything else is not
76    /// a ceremony's stream and is refused, and so is one that carries
77    /// an import anywhere but at its first position — an import
78    /// replaces the whole session, so a second one would silently
79    /// discard everything between them.
80    pub fn rehydrate<'a>(
81        events: impl IntoIterator<Item = &'a CeremonyEvent>,
82    ) -> Result<Self, DomainError> {
83        let mut events = events.into_iter();
84        let mut instance = match events.next() {
85            Some(CeremonyEvent::CeremonyInstanceStarted(started)) => Self::from_started(started),
86            Some(CeremonyEvent::InstanceImported(imported)) => Self::from_imported(imported),
87            _ => {
88                return Err(DomainError::InvariantViolated {
89                    reason: "a ceremony stream opens with its start or with its import",
90                })
91            }
92        };
93        for event in events {
94            if matches!(event, CeremonyEvent::InstanceImported(_)) {
95                return Err(DomainError::InvariantViolated {
96                    reason: "a ceremony stream carries an import only as its first event",
97                });
98            }
99            instance.apply(event);
100        }
101        Ok(instance)
102    }
103
104    /// Fold the events one command decided, in order.
105    pub(in crate::entities::ceremony_instance) fn apply_all(&mut self, events: &[CeremonyEvent]) {
106        for event in events {
107            self.apply(event);
108        }
109    }
110}