rite-model 0.7.2

Domain model and intermediate representation for the Rite key ceremony DSL
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
//! Resolved ceremony IR types.
//!
//! These types represent a fully resolved ceremony, ready for execution.
//! All references have been validated, execution order computed, and
//! parameter values merged with defaults.

use super::SymbolTable;
use super::ids::{ActId, ArtifactId, MaterialId, OutputId, ParamId, RoleId, SectionId, StepId};
use crate::expression::ExprValue;
use crate::material::MaterialSource;
use crate::types::{ActionType, DutyType, Metadata, OutputType, ParameterType};
use rite_sdk::BackendConfig;
use std::collections::HashMap;

/// A fully resolved ceremony, ready for execution.
///
/// All references have been validated, the execution order has been computed,
/// and parameter values have been merged with external inputs.
#[derive(Debug, Clone)]
pub struct Ceremony {
    /// Ceremony metadata (name, description).
    pub metadata: Metadata,

    /// Resolved roles, keyed by role ID.
    pub roles: SymbolTable<RoleId, Role>,

    /// Resolved acts, keyed by act ID (for script display).
    pub acts: SymbolTable<ActId, Act>,

    /// Resolved sections, keyed by section ID.
    pub sections: SymbolTable<SectionId, Section>,

    /// Resolved parameters with their values, keyed by parameter ID.
    pub parameters: SymbolTable<ParamId, Parameter>,

    /// Resolved materials with their sources, keyed by material ID.
    pub materials: SymbolTable<MaterialId, Material>,

    /// Pre-ceremony conditions (prose-only checklist).
    pub prerequisites: Vec<String>,

    /// Resolved outputs with their destinations, keyed by output ID.
    pub outputs: SymbolTable<OutputId, Output>,

    /// Backend configurations, keyed by backend name.
    pub backends: HashMap<String, BackendConfig>,

    /// Steps in execution order.
    ///
    /// Pre-computed topological order based on sections.
    pub execution_plan: Vec<Step>,

    /// Post-ceremony duties, in declaration order.
    pub after: Vec<PostCeremonyDuty>,

    /// Digest of the ceremony YAML this ceremony was resolved from. Covers
    /// the template only: run-time inputs are merged into the fields above.
    pub source_digest: crate::Sha256Digest,
}

/// A resolved role.
#[derive(Debug, Clone)]
pub struct Role {
    /// Role identifier.
    pub id: RoleId,

    /// Human-readable name (derived or explicit).
    pub name: String,

    /// Role type: the prefix before `__` (or the whole ID if no `__`).
    ///
    /// E.g. `"witness__1"` → `"witness"`, `"operator"` → `"operator"`.
    pub role_type: String,

    /// The person assigned to this role slot, if any.
    pub person: Option<String>,
}

/// A resolved act (for script display).
#[derive(Debug, Clone)]
pub struct Act {
    /// Act identifier.
    pub id: ActId,

    /// Human-readable name.
    pub name: Option<String>,

    /// Preamble text displayed at the start of the act.
    pub description: Option<String>,
}

/// A resolved section.
#[derive(Debug, Clone)]
pub struct Section {
    /// Section identifier.
    pub id: SectionId,

    /// Reference to the act this section belongs to.
    pub act: Option<ActId>,

    /// Human-readable name.
    pub name: Option<String>,

    /// Section description.
    pub description: Option<String>,

    /// Default role for steps in this section.
    pub default_role: Option<RoleId>,
}

/// A resolved step, ready for execution.
#[derive(Debug, Clone)]
pub struct Step {
    /// Step identifier.
    pub id: StepId,

    /// Display label for this step (e.g., `"1.1"`, `"2.3"`).
    ///
    /// Uses hierarchical numbering: `act.step_within_act`.
    /// When there's only one act, just the step number (e.g., `"1"`, `"2"`).
    pub step_label: String,

    /// Section this step belongs to.
    pub section: SectionId,

    /// Action to perform.
    pub action: ActionType,

    /// Backend to use for this step (if action requires one).
    ///
    /// References a backend declared in the ceremony's backends section.
    pub backend: Option<String>,

    /// Role that performs this step (resolved from step or section default).
    pub role: Option<RoleId>,

