dsfb_densor_runtime/runtime.rs
1//! The deterministic execution spine: load manifest → validate authority hashes → execute stages → seal → emit.
2//!
3//! The runtime is intentionally thin. Its only jobs are (1) to validate the frozen manifest, (2) to *gate* each
4//! stage — refusing any stage that declares no authority, or whose declared authority is not in the manifest's
5//! frozen allow-list — and (3) to seal the ordered stage receipts into a tamper-evident [`RuntimeReceiptV1`].
6//! It never mutates authority, never performs I/O, and admits no claim without an authority anchor.
7//!
8//! Because [`RuntimeStage`] is generic over `I`/`O`, a heterogeneous multi-stage pipeline is built by the caller
9//! (each stage's `O` feeding the next stage's `I`); the runtime's contribution is the per-stage *gate* +
10//! `record`, and the final `seal`. The chemical crate is one such caller; this crate carries no chemical logic.
11
12use crate::authority::AuthorityHash;
13use crate::errors::RuntimeError;
14use crate::manifest::DensorManifest;
15use crate::receipt::RuntimeReceiptV1;
16use crate::stage::{RuntimeStage, StageReceipt, StageReceiptSummary};
17
18/// Accumulates the gated, validated stage summaries of one run, then seals them.
19pub struct Runtime<'m> {
20 manifest: &'m DensorManifest,
21 stages: Vec<StageReceiptSummary>,
22}
23
24impl<'m> Runtime<'m> {
25 /// Start a run against a validated manifest. Validates the manifest up front (fails closed).
26 pub fn start(manifest: &'m DensorManifest) -> Result<Self, RuntimeError> {
27 manifest.validate()?;
28 Ok(Runtime {
29 manifest,
30 stages: Vec::new(),
31 })
32 }
33
34 /// Gate a stage's declared authorities against the manifest's frozen allow-list. A stage with no authorities,
35 /// or one citing an authority the manifest does not pin, is refused (no claim without an authority anchor).
36 fn gate(&self, stage_id: &str, authorities: &[AuthorityHash]) -> Result<(), RuntimeError> {
37 if authorities.is_empty() {
38 return Err(RuntimeError::MissingAuthority {
39 stage: stage_id.to_string(),
40 });
41 }
42 for a in authorities {
43 if !self.manifest.permits_authority(a) {
44 return Err(RuntimeError::AuthorityMismatch {
45 stage: stage_id.to_string(),
46 authority: a.name.clone(),
47 });
48 }
49 }
50 Ok(())
51 }
52
53 /// Execute one stage on `input`, gating its authorities first, then recording its receipt summary. Returns
54 /// the stage's typed output so the caller can feed it into the next stage. Deterministic given a deterministic
55 /// stage (the runtime adds no nondeterminism).
56 pub fn run_stage<I, O, S: RuntimeStage<I, O>>(
57 &mut self,
58 stage: &S,
59 input: I,
60 ) -> Result<O, RuntimeError> {
61 self.gate(stage.stage_id(), stage.authority_hashes())?;
62 let receipt: StageReceipt<O> = stage.execute(input)?;
63 // Defence in depth: the stage's own receipt authorities must also be gated (a stage cannot smuggle a
64 // different authority into its receipt than the one it advertised).
65 self.gate(&receipt.stage_id, &receipt.authority_hashes)?;
66 self.stages.push(receipt.summary());
67 Ok(receipt.output)
68 }
69
70 /// Seal the run: build the tamper-evident [`RuntimeReceiptV1`] over the ordered stage summaries.
71 pub fn seal(self) -> RuntimeReceiptV1 {
72 RuntimeReceiptV1::build(self.manifest, self.stages)
73 }
74}