made_core/entities/ceremony_instance/decisions/
start.rs1use time::{Duration, OffsetDateTime};
2
3use crate::entities::ceremony_events::{CeremonyInstanceStarted, MemoryRecalled};
4use crate::entities::{
5 CeremonyDefinition, CeremonyEvent, CeremonyInstance, PublishedCeremonyDefinition,
6};
7use crate::error::DomainError;
8use crate::value_objects::{
9 BudgetAccountId, CeremonyContext, CeremonyDeadline, CeremonyDefinitionDigest, CeremonyId,
10 CeremonyLineage, SessionRecollection, StateDeadline, StateVisit,
11};
12
13impl CeremonyInstance {
14 pub fn decide_start(
26 id: CeremonyId,
27 definition: &CeremonyDefinition,
28 context: CeremonyContext,
29 recollection: Option<SessionRecollection>,
30 now: OffsetDateTime,
31 ) -> Result<Vec<CeremonyEvent>, DomainError> {
32 Ok(Self::opening_batch(
33 Self::opening(id, definition, context, now, None, None, None)?,
34 recollection,
35 now,
36 ))
37 }
38
39 pub fn decide_start_bound(
42 id: CeremonyId,
43 published: &PublishedCeremonyDefinition,
44 context: CeremonyContext,
45 recollection: Option<SessionRecollection>,
46 now: OffsetDateTime,
47 ) -> Result<Vec<CeremonyEvent>, DomainError> {
48 Ok(Self::opening_batch(
49 Self::opening(
50 id,
51 published.definition(),
52 context,
53 now,
54 Some(published.digest()),
55 None,
56 None,
57 )?,
58 recollection,
59 now,
60 ))
61 }
62
63 pub fn decide_start_bound_child(
65 id: CeremonyId,
66 published: &PublishedCeremonyDefinition,
67 context: CeremonyContext,
68 lineage: CeremonyLineage,
69 recollection: Option<SessionRecollection>,
70 now: OffsetDateTime,
71 ) -> Result<Vec<CeremonyEvent>, DomainError> {
72 Ok(Self::opening_batch(
73 Self::opening(
74 id,
75 published.definition(),
76 context,
77 now,
78 Some(published.digest()),
79 Some(lineage),
80 None,
81 )?,
82 recollection,
83 now,
84 ))
85 }
86
87 pub fn decide_start_bound_budgeted(
89 id: CeremonyId,
90 published: &PublishedCeremonyDefinition,
91 context: CeremonyContext,
92 budget_account_id: BudgetAccountId,
93 recollection: Option<SessionRecollection>,
94 now: OffsetDateTime,
95 ) -> Result<Vec<CeremonyEvent>, DomainError> {
96 Ok(Self::opening_batch(
97 Self::opening(
98 id,
99 published.definition(),
100 context,
101 now,
102 Some(published.digest()),
103 None,
104 Some(budget_account_id),
105 )?,
106 recollection,
107 now,
108 ))
109 }
110
111 pub fn decide_start_bound_budgeted_child(
113 id: CeremonyId,
114 published: &PublishedCeremonyDefinition,
115 context: CeremonyContext,
116 lineage: CeremonyLineage,
117 budget_account_id: BudgetAccountId,
118 recollection: Option<SessionRecollection>,
119 now: OffsetDateTime,
120 ) -> Result<Vec<CeremonyEvent>, DomainError> {
121 Ok(Self::opening_batch(
122 Self::opening(
123 id,
124 published.definition(),
125 context,
126 now,
127 Some(published.digest()),
128 Some(lineage),
129 Some(budget_account_id),
130 )?,
131 recollection,
132 now,
133 ))
134 }
135
136 fn opening_batch(
145 opening: CeremonyInstanceStarted,
146 recollection: Option<SessionRecollection>,
147 now: OffsetDateTime,
148 ) -> Vec<CeremonyEvent> {
149 let mut events = vec![CeremonyEvent::CeremonyInstanceStarted(opening)];
150 if let Some(recollection) = recollection.filter(|recalled| !recalled.is_empty()) {
151 events.push(CeremonyEvent::MemoryRecalled(MemoryRecalled {
152 recollection,
153 recalled_at: now,
154 }));
155 }
156 events
157 }
158
159 pub(in crate::entities::ceremony_instance) fn opening(
162 id: CeremonyId,
163 definition: &CeremonyDefinition,
164 context: CeremonyContext,
165 now: OffsetDateTime,
166 bound_definition: Option<CeremonyDefinitionDigest>,
167 lineage: Option<CeremonyLineage>,
168 budget_account_id: Option<BudgetAccountId>,
169 ) -> Result<CeremonyInstanceStarted, DomainError> {
170 let missing = definition
171 .inputs()
172 .values()
173 .filter(|input| input.requirement().is_required())
174 .filter(|input| context.attributes().get(input.name().as_str()).is_none())
175 .map(|input| input.name().as_str())
176 .collect::<Vec<_>>();
177 if !missing.is_empty() {
178 return Err(DomainError::InvalidDocument {
179 reason: format!("missing required ceremony inputs: {}", missing.join(", ")),
180 });
181 }
182 let ceremony_deadline = definition
183 .ceremony_timeout()
184 .map(|timeout| checked_deadline(now, timeout.duration(), "ceremony_deadline"))
185 .transpose()?
186 .map(CeremonyDeadline::new);
187 let state_deadline = definition
188 .state_timeout()
189 .map(|timeout| checked_deadline(now, timeout.duration(), "state_deadline"))
190 .transpose()?
191 .map(|at| {
192 StateDeadline::new(definition.initial_state_id().clone(), StateVisit::FIRST, at)
193 });
194 Ok(CeremonyInstanceStarted {
195 ceremony_id: id,
196 definition_name: definition.name().clone(),
197 definition_version: definition.version().clone(),
198 initial_state: definition.initial_state_id().clone(),
199 step_ids: definition.steps().keys().cloned().collect(),
200 context,
201 bound_definition,
202 lineage,
203 budget_account_id,
204 ceremony_deadline,
205 state_deadline,
206 created_at: now,
207 })
208 }
209}
210
211pub(crate) fn checked_deadline(
212 now: OffsetDateTime,
213 duration: crate::value_objects::DurationMs,
214 field: &'static str,
215) -> Result<OffsetDateTime, DomainError> {
216 let millis = i64::try_from(duration.get()).map_err(|_| DomainError::OutOfRange {
217 field,
218 value: duration.get() as f64,
219 min: 0.0,
220 max: i64::MAX as f64,
221 })?;
222 now.checked_add(Duration::milliseconds(millis))
223 .ok_or(DomainError::OutOfRange {
224 field,
225 value: duration.get() as f64,
226 min: 0.0,
227 max: i64::MAX as f64,
228 })
229}