    /// Human-confirmed preconditions before this step executes.
    pub preconditions: Vec<String>,

    /// Action-specific parameters with expressions parsed (not yet evaluated).
    ///
    /// The runtime evaluates these to `serde_json::Value` before passing to handlers.
    pub with: ExprValue,

    /// Validated input artifact references (for ordering validation).
    pub reads: Vec<ArtifactRef>,

    /// Pre-resolved input references for handlers.
    ///
    /// Handlers can access artifact IDs directly without parsing.
    pub reads_resolved: Option<StepInputs>,

    /// Artifact ID this step produces.
    pub creates: Option<ArtifactId>,

    /// Human-readable description for display (may contain interpolated expressions).
    pub description: Option<ExprValue>,

    /// Skip the default pause after step completion.
    ///
    /// When `true`, the executor auto-advances without waiting for user acknowledgment.
    pub silent: bool,

    /// How the runtime treats a *transient* failure of this step.
    ///
    /// The DSL constrains the retry-by-default; see [`RetryPolicy`]. Absent in
    /// the DSL means [`RetryPolicy::Prompt`].
    pub retry: RetryPolicy,
}

/// How the runtime treats a transient (retriable) failure of a step.
///
/// Rite ceremonies always have a human at the console, so the default is to
/// pause and let the operator decide, rather than burn a fixed retry budget.
/// The DSL field constrains that default rather than enabling it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RetryPolicy {
    /// Pause on a transient error and prompt the operator (Retry / Abort),
    /// unlimited times. The default when the DSL omits `retry:`.
    #[default]
    Prompt,
    /// Never retry: a transient error fails the step immediately. For steps
    /// where repeated attempts are themselves security-relevant.
    Never,
    /// Retry up to this many total attempts, then fail. A hard cap.
    MaxAttempts(u32),
}

/// A resolved parameter with its value.
#[derive(Debug, Clone)]
pub struct Parameter {
    /// Parameter identifier.
    pub id: ParamId,

    /// Declared type.
    pub declared_type: ParameterType,

    /// Resolved value (from CLI/env/prompt or default).
    pub value: serde_json::Value,

    /// Human-readable description.
    pub description: Option<String>,
}

/// What kind of material and its resolved source/identifier.
#[derive(Debug, Clone)]
pub enum MaterialKind {
    /// Digital: loaded from file path. `None` if source not yet provided.
    Digital {
        /// Resolved file source, or `None` if not yet provided.
        source: Option<MaterialSource>,
    },
    /// Physical: optional identifier string for display.
    Physical {
        /// Human-readable identifier (e.g., serial number).
        identifier: Option<String>,
        /// Item count for checklist rendering.
        quantity: Option<u32>,
    },
}

/// A resolved material with its source.
#[derive(Debug, Clone)]
pub struct Material {
    /// Material identifier.
    pub id: MaterialId,

    /// What kind of material and its resolved source/identifier.
    pub kind: MaterialKind,

    /// Optional human-readable title for display (falls back to material ID).
    pub title: Option<String>,

    /// Human-readable description.
    pub description: Option<String>,
}

impl Material {
    /// Get the display name for this material (title or ID).
    pub fn display_name(&self) -> &str {
        self.title.as_deref().unwrap_or(self.id.as_str())
    }

    /// Whether this is a digital material.
    pub fn is_digital(&self) -> bool {
        matches!(self.kind, MaterialKind::Digital { .. })
    }

    /// Whether this is a physical material.
    pub fn is_physical(&self) -> bool {
        matches!(self.kind, MaterialKind::Physical { .. })
    }
}

/// A resolved post-ceremony duty.
#[derive(Debug, Clone)]
pub struct PostCeremonyDuty {
    /// Duty identifier (the dict key in the YAML `after:` block).
    pub id: String,

    /// Type of duty.
    pub kind: DutyType,

    /// Role responsible for this duty (resolved from plain role ID string).
    pub role: Option<RoleId>,

    /// Description of this duty (overrides built-in prose for the type).
    pub description: Option<String>,

    /// Checklist sub-items.
    pub items: Vec<String>,

    /// For distribute_* types: who receives.
    pub recipient: Option<String>,

    /// For storage/archive types: where.
    pub location: Option<String>,
}

