Skip to main content

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}