Skip to main content

sim_incremental_core/projection/
model.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    error::Error,
4    fmt,
5    sync::Mutex,
6};
7
8use sim_kernel::{ContentId, Datum};
9
10macro_rules! string_id {
11    ($name:ident, $doc:literal) => {
12        #[doc = $doc]
13        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14        pub struct $name(String);
15
16        impl $name {
17            /// Constructs an identifier after rejecting an empty spelling.
18            pub fn new(value: impl Into<String>) -> Result<Self, ProjectionError> {
19                let value = value.into();
20                if value.trim().is_empty() {
21                    return Err(ProjectionError::InvalidIdentifier(stringify!($name)));
22                }
23                Ok(Self(value))
24            }
25
26            /// Returns the stable identifier spelling.
27            #[must_use]
28            pub fn as_str(&self) -> &str {
29                &self.0
30            }
31        }
32
33        impl fmt::Display for $name {
34            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35                self.0.fmt(formatter)
36            }
37        }
38    };
39}
40
41string_id!(FactId, "Stable identity of one semantic observed fact.");
42string_id!(
43    ConclusionId,
44    "Stable identity of a conclusion consuming facts."
45);
46string_id!(
47    ProjectionKindRef,
48    "Open identifier of a loaded projection kind."
49);
50
51/// Exact package and implementation identity of a projection provider.
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct PackageIdentity {
54    /// Registry package name.
55    pub name: String,
56    /// Semantic version used by the provider.
57    pub version: String,
58    /// Content identity of the exact loaded implementation.
59    pub code: ContentId,
60}
61
62/// One fact with a semantic value and a diagnostic envelope.
63///
64/// Only `semantic` enters projection identity. The envelope may carry timing,
65/// retries, path aliases, broker location, or logs without invalidating reuse.
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct ObservedFact {
68    /// Canonical value available to a projector.
69    pub semantic: Datum,
70    /// Diagnostic data retained outside semantic identity.
71    pub envelope: Option<Datum>,
72}
73
74/// Immutable observed facts from which a selector creates a bounded view.
75#[derive(Clone, Debug, Default, Eq, PartialEq)]
76pub struct ObservedWorld {
77    facts: BTreeMap<FactId, ObservedFact>,
78}
79
80impl ObservedWorld {
81    /// Builds a world and refuses duplicate fact identities.
82    pub fn new(
83        facts: impl IntoIterator<Item = (FactId, ObservedFact)>,
84    ) -> Result<Self, ProjectionError> {
85        let mut world = Self::default();
86        for (id, fact) in facts {
87            if world.facts.insert(id.clone(), fact).is_some() {
88                return Err(ProjectionError::DuplicateFact(id));
89            }
90        }
91        Ok(world)
92    }
93
94    pub(crate) fn select(
95        &self,
96        selector: &DeclaredInputSelector,
97    ) -> Result<ProjectionInputs, ProjectionError> {
98        let mut selected = BTreeMap::new();
99        for id in &selector.facts {
100            let fact = self
101                .facts
102                .get(id)
103                .ok_or_else(|| ProjectionError::MissingFact(id.clone()))?;
104            selected.insert(id.clone(), fact.semantic.clone());
105        }
106        Ok(ProjectionInputs {
107            facts: selected,
108            accessed: Mutex::new(BTreeSet::new()),
109        })
110    }
111}
112
113/// Closed set of facts admitted as one provider invocation's entire input.
114#[derive(Clone, Debug, Default, Eq, PartialEq)]
115pub struct DeclaredInputSelector {
116    facts: BTreeSet<FactId>,
117}
118
119impl DeclaredInputSelector {
120    /// Builds a canonical selector.
121    #[must_use]
122    pub fn new(facts: impl IntoIterator<Item = FactId>) -> Self {
123        Self {
124            facts: facts.into_iter().collect(),
125        }
126    }
127
128    /// Returns selected fact identities in canonical order.
129    pub fn facts(&self) -> impl ExactSizeIterator<Item = &FactId> {
130        self.facts.iter()
131    }
132}
133
134/// Bounded immutable input view supplied to a projector.
135///
136/// Its fields are private so a provider cannot reach the rest of the world or
137/// any diagnostic envelope. Every successful read is recorded.
138#[derive(Debug)]
139pub struct ProjectionInputs {
140    facts: BTreeMap<FactId, Datum>,
141    accessed: Mutex<BTreeSet<FactId>>,
142}
143
144impl ProjectionInputs {
145    /// Reads a selected fact and records the access.
146    pub fn get(&self, id: &FactId) -> Option<&Datum> {
147        let value = self.facts.get(id)?;
148        self.accessed
149            .lock()
150            .expect("projection access mutex poisoned")
151            .insert(id.clone());
152        Some(value)
153    }
154
155    /// Iterates over every selected fact and records each access.
156    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&FactId, &Datum)> {
157        self.accessed
158            .lock()
159            .expect("projection access mutex poisoned")
160            .extend(self.facts.keys().cloned());
161        self.facts.iter()
162    }
163
164    pub(crate) fn accessed(&self) -> BTreeSet<FactId> {
165        self.accessed
166            .lock()
167            .expect("projection access mutex poisoned")
168            .clone()
169    }
170}
171
172/// Canonical provider output and its declared fact dependencies.
173#[derive(Clone, Debug, Eq, PartialEq)]
174pub struct ProjectionOutput {
175    /// Canonical semantic projection value.
176    pub value: Datum,
177    /// Exact facts on which the value depends.
178    pub dependencies: BTreeSet<FactId>,
179}
180
181/// Loaded implementation of an open projection kind.
182pub trait ProjectionProvider: Send + Sync {
183    /// Open kind implemented by this provider.
184    fn kind(&self) -> &ProjectionKindRef;
185    /// Stable Shape identity used to check configuration before invocation.
186    fn config_shape(&self) -> &ContentId;
187    /// Projects solely over the bounded immutable input view.
188    fn project(
189        &self,
190        inputs: &ProjectionInputs,
191        config: &Datum,
192    ) -> Result<ProjectionOutput, ProjectionError>;
193}
194
195/// Checked request for one loaded projection.
196#[derive(Clone, Debug, Eq, PartialEq)]
197pub struct ProjectionSpec {
198    /// Stable request identity.
199    pub id: ContentId,
200    /// Open loaded provider kind.
201    pub kind: ProjectionKindRef,
202    /// Provider-specific configuration value.
203    pub config: Datum,
204    /// Shape that must match the loaded provider's declared config Shape.
205    pub config_shape: ContentId,
206    /// Exact provider package and code identity.
207    pub provider: PackageIdentity,
208}
209
210/// Fuel, memory, output, and selected-input ceilings for projection.
211#[derive(Clone, Copy, Debug, Eq, PartialEq)]
212pub struct ProjectionBudget {
213    /// Maximum selected facts.
214    pub max_inputs: usize,
215    /// Maximum canonical output bytes.
216    pub max_output_bytes: usize,
217    /// Maximum wasm fuel, when the closed wasm route is used.
218    pub max_fuel: u64,
219    /// Maximum wasm linear-memory bytes.
220    pub max_memory_bytes: usize,
221}
222
223/// Closed deterministic imports made available to a wasm projector.
224#[derive(Clone, Debug, Default, Eq, PartialEq)]
225pub struct DeterministicImportManifest {
226    /// Fully qualified `module/name` imports in canonical order.
227    pub imports: BTreeSet<String>,
228}
229
230/// Runtime semantics that affect deterministic projection.
231#[derive(Clone, Debug, Eq, PartialEq)]
232pub struct ExecutionSemantics {
233    /// Stable semantics family and version.
234    pub id: String,
235    /// Whether canonical NaN behavior is fixed.
236    pub canonical_nan: bool,
237    /// Whether collection traversal order is fixed.
238    pub canonical_collections: bool,
239    /// Whether every invocation starts with fresh mutable instance state.
240    pub fresh_instance: bool,
241}
242
243/// Admission policy bound before projector qualification.
244#[derive(Clone, Debug, Eq, PartialEq)]
245pub struct ProjectorPolicy {
246    /// Semantic identity of the input Shape.
247    pub input_shape: ContentId,
248    /// Complete selected input universe.
249    pub reads: DeclaredInputSelector,
250    /// Closed deterministic import universe.
251    pub imports: DeterministicImportManifest,
252    /// Deterministic runtime semantics.
253    pub execution: ExecutionSemantics,
254    /// Bounded work and output policy.
255    pub budgets: ProjectionBudget,
256    /// Whether effect confinement is independently required on this host.
257    pub requires_confinement: bool,
258}
259
260/// Source closure admitted for a trusted native projector.
261#[derive(Clone, Debug, Eq, PartialEq)]
262pub struct QualifiedSourceClosure {
263    /// Exact implementation content identity.
264    pub code: ContentId,
265    /// Exact transitive runtime dependency closure identity.
266    pub dependencies: ContentId,
267    /// Independent source/dependency review evidence identity.
268    pub review: ContentId,
269}
270
271/// Runtime admitted for the closed wasm route.
272#[derive(Clone, Debug, Eq, PartialEq)]
273pub struct QualifiedRuntime {
274    /// Exact runtime implementation identity.
275    pub code: ContentId,
276    /// Qualified execution semantics.
277    pub semantics: ExecutionSemantics,
278}
279
280/// One of the two projector admission routes allowed by the roadmap.
281#[derive(Clone, Debug, Eq, PartialEq)]
282pub enum ProjectorQualification {
283    /// Exact reviewed native code and dependency closure.
284    TrustedNative {
285        /// Qualified source closure.
286        source: QualifiedSourceClosure,
287        /// Policy identity reviewed with that source.
288        policy: ContentId,
289    },
290    /// Closed wasm module with verified imports and runtime behavior.
291    ClosedWasm {
292        /// Exact semantic module identity.
293        module: ContentId,
294        /// Policy identity used for admission.
295        policy: ContentId,
296        /// Qualified runtime and semantics.
297        runtime: QualifiedRuntime,
298        /// Verified complete import manifest.
299        imports: DeterministicImportManifest,
300        /// Admission evidence identity.
301        admission: ContentId,
302    },
303}
304
305impl ProjectorQualification {
306    pub(crate) fn implementation(&self) -> &ContentId {
307        match self {
308            Self::TrustedNative { source, .. } => &source.code,
309            Self::ClosedWasm { module, .. } => module,
310        }
311    }
312
313    pub(crate) fn policy(&self) -> &ContentId {
314        match self {
315            Self::TrustedNative { policy, .. } | Self::ClosedWasm { policy, .. } => policy,
316        }
317    }
318}
319
320/// Independent bounded-effect confinement evidence.
321#[derive(Clone, Debug, Eq, PartialEq)]
322pub struct ConfinementEvidence {
323    /// Membrane implementation identity.
324    pub membrane: String,
325    /// Exact bounded policy identity.
326    pub policy: ContentId,
327    /// Live host readiness was probed at dispatch.
328    pub live: bool,
329}
330
331/// Exact facts read through the bounded projection input view.
332#[derive(Clone, Debug, Eq, PartialEq)]
333pub struct MediatedAccessWitness {
334    /// Facts selected by policy.
335    pub selected: BTreeSet<FactId>,
336    /// Facts actually read by the provider.
337    pub accessed: BTreeSet<FactId>,
338}
339
340/// One causal path from requested conclusion to changed leaf fact.
341#[derive(Clone, Debug, Eq, PartialEq)]
342pub struct Explanation {
343    /// Requested conclusion.
344    pub conclusion: ConclusionId,
345    /// Changed fact on which it depends.
346    pub fact: FactId,
347    /// Ordered owner-local path including conclusion and fact endpoints.
348    pub path: Vec<String>,
349}
350
351/// Durable semantic digest of one qualified projection.
352#[derive(Clone, Debug, Eq, PartialEq)]
353pub struct ProjectionDigest(pub ContentId);
354
355/// Complete checked result of one provider invocation.
356#[derive(Clone, Debug, Eq, PartialEq)]
357pub struct ProjectionResult {
358    /// Canonical semantic projection.
359    pub projection: Datum,
360    /// Exact selected and accessed facts.
361    pub mediated_access: MediatedAccessWitness,
362    /// Qualification used for this invocation.
363    pub projector_qualification: ProjectorQualification,
364    /// Independent confinement evidence, when required.
365    pub confinement: Option<ConfinementEvidence>,
366    /// Durable semantic identity excluding diagnostic envelopes.
367    pub digest: ProjectionDigest,
368    /// Conclusions affected by the consumed facts.
369    pub affected: Vec<ConclusionId>,
370    /// Causal explanations for affected conclusion/fact pairs.
371    pub explanations: Vec<Explanation>,
372}
373
374/// Fail-closed projection refusal with distinct policy boundaries.
375#[derive(Clone, Debug, Eq, PartialEq)]
376pub enum ProjectionError {
377    /// An identifier was empty.
378    InvalidIdentifier(&'static str),
379    /// A world declared the same fact twice.
380    DuplicateFact(FactId),
381    /// A selected fact was absent.
382    MissingFact(FactId),
383    /// No loaded provider owns the requested open kind.
384    UnknownProvider(ProjectionKindRef),
385    /// More than one loaded provider claimed the same kind.
386    DuplicateProvider(ProjectionKindRef),
387    /// Loaded provider and requested config Shape disagree.
388    ConfigShapeMismatch,
389    /// Configuration did not satisfy its declared Shape.
390    InvalidConfig(String),
391    /// A path selector or logical path was not canonical or valid.
392    InvalidPathSelection(String),
393    /// Provider code identity differs from qualified loaded code.
394    CodeIdentityMismatch,
395    /// Projector qualification is missing or invalid.
396    UnqualifiedProjector(String),
397    /// Required confinement is absent or unavailable.
398    UnavailableConfinement(String),
399    /// Provider read and dependency declarations disagree.
400    UndeclaredAccess {
401        /// Fact read without a matching dependency claim.
402        accessed: FactId,
403    },
404    /// Provider claimed a dependency it never read.
405    UnreadDependency(FactId),
406    /// A configured budget was exceeded.
407    BudgetExceeded(&'static str),
408    /// Canonical projection identity could not be constructed.
409    Canonical(String),
410    /// A requested explanation path does not exist.
411    MissingExplanation {
412        /// Requested conclusion.
413        conclusion: ConclusionId,
414        /// Requested leaf fact.
415        fact: FactId,
416    },
417}
418
419impl fmt::Display for ProjectionError {
420    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
421        write!(formatter, "{self:?}")
422    }
423}
424
425impl Error for ProjectionError {}