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 children;
14mod context;
15mod execution_receipts;
16mod guard_decisions;
17mod import;
18mod interventions;
19mod lifecycle;
20mod participant_bindings;
21mod reasons;
22mod recollection;
23mod start;
24mod step_execution;
25mod transitions;
26
27impl CeremonyInstance {
28    /// Write one event into this session.
29    ///
30    /// Infallible and total: an event the session cannot honour —
31    /// a response to an item it does not hold, a second opening —
32    /// leaves it untouched rather than failing, because a fold is
33    /// not where a bad stream gets refused. `updated_at` becomes the
34    /// event's own timestamp.
35    pub fn apply(&mut self, event: &CeremonyEvent) {
36        match event {
37            // A stream opens once. Applying the opening to a session
38            // that already exists is a programming error in the
39            // caller, and the session is left exactly as it was: a
40            // fold that panicked over it would take a host down over
41            // one bad stream, and one that reopened the session would
42            // silently discard everything after the first opening.
43            CeremonyEvent::CeremonyInstanceStarted(_) => {}
44            CeremonyEvent::ParticipantsBound(bound) => self.apply_participants_bound(bound),
45            CeremonyEvent::StepStarted(started) => self.apply_step_started(started),
46            CeremonyEvent::StepCompleted(completed) => self.apply_step_completed(completed),
47            CeremonyEvent::StepFailed(failed) => self.apply_step_failed(failed),
48            CeremonyEvent::ContextWritten(written) => self.apply_context_written(written),
49            CeremonyEvent::StateIterationStarted(started) => {
50                self.apply_state_iteration_started(started);
51            }
52            CeremonyEvent::TransitionApplied(applied) => self.apply_transition_applied(applied),
53            CeremonyEvent::InterventionRequested(requested) => {
54                self.apply_intervention_requested(requested);
55            }
56            CeremonyEvent::InterventionResponded(responded) => {
57                self.apply_intervention_responded(responded);
58            }
59            CeremonyEvent::InterventionClosed(closed) => self.apply_intervention_closed(closed),
60            CeremonyEvent::EvidenceCollected(collected) => self.apply_evidence_collected(collected),
61            CeremonyEvent::ReasonAsserted(asserted) => self.apply_reason_asserted(asserted),
62            CeremonyEvent::HumanApprovalRecorded(recorded) => {
63                self.apply_human_approval_recorded(recorded);
64            }
65            CeremonyEvent::HumanDeferralRecorded(recorded) => {
66                self.apply_human_deferral_recorded(recorded);
67            }
68            CeremonyEvent::CeremonyCompleted(completed) => self.apply_ceremony_completed(completed),
69            CeremonyEvent::InstanceImported(imported) => self.apply_instance_imported(imported),
70            CeremonyEvent::MemoryRecalled(recalled) => self.apply_memory_recalled(recalled),
71            CeremonyEvent::ChildSpawnPlanned(event) => self.apply_child_spawn_planned(event),
72            CeremonyEvent::ChildSpawnPlanAdopted(event) => {
73                self.apply_child_spawn_plan_adopted(event);
74            }
75            CeremonyEvent::ChildCompletionAccepted(event) => {
76                self.apply_child_completion_accepted(event);
77            }
78            CeremonyEvent::CeremonyPaused(event) => self.apply_ceremony_paused(event),
79            CeremonyEvent::CeremonyResumed(event) => self.apply_ceremony_resumed(event),
80            CeremonyEvent::CeremonyCancelled(event) => self.apply_ceremony_cancelled(event),
81            CeremonyEvent::CeremonyDeadlineExceeded(event) => {
82                self.apply_ceremony_deadline_exceeded(event);
83            }
84            CeremonyEvent::StateDeadlineExceeded(event) => {
85                self.apply_state_deadline_exceeded(event);
86            }
87            CeremonyEvent::StepDeadlineExceeded(event) => self.apply_step_deadline_exceeded(event),
88            CeremonyEvent::LateStepResultObserved(event) => {
89                self.apply_late_step_result_observed(event);
90            }
91            CeremonyEvent::ExecutionReceiptLinked(event) => {
92                self.apply_execution_receipt_linked(event);
93            }
94        }
95    }
96
97    /// Fold a whole stream into the session it describes.
98    ///
99    /// The first event opens the session: either it was started here,
100    /// or it was imported from a store written before ceremonies were
101    /// streams (ADR-012). A stream that opens with anything else is not
102    /// a ceremony's stream and is refused, and so is one that carries
103    /// an import anywhere but at its first position — an import
104    /// replaces the whole session, so a second one would silently
105    /// discard everything between them.
106    pub fn rehydrate<'a>(
107        events: impl IntoIterator<Item = &'a CeremonyEvent>,
108    ) -> Result<Self, DomainError> {
109        let mut events = events.into_iter();
110        let mut instance = match events.next() {
111            Some(CeremonyEvent::CeremonyInstanceStarted(started)) => Self::from_started(started),
112            Some(CeremonyEvent::InstanceImported(imported)) => Self::from_imported(imported),
113            _ => {
114                return Err(DomainError::InvariantViolated {
115                    reason: "a ceremony stream opens with its start or with its import",
116                })
117            }
118        };
119        for event in events {
120            if matches!(event, CeremonyEvent::InstanceImported(_)) {
121                return Err(DomainError::InvariantViolated {
122                    reason: "a ceremony stream carries an import only as its first event",
123                });
124            }
125            instance.apply(event);
126        }
127        Ok(instance)
128    }
129
130    /// Fold the events one command decided, in order.
131    pub(in crate::entities::ceremony_instance) fn apply_all(&mut self, events: &[CeremonyEvent]) {
132        for event in events {
133            self.apply(event);
134        }
135    }
136}