Skip to main content

rite_model/ir/
ceremony.rs

1//! Resolved ceremony IR types.
2//!
3//! These types represent a fully resolved ceremony, ready for execution.
4//! All references have been validated, execution order computed, and
5//! parameter values merged with defaults.
6
7use super::SymbolTable;
8use super::ids::{ActId, ArtifactId, MaterialId, OutputId, ParamId, RoleId, SectionId, StepId};
9use crate::expression::ExprValue;
10use crate::material::MaterialSource;
11use crate::types::{ActionType, DutyType, Metadata, OutputType, ParameterType};
12use rite_sdk::BackendConfig;
13use std::collections::HashMap;
14
15/// A fully resolved ceremony, ready for execution.
16///
17/// All references have been validated, the execution order has been computed,
18/// and parameter values have been merged with external inputs.
19#[derive(Debug, Clone)]
20pub struct Ceremony {
21    /// Ceremony metadata (name, description).
22    pub metadata: Metadata,
23
24    /// Resolved roles, keyed by role ID.
25    pub roles: SymbolTable<RoleId, Role>,
26
27    /// Resolved acts, keyed by act ID (for script display).
28    pub acts: SymbolTable<ActId, Act>,
29
30    /// Resolved sections, keyed by section ID.
31    pub sections: SymbolTable<SectionId, Section>,
32
33    /// Resolved parameters with their values, keyed by parameter ID.
34    pub parameters: SymbolTable<ParamId, Parameter>,
35
36    /// Resolved materials with their sources, keyed by material ID.
37    pub materials: SymbolTable<MaterialId, Material>,
38
39    /// Pre-ceremony conditions (prose-only checklist).
40    pub prerequisites: Vec<String>,
41
42    /// Resolved outputs with their destinations, keyed by output ID.
43    pub outputs: SymbolTable<OutputId, Output>,
44
45    /// Backend configurations, keyed by backend name.
46    pub backends: HashMap<String, BackendConfig>,
47
48    /// Steps in execution order.
49    ///
50    /// Pre-computed topological order based on sections.
51    pub execution_plan: Vec<Step>,
52
53    /// Post-ceremony duties, in declaration order.
54    pub after: Vec<PostCeremonyDuty>,
55}
56
57/// A resolved role.
58#[derive(Debug, Clone)]
59pub struct Role {
60    /// Role identifier.
61    pub id: RoleId,
62
63    /// Human-readable name (derived or explicit).
64    pub name: String,
65
66    /// Role type: the prefix before `__` (or the whole ID if no `__`).
67    ///
68    /// E.g. `"witness__1"` → `"witness"`, `"operator"` → `"operator"`.
69    pub role_type: String,
70
71    /// The person assigned to this role slot, if any.
72    pub person: Option<String>,
73}
74
75/// A resolved act (for script display).
76#[derive(Debug, Clone)]
77pub struct Act {
78    /// Act identifier.
79    pub id: ActId,
80
81    /// Human-readable name.
82    pub name: Option<String>,
83
84    /// Preamble text displayed at the start of the act.
85    pub description: Option<String>,
86}
87
88/// A resolved section.
89#[derive(Debug, Clone)]
90pub struct Section {
91    /// Section identifier.
92    pub id: SectionId,
93
94    /// Reference to the act this section belongs to.
95    pub act: Option<ActId>,
96
97    /// Human-readable name.
98    pub name: Option<String>,
99
100    /// Section description.
101    pub description: Option<String>,
102
103    /// Default role for steps in this section.
104    pub default_role: Option<RoleId>,
105}
106
107/// A resolved step, ready for execution.
108#[derive(Debug, Clone)]
109pub struct Step {
110    /// Step identifier.
111    pub id: StepId,
112
113    /// Display label for this step (e.g., `"1.1"`, `"2.3"`).
114    ///
115    /// Uses hierarchical numbering: `act.step_within_act`.
116    /// When there's only one act, just the step number (e.g., `"1"`, `"2"`).
117    pub step_label: String,
118
119    /// Section this step belongs to.
120    pub section: SectionId,
121
122    /// Action to perform.
123    pub action: ActionType,
124
125    /// Backend to use for this step (if action requires one).
126    ///
127    /// References a backend declared in the ceremony's backends section.
128    pub backend: Option<String>,
129
130    /// Role that performs this step (resolved from step or section default).
131    pub role: Option<RoleId>,
132
133    /// Human-confirmed preconditions before this step executes.
134    pub preconditions: Vec<String>,
135
136    /// Action-specific parameters with expressions parsed (not yet evaluated).
137    ///
138    /// The runtime evaluates these to `serde_json::Value` before passing to handlers.
139    pub with: ExprValue,
140
141    /// Validated input artifact references (for ordering validation).
142    pub reads: Vec<ArtifactRef>,
143
144    /// Pre-resolved input references for handlers.
145    ///
146    /// Handlers can access artifact IDs directly without parsing.
147    pub reads_resolved: Option<StepInputs>,
148
149    /// Artifact ID this step produces.
150    pub creates: Option<ArtifactId>,
151
152    /// Human-readable description for display (may contain interpolated expressions).
153    pub description: Option<ExprValue>,
154
155    /// Skip the default pause after step completion.
156    ///
157    /// When `true`, the executor auto-advances without waiting for user acknowledgment.
158    pub silent: bool,
159
160    /// How the runtime treats a *transient* failure of this step.
161    ///
162    /// The DSL constrains the retry-by-default; see [`RetryPolicy`]. Absent in
163    /// the DSL means [`RetryPolicy::Prompt`].
164    pub retry: RetryPolicy,
165}
166
167/// How the runtime treats a transient (retriable) failure of a step.
168///
169/// Rite ceremonies always have a human at the console, so the default is to
170/// pause and let the operator decide, rather than burn a fixed retry budget.
171/// The DSL field constrains that default rather than enabling it.
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
173pub enum RetryPolicy {
174    /// Pause on a transient error and prompt the operator (Retry / Abort),
175    /// unlimited times. The default when the DSL omits `retry:`.
176    #[default]
177    Prompt,
178    /// Never retry: a transient error fails the step immediately. For steps
179    /// where repeated attempts are themselves security-relevant.
180    Never,
181    /// Retry up to this many total attempts, then fail. A hard cap.
182    MaxAttempts(u32),
183}
184
185/// A resolved parameter with its value.
186#[derive(Debug, Clone)]
187pub struct Parameter {
188    /// Parameter identifier.
189    pub id: ParamId,
190
191    /// Declared type.
192    pub declared_type: ParameterType,
193
194    /// Resolved value (from CLI/env/prompt or default).
195    pub value: serde_json::Value,
196
197    /// Human-readable description.
198    pub description: Option<String>,
199}
200
201/// What kind of material and its resolved source/identifier.
202#[derive(Debug, Clone)]
203pub enum MaterialKind {
204    /// Digital: loaded from file path. `None` if source not yet provided.
205    Digital {
206        /// Resolved file source, or `None` if not yet provided.
207        source: Option<MaterialSource>,
208    },
209    /// Physical: optional identifier string for display.
210    Physical {
211        /// Human-readable identifier (e.g., serial number).
212        identifier: Option<String>,
213        /// Item count for checklist rendering.
214        quantity: Option<u32>,
215    },
216}
217
218/// A resolved material with its source.
219#[derive(Debug, Clone)]
220pub struct Material {
221    /// Material identifier.
222    pub id: MaterialId,
223
224    /// What kind of material and its resolved source/identifier.
225    pub kind: MaterialKind,
226
227    /// Optional human-readable title for display (falls back to material ID).
228    pub title: Option<String>,
229
230    /// Human-readable description.
231    pub description: Option<String>,
232}
233
234impl Material {
235    /// Get the display name for this material (title or ID).
236    pub fn display_name(&self) -> &str {
237        self.title.as_deref().unwrap_or(self.id.as_str())
238    }
239
240    /// Whether this is a digital material.
241    pub fn is_digital(&self) -> bool {
242        matches!(self.kind, MaterialKind::Digital { .. })
243    }
244
245    /// Whether this is a physical material.
246    pub fn is_physical(&self) -> bool {
247        matches!(self.kind, MaterialKind::Physical { .. })
248    }
249}
250
251/// A resolved post-ceremony duty.
252#[derive(Debug, Clone)]
253pub struct PostCeremonyDuty {
254    /// Duty identifier (the dict key in the YAML `after:` block).
255    pub id: String,
256
257    /// Type of duty.
258    pub kind: DutyType,
259
260    /// Role responsible for this duty (resolved from plain role ID string).
261    pub role: Option<RoleId>,
262
263    /// Description of this duty (overrides built-in prose for the type).
264    pub description: Option<String>,
265
266    /// Checklist sub-items.
267    pub items: Vec<String>,
268
269    /// For distribute_* types: who receives.
270    pub recipient: Option<String>,
271
272    /// For storage/archive types: where.
273    pub location: Option<String>,
274}
275
276/// A resolved output declaration.
277#[derive(Debug, Clone)]
278pub struct Output {
279    /// Output identifier.
280    pub id: OutputId,
281
282    /// Type of output.
283    pub kind: OutputType,
284
285    /// Human-readable description.
286    pub description: Option<String>,
287}
288
289/// Reference to an artifact (either a material or a produced artifact).
290#[derive(Debug, Clone)]
291pub enum ArtifactRef {
292    /// References a pre-loaded material.
293    Material {
294        /// Material ID.
295        id: MaterialId,
296        /// Optional property accessor (e.g., `"public"` from a keypair).
297        property: Option<String>,
298    },
299
300    /// References an artifact produced by an earlier step.
301    Produced {
302        /// Artifact ID.
303        id: ArtifactId,
304        /// Optional property accessor.
305        property: Option<String>,
306    },
307}
308
309impl ArtifactRef {
310    /// Get a display name for this reference.
311    pub fn display_name(&self) -> String {
312        match self {
313            ArtifactRef::Material { id, property: None } => id.to_string(),
314            ArtifactRef::Material {
315                id,
316                property: Some(p),
317            } => format!("{id}.{p}"),
318            ArtifactRef::Produced { id, property: None } => id.to_string(),
319            ArtifactRef::Produced {
320                id,
321                property: Some(p),
322            } => format!("{id}.{p}"),
323        }
324    }
325
326    /// Get the artifact ID from this reference.
327    pub fn artifact_id(&self) -> ArtifactId {
328        match self {
329            ArtifactRef::Material { id, .. } => ArtifactId::new(id.as_str()),
330            ArtifactRef::Produced { id, .. } => id.clone(),
331        }
332    }
333
334    /// Get the property accessor from this reference (if any).
335    pub fn property(&self) -> Option<&str> {
336        match self {
337            ArtifactRef::Material { property, .. } | ArtifactRef::Produced { property, .. } => {
338                property.as_deref()
339            }
340        }
341    }
342}
343
344/// Pre-resolved input references for a step.
345///
346/// At resolution time, the resolver parses `${artifact.name}` strings and determines
347/// whether they reference materials or produced artifacts. This eliminates runtime
348/// string parsing in handlers.
349#[derive(Debug, Clone)]
350pub enum StepInputs {
351    /// Single artifact reference (e.g., `reads: "${artifact.keypair}"`)
352    Single(ArtifactRef),
353    /// Named artifact references (e.g., `reads: { key_to_wrap: "...", wrapping_key: "..." }`)
354    Named(HashMap<String, ArtifactRef>),
355}
356
357impl StepInputs {
358    /// Get a single input reference (returns `None` if this is `Named`).
359    pub fn as_single(&self) -> Option<&ArtifactRef> {
360        match self {
361            StepInputs::Single(r) => Some(r),
362            StepInputs::Named(_) => None,
363        }
364    }
365
366    /// Get named input references (returns `None` if this is `Single`).
367    pub fn as_named(&self) -> Option<&HashMap<String, ArtifactRef>> {
368        match self {
369            StepInputs::Single(_) => None,
370            StepInputs::Named(m) => Some(m),
371        }
372    }
373
374    /// Get a named input by key.
375    pub fn get(&self, key: &str) -> Option<&ArtifactRef> {
376        match self {
377            StepInputs::Single(_) => None,
378            StepInputs::Named(m) => m.get(key),
379        }
380    }
381}