/// A resolved output declaration.
#[derive(Debug, Clone)]
pub struct Output {
    /// Output identifier.
    pub id: OutputId,

    /// Type of output.
    pub kind: OutputType,

    /// Human-readable description.
    pub description: Option<String>,
}

/// Reference to an artifact (either a material or a produced artifact).
#[derive(Debug, Clone)]
pub enum ArtifactRef {
    /// References a pre-loaded material.
    Material {
        /// Material ID.
        id: MaterialId,
        /// Optional property accessor (e.g., `"public"` from a keypair).
        property: Option<String>,
    },

    /// References an artifact produced by an earlier step.
    Produced {
        /// Artifact ID.
        id: ArtifactId,
        /// Optional property accessor.
        property: Option<String>,
    },
}

impl ArtifactRef {
    /// Get a display name for this reference.
    pub fn display_name(&self) -> String {
        match self {
            ArtifactRef::Material { id, property: None } => id.to_string(),
            ArtifactRef::Material {
                id,
                property: Some(p),
            } => format!("{id}.{p}"),
            ArtifactRef::Produced { id, property: None } => id.to_string(),
            ArtifactRef::Produced {
                id,
                property: Some(p),
            } => format!("{id}.{p}"),
        }
    }

    /// Get the artifact ID from this reference.
    pub fn artifact_id(&self) -> ArtifactId {
        match self {
            ArtifactRef::Material { id, .. } => ArtifactId::new(id.as_str()),
            ArtifactRef::Produced { id, .. } => id.clone(),
        }
    }

    /// Get the property accessor from this reference (if any).
    pub fn property(&self) -> Option<&str> {
        match self {
            ArtifactRef::Material { property, .. } | ArtifactRef::Produced { property, .. } => {
                property.as_deref()
            }
        }
    }
}

/// Pre-resolved input references for a step.
///
/// At resolution time, the resolver parses `${artifact.name}` strings and determines
/// whether they reference materials or produced artifacts. This eliminates runtime
/// string parsing in handlers.
#[derive(Debug, Clone)]
pub enum StepInputs {
    /// Single artifact reference (e.g., `reads: "${artifact.keypair}"`)
    Single(ArtifactRef),
    /// Named artifact references (e.g., `reads: { key_to_wrap: "...", wrapping_key: "..." }`)
    Named(HashMap<String, NamedInput>),
}

/// The value under one key of a `reads:` map: one reference, or a list of
/// them for an action that takes as many as the ceremony gives it.
///
/// Which shape a key takes is the action's contract, and the resolver
/// checks it, so a handler asks for the shape it expects and treats the
/// other as absent.
#[derive(Debug, Clone)]
pub enum NamedInput {
    /// `key: "${artifact.x}"`
    One(ArtifactRef),
    /// `key: ["${artifact.x}", "${artifact.y}"]`, in the order written.
    Many(Vec<ArtifactRef>),
}

impl NamedInput {
    /// Every reference under the key, one or many.
    pub fn refs(&self) -> &[ArtifactRef] {
        match self {
            NamedInput::One(r) => std::slice::from_ref(r),
            NamedInput::Many(refs) => refs,
        }
    }
}

impl StepInputs {
    /// Get a single input reference (returns `None` if this is `Named`).
    pub fn as_single(&self) -> Option<&ArtifactRef> {
        match self {
            StepInputs::Single(r) => Some(r),
            StepInputs::Named(_) => None,
        }
    }

    /// Get named input references (returns `None` if this is `Single`).
    pub fn as_named(&self) -> Option<&HashMap<String, NamedInput>> {
        match self {
            StepInputs::Single(_) => None,
            StepInputs::Named(m) => Some(m),
        }
    }

    /// Get a named input by key, when it holds one reference.
    pub fn get(&self, key: &str) -> Option<&ArtifactRef> {
        match self.as_named()?.get(key)? {
            NamedInput::One(r) => Some(r),
            NamedInput::Many(_) => None,
        }
    }

    /// Get a named input by key, when it holds a list.
    pub fn get_many(&self, key: &str) -> Option<&[ArtifactRef]> {
        match self.as_named()?.get(key)? {
            NamedInput::One(_) => None,
            NamedInput::Many(refs) => Some(refs),
        }
    }
}