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