Skip to main content

rustev_runtime/
progress.rs

1//! Optional value-free progress observation (spec 042, 3.7.3).
2use rustev_contract::{
3    evidence::StepStatus,
4    judgment::{Derivation, Unresolved},
5};
6use std::{
7    panic::{AssertUnwindSafe, catch_unwind},
8    sync::atomic::{AtomicU64, Ordering},
9};
10
11/// A recorded result, without value bytes or authorization semantics.
12#[derive(Debug, Clone, PartialEq)]
13pub struct StepProgress {
14    pub step_id: String,
15    pub instance_ids: Vec<String>,
16    pub outcome: Result<(), Unresolved>,
17    pub derivation: Derivation,
18}
19
20/// A step a guard or tiered widening skipped (spec 036 3.8): no value and
21/// no reason, only the compiled derivation bound of the step.
22#[derive(Debug, Clone, PartialEq)]
23pub struct SkippedStep {
24    pub step_id: String,
25    pub instance_ids: Vec<String>,
26    pub derivation: Derivation,
27}
28
29/// Synchronous, non-blocking callbacks. Implementations must perform no I/O,
30/// acquire no contended locks, and never wait for a consumer. Panics are counted
31/// and contained by the runtime. Observation cannot alter evidence.
32pub trait ProgressObserver: Send + Sync {
33    /// Admission was acquired; this decision is no longer queued.
34    fn admitted(&self) {}
35    fn step(&self, progress: StepProgress);
36    /// A recorded step was skipped (spec 042 3.7.1). Ignored by default, so
37    /// an observer written before skipped steps existed is unchanged.
38    fn skipped(&self, _step: SkippedStep) {}
39}
40
41pub(crate) struct Observing<'a> {
42    pub observer: &'a dyn ProgressObserver,
43    pub panics: &'a AtomicU64,
44}
45impl Observing<'_> {
46    pub fn call(&self, f: impl FnOnce(&dyn ProgressObserver)) {
47        if catch_unwind(AssertUnwindSafe(|| f(self.observer))).is_err() {
48            self.panics.fetch_add(1, Ordering::Relaxed);
49        }
50    }
51    pub fn remaining(
52        &self,
53        evidence: &rustev_contract::evidence::EvidenceRecord,
54        requests: &[rustev_contract::run::RequestRecord],
55        plan: &rustev_contract::plan::Plan,
56    ) {
57        for step in &evidence.steps {
58            // Supplied semantic instances were already observed at supply time.
59            if requests
60                .iter()
61                .any(|r| r.step == step.step && r.instance == step.instance)
62            {
63                continue;
64            }
65            // Unresolved and skipped steps have no dynamic derivation in core
66            // evidence: report the compiled, conservative derivation bound.
67            let compiled = || {
68                plan.steps
69                    .iter()
70                    .find(|s| s.id == step.step)
71                    .expect("recorded step belongs to plan")
72                    .derivation
73            };
74            let (outcome, derivation) = match &step.status {
75                StepStatus::Skipped { .. } => {
76                    let derivation = compiled();
77                    self.call(|o| {
78                        o.skipped(SkippedStep {
79                            step_id: step.step.clone(),
80                            instance_ids: step.instance.clone(),
81                            derivation,
82                        })
83                    });
84                    continue;
85                }
86                StepStatus::Resolved { derivation, .. } => (Ok(()), *derivation),
87                StepStatus::Unresolved(reason) => (Err(reason.clone()), compiled()),
88            };
89            self.call(|o| {
90                o.step(StepProgress {
91                    step_id: step.step.clone(),
92                    instance_ids: step.instance.clone(),
93                    outcome,
94                    derivation,
95                })
96            });
97        }
98    }
99}