Skip to main content

sim_expr_tree_calc/calc/
model.rs

1use std::{
2    fmt,
3    hash::{Hash, Hasher},
4};
5
6use sim_incremental_core::{IncrementalError, QueryBudgets};
7use sim_kernel::{CanonicalKey, CapabilityName, Cx, Value};
8
9/// Absolute safety ceilings for one expression-tree calculation.
10///
11/// Requested limits are always clamped to these values. This keeps a persisted
12/// or caller-supplied policy from turning malformed source into unbounded host
13/// recursion or output.
14pub const HARD_MAX_WORK: usize = 1_000_000;
15pub const HARD_MAX_OBSERVATIONS: usize = 100_000;
16pub const HARD_MAX_QUERY_DEPTH: usize = 64;
17pub const HARD_MAX_OUTPUT: usize = 1_048_576;
18pub const HARD_MAX_EXPR_DEPTH: usize = 128;
19
20pub(super) type ContextFactory = dyn Fn() -> Cx + Send + Sync + 'static;
21
22/// A query key in the expression-tree incremental graph.
23#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24pub enum CalcQuery {
25    /// The calculated result of one cell.
26    Cell(String),
27    /// A name lookup slot, including a previously missing name.
28    NameSlot(String),
29    /// One traversed segment of a path lookup.
30    LookupStep(String),
31    /// A directory listing inspected during lookup.
32    Listing(String),
33    /// The observed epoch of a mounted backend.
34    MountEpoch(String),
35    /// Effective inherited calculation policy for one canonical cell.
36    EffectivePolicy(String),
37    /// Effective inherited authority policy for one canonical cell.
38    AuthorityPolicy(String),
39    /// Installed source/result codec registry.
40    CodecRegistry,
41    /// Open-time authority ceiling.
42    AuthorityCeiling,
43    /// Explicit force epoch for one canonical cell.
44    ForceEpoch(String),
45}
46
47/// Caller-requested limits for one verification.
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub struct CalcLimits {
50    /// Query executions plus explicitly charged expression nodes.
51    pub max_work: usize,
52    /// Dynamic dependency observations.
53    pub max_observations: usize,
54    /// Nested cell-query depth.
55    pub max_query_depth: usize,
56    /// Aggregate canonical output units.
57    pub max_output: usize,
58}
59
60impl CalcLimits {
61    /// Builds an explicit requested policy. Every field is hard-clamped.
62    #[must_use]
63    pub const fn new(
64        max_work: usize,
65        max_observations: usize,
66        max_query_depth: usize,
67        max_output: usize,
68    ) -> Self {
69        Self {
70            max_work,
71            max_observations,
72            max_query_depth,
73            max_output,
74        }
75    }
76
77    pub(super) fn clamped(self) -> QueryBudgets {
78        QueryBudgets::new(
79            self.max_work.min(HARD_MAX_WORK),
80            self.max_observations.min(HARD_MAX_OBSERVATIONS),
81            self.max_query_depth.min(HARD_MAX_QUERY_DEPTH),
82            self.max_output.min(HARD_MAX_OUTPUT),
83        )
84    }
85}
86
87impl Default for CalcLimits {
88    fn default() -> Self {
89        Self::new(
90            HARD_MAX_WORK,
91            HARD_MAX_OBSERVATIONS,
92            HARD_MAX_QUERY_DEPTH,
93            HARD_MAX_OUTPUT,
94        )
95    }
96}
97
98/// A memoized, deterministic calculation failure.
99#[derive(Clone, Debug, Eq, Hash, PartialEq)]
100pub enum CellFailure {
101    /// SIM evaluation rejected the ordinary source expression.
102    Evaluation {
103        /// Stable diagnostic text.
104        message: String,
105    },
106    /// Dynamic dependency evaluation entered a cycle.
107    Cycle {
108        /// Deterministic path with the repeated query at the end.
109        path: Vec<CalcQuery>,
110    },
111    /// Expression nesting exceeded the non-configurable host safety ceiling.
112    ExpressionDepth {
113        /// Hard ceiling.
114        limit: usize,
115    },
116    /// Effective trigger policy prevented this attempt.
117    Blocked {
118        /// Canonical blocked cell.
119        path: String,
120        /// Stable policy explanation.
121        reason: String,
122    },
123    /// Diminished authority did not contain a required capability.
124    RequiredCapability {
125        /// Canonical cell requiring the capability.
126        path: String,
127        /// First missing required capability in stable name order.
128        capability: CapabilityName,
129    },
130}
131
132impl fmt::Display for CellFailure {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        match self {
135            Self::Evaluation { message } => write!(f, "cell evaluation failed: {message}"),
136            Self::Cycle { path } => write!(f, "cell dependency cycle {path:?}"),
137            Self::ExpressionDepth { limit } => {
138                write!(f, "cell expression depth exceeds hard limit {limit}")
139            }
140            Self::Blocked { path, reason } => {
141                write!(f, "cell {path} is blocked: {reason}")
142            }
143            Self::RequiredCapability { path, capability } => {
144                write!(f, "cell {path} requires capability {capability}")
145            }
146        }
147    }
148}
149
150/// A current-result error. Last-good data is available separately.
151#[derive(Clone, Debug, Eq, PartialEq)]
152pub enum CalcError {
153    /// No result has committed since the source last changed.
154    NotCalculated {
155        /// Absolute cell path.
156        path: String,
157    },
158    /// A deterministic cell failure was committed as the current memo.
159    Cell(CellFailure),
160    /// Verification stopped before a current memo could commit.
161    Incremental(IncrementalError<CalcQuery>),
162    /// An automatic continuation did not match the current restored queue.
163    UnknownAutomaticContinuation {
164        /// Supplied queue generation.
165        generation: u64,
166    },
167    /// A persisted automatic queue snapshot was structurally invalid.
168    CorruptAutomaticQueue {
169        /// Canonical duplicated or invalid cell key.
170        cell: String,
171    },
172}
173
174impl fmt::Display for CalcError {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            Self::NotCalculated { path } => write!(f, "cell {path} has no current result"),
178            Self::Cell(failure) => failure.fmt(f),
179            Self::Incremental(error) => error.fmt(f),
180            Self::UnknownAutomaticContinuation { generation } => {
181                write!(f, "unknown automatic continuation generation {generation}")
182            }
183            Self::CorruptAutomaticQueue { cell } => {
184                write!(f, "corrupt automatic queue entry for {cell}")
185            }
186        }
187    }
188}
189
190impl std::error::Error for CalcError {}
191
192/// A retained successful value explicitly labelled as historical.
193#[derive(Clone)]
194pub struct LastGoodValue {
195    pub(super) value: Value,
196}
197
198impl LastGoodValue {
199    /// The stable label callers must present beside this historical value.
200    #[must_use]
201    pub const fn label(&self) -> &'static str {
202        "last-good"
203    }
204
205    /// Borrows the retained ordinary SIM value.
206    #[must_use]
207    pub fn value(&self) -> &Value {
208        &self.value
209    }
210}
211
212#[derive(Clone)]
213pub(super) enum MemoOutcome {
214    Value(Value),
215    Failure(CellFailure),
216}
217
218#[derive(Clone, Debug, Eq, Hash, PartialEq)]
219enum MemoIdentity {
220    Canonical(CanonicalKey),
221    Volatile(u64),
222    Failure(CellFailure),
223}
224
225#[derive(Clone)]
226pub(super) struct MemoValue {
227    pub(super) outcome: MemoOutcome,
228    identity: MemoIdentity,
229}
230
231impl MemoValue {
232    pub(super) fn canonical(value: Value, key: CanonicalKey) -> Self {
233        Self {
234            outcome: MemoOutcome::Value(value),
235            identity: MemoIdentity::Canonical(key),
236        }
237    }
238
239    pub(super) fn volatile(value: Value, nonce: u64) -> Self {
240        Self {
241            outcome: MemoOutcome::Value(value),
242            identity: MemoIdentity::Volatile(nonce),
243        }
244    }
245
246    pub(super) fn failure(failure: CellFailure) -> Self {
247        Self {
248            outcome: MemoOutcome::Failure(failure.clone()),
249            identity: MemoIdentity::Failure(failure),
250        }
251    }
252
253    pub(super) fn is_volatile(&self) -> bool {
254        matches!(self.identity, MemoIdentity::Volatile(_))
255    }
256}
257
258impl fmt::Debug for MemoValue {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        f.debug_struct("MemoValue")
261            .field("identity", &self.identity)
262            .finish_non_exhaustive()
263    }
264}
265
266impl Hash for MemoValue {
267    fn hash<H: Hasher>(&self, state: &mut H) {
268        self.identity.hash(state);
269    }
270}