Skip to main content

sim_incremental_core/projection/
engine.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    sync::Arc,
4};
5
6use sim_conformance_core::{
7    LiveCheckerAuthority, LiveCheckerOwner, LiveCheckerReceipt, OwnerBindingId,
8};
9use sim_kernel::{ContentId, Datum, Symbol};
10
11use super::{
12    ConfinementEvidence, FederatedClosure, MediatedAccessWitness, ProjectionDigest,
13    ProjectionError, ProjectionKindRef, ProjectionProvider, ProjectionResult, ProjectionSpec,
14    ProjectorPolicy, ProjectorQualification, QualificationError,
15    admission::{
16        NativeSourceEvidence, ProjectorQualificationVerifier, bootstrap_native_source,
17        content_id_datum,
18    },
19};
20
21/// Port used to validate a provider configuration through its declared Shape.
22pub trait ConfigShapeVerifier {
23    /// Rejects a config that does not match `shape`.
24    fn verify(&self, shape: &ContentId, config: &Datum) -> Result<(), String>;
25}
26
27/// Open loaded registry keyed by projection kind.
28#[derive(Default)]
29pub struct ProjectionRegistry {
30    providers: BTreeMap<ProjectionKindRef, RegisteredProvider>,
31}
32
33struct RegisteredProvider {
34    identity: super::PackageIdentity,
35    provider: Arc<dyn ProjectionProvider>,
36    bootstrap: Option<BootstrapAdmission>,
37}
38
39/// This registry's own live checker owner and dependency identity for one
40/// bootstrap-admitted provider. Never exposed: a caller can only reach it
41/// indirectly, through [`ProjectionRegistry::qualification_for`], which
42/// re-verifies it fresh on every call and hands back an opaque
43/// [`ProjectorQualification`] -- never the owner, authority, or receipt
44/// themselves.
45struct BootstrapAdmission {
46    #[allow(dead_code)] // kept alive: authority/receipt are weak handles into it
47    owner: LiveCheckerOwner,
48    authority: LiveCheckerAuthority,
49    receipt: LiveCheckerReceipt,
50    dependencies: ContentId,
51}
52
53impl ProjectionRegistry {
54    /// Constructs an empty registry.
55    #[must_use]
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Loads one provider without changing a central kind enum.
61    ///
62    /// # Errors
63    /// Refuses without changing this registry's state if `kind` is already
64    /// loaded: a caller cannot silently replace an admitted provider (and,
65    /// with it, drop the live checker owner backing its qualification) by
66    /// registering a second one under the same kind.
67    pub fn register(
68        &mut self,
69        identity: super::PackageIdentity,
70        provider: Arc<dyn ProjectionProvider>,
71    ) -> Result<(), ProjectionError> {
72        let kind = provider.kind().clone();
73        if self.providers.contains_key(&kind) {
74            return Err(ProjectionError::DuplicateProvider(kind));
75        }
76        self.providers.insert(
77            kind,
78            RegisteredProvider {
79                identity,
80                provider,
81                bootstrap: None,
82            },
83        );
84        Ok(())
85    }
86
87    /// Grants an already-registered provider `EvidenceGrade::Bootstrap`
88    /// native-source admission: proof that its declared code identity
89    /// equals the identity `install_baseline_providers` itself measured, and
90    /// nothing stronger.
91    ///
92    /// `pub(crate)`: only [`super::install_baseline_providers`] calls this,
93    /// using a `code`/`dependencies` pair it computed from its own compiled
94    /// source, never a value a downstream crate supplied. This is the sole
95    /// place in the crate that stores a live checker owner, and the sole
96    /// route by which `kind` can later yield a `TrustedNative`
97    /// qualification via [`Self::qualification_for`].
98    ///
99    /// # Errors
100    /// Refuses if `kind` is not already registered, or if
101    /// [`bootstrap_native_source`] itself refuses.
102    pub(crate) fn admit_bootstrap_native(
103        &mut self,
104        kind: &ProjectionKindRef,
105        owner: OwnerBindingId,
106        code: ContentId,
107        dependencies: ContentId,
108    ) -> Result<(), QualificationError> {
109        let registered = self
110            .providers
111            .get_mut(kind)
112            .ok_or_else(|| QualificationError::Checker(format!("unknown kind {kind:?}")))?;
113        let (checker_owner, authority, receipt) =
114            bootstrap_native_source(owner, code, &registered.identity.code, &dependencies)?;
115        registered.bootstrap = Some(BootstrapAdmission {
116            owner: checker_owner,
117            authority,
118            receipt,
119            dependencies,
120        });
121        Ok(())
122    }
123
124    /// Returns a fresh, independently verified [`ProjectorQualification`] for
125    /// a bootstrap-admitted provider, bound to `policy` and to this exact
126    /// registered provider instance, or `None` if `kind` was never granted
127    /// one via [`Self::admit_bootstrap_native`].
128    ///
129    /// `pub(crate)`, called only from [`ProjectionEngine::project`], at the
130    /// moment of actual dispatch -- never exposed as a detached, replayable
131    /// value a caller could obtain once and present later, to this registry
132    /// or a different one, against a different provider. Re-checks the
133    /// underlying receipt's currentness on every call.
134    ///
135    /// # Errors
136    /// Returns the underlying [`QualificationError`] if the stored receipt
137    /// is no longer current (its owner was dropped, or it was revoked).
138    fn qualification_for(
139        &self,
140        kind: &ProjectionKindRef,
141        policy: &ProjectorPolicy,
142    ) -> Option<Result<ProjectorQualification, QualificationError>> {
143        let registered = self.providers.get(kind)?;
144        let bootstrap = registered.bootstrap.as_ref()?;
145        let evidence = NativeSourceEvidence {
146            code: registered.identity.code.clone(),
147            dependencies: bootstrap.dependencies.clone(),
148            receipt: bootstrap.receipt.clone(),
149        };
150        Some(ProjectorQualificationVerifier::trusted_native(
151            policy,
152            evidence,
153            &bootstrap.authority,
154        ))
155    }
156
157    /// Resolves a loaded provider.
158    #[must_use]
159    pub fn get(
160        &self,
161        kind: &ProjectionKindRef,
162    ) -> Option<(&super::PackageIdentity, &Arc<dyn ProjectionProvider>)> {
163        self.providers
164            .get(kind)
165            .map(|registered| (&registered.identity, &registered.provider))
166    }
167
168    /// Lists loaded kinds in canonical order.
169    pub fn kinds(&self) -> impl ExactSizeIterator<Item = &ProjectionKindRef> {
170        self.providers.keys()
171    }
172}
173
174/// Pure projection coordinator over a sealed world and federated closure.
175pub struct ProjectionEngine<'a> {
176    registry: &'a ProjectionRegistry,
177    shapes: &'a dyn ConfigShapeVerifier,
178    closure: &'a FederatedClosure,
179}
180
181impl<'a> ProjectionEngine<'a> {
182    /// Binds the loaded registry, Shape verifier, and sealed closure.
183    #[must_use]
184    pub fn new(
185        registry: &'a ProjectionRegistry,
186        shapes: &'a dyn ConfigShapeVerifier,
187        closure: &'a FederatedClosure,
188    ) -> Self {
189        Self {
190            registry,
191            shapes,
192            closure,
193        }
194    }
195
196    /// Runs one qualified projection without acquiring observation or effects.
197    ///
198    /// Qualification is not a caller-suppliable parameter: it is fetched
199    /// from this engine's own bound registry, for this exact registered
200    /// provider instance, at the moment of dispatch. A caller cannot obtain
201    /// a qualification once and replay it later, against this registry
202    /// after the provider changed, or against a different registry
203    /// entirely -- there is no value of that shape for it to hold or pass.
204    ///
205    /// # Errors
206    /// Returns [`ProjectionError::UnqualifiedProjector`] if `spec.kind` was
207    /// never granted bootstrap admission, or if the underlying receipt is no
208    /// longer current.
209    pub fn project(
210        &self,
211        world: &super::ObservedWorld,
212        spec: &ProjectionSpec,
213        policy: &ProjectorPolicy,
214        confinement: Option<ConfinementEvidence>,
215    ) -> Result<ProjectionResult, ProjectionError> {
216        let (loaded_identity, provider) = self
217            .registry
218            .get(&spec.kind)
219            .ok_or_else(|| ProjectionError::UnknownProvider(spec.kind.clone()))?;
220        if loaded_identity != &spec.provider {
221            return Err(ProjectionError::CodeIdentityMismatch);
222        }
223        if provider.config_shape() != &spec.config_shape {
224            return Err(ProjectionError::ConfigShapeMismatch);
225        }
226        self.shapes
227            .verify(&spec.config_shape, &spec.config)
228            .map_err(ProjectionError::InvalidConfig)?;
229        let qualification = self
230            .registry
231            .qualification_for(&spec.kind, policy)
232            .ok_or_else(|| {
233                ProjectionError::UnqualifiedProjector(
234                    "this provider was never granted admission".to_owned(),
235                )
236            })?
237            .map_err(|error| ProjectionError::UnqualifiedProjector(error.to_string()))?;
238        if qualification.implementation() != &spec.provider.code {
239            return Err(ProjectionError::CodeIdentityMismatch);
240        }
241        if policy.requires_confinement {
242            let evidence = confinement.as_ref().ok_or_else(|| {
243                ProjectionError::UnavailableConfinement("required membrane absent".to_owned())
244            })?;
245            if !evidence.live {
246                return Err(ProjectionError::UnavailableConfinement(
247                    "required membrane unavailable on selected host".to_owned(),
248                ));
249            }
250        }
251        if policy.reads.facts().len() > policy.budgets.max_inputs {
252            return Err(ProjectionError::BudgetExceeded("selected inputs"));
253        }
254        let inputs = world.select(&policy.reads)?;
255        let output = provider.project(&inputs, &spec.config)?;
256        let accessed = inputs.accessed();
257        for fact in &accessed {
258            if !output.dependencies.contains(fact) {
259                return Err(ProjectionError::UndeclaredAccess {
260                    accessed: fact.clone(),
261                });
262            }
263        }
264        for fact in &output.dependencies {
265            if !accessed.contains(fact) {
266                return Err(ProjectionError::UnreadDependency(fact.clone()));
267            }
268        }
269        let output_bytes = output
270            .value
271            .canonical_bytes()
272            .map_err(|error| ProjectionError::Canonical(error.to_string()))?;
273        if output_bytes.len() > policy.budgets.max_output_bytes {
274            return Err(ProjectionError::BudgetExceeded("projection output"));
275        }
276        let digest = projection_digest(
277            spec,
278            &qualification,
279            &inputs,
280            &output.dependencies,
281            &output.value,
282        )?;
283        let affected = self.closure.affected(output.dependencies.iter().cloned());
284        let mut explanations = Vec::new();
285        for conclusion in &affected {
286            for fact in &output.dependencies {
287                if let Ok(explanation) = self.closure.explain(conclusion, fact) {
288                    explanations.push(explanation);
289                }
290            }
291        }
292        Ok(ProjectionResult {
293            projection: output.value,
294            mediated_access: MediatedAccessWitness {
295                selected: policy.reads.facts().cloned().collect(),
296                accessed,
297            },
298            projector_qualification: qualification,
299            confinement,
300            digest,
301            affected,
302            explanations,
303        })
304    }
305}
306
307fn projection_digest(
308    spec: &ProjectionSpec,
309    qualification: &ProjectorQualification,
310    inputs: &super::ProjectionInputs,
311    dependencies: &BTreeSet<super::FactId>,
312    output: &Datum,
313) -> Result<ProjectionDigest, ProjectionError> {
314    let facts = dependencies
315        .iter()
316        .map(|id| {
317            let value = inputs
318                .get(id)
319                .expect("provider dependency was validated as an accessed selected fact");
320            Datum::Node {
321                tag: Symbol::qualified("projection", "semantic-input-v1"),
322                fields: vec![
323                    (Symbol::new("id"), Datum::String(id.as_str().to_owned())),
324                    (Symbol::new("value"), value.clone()),
325                ],
326            }
327        })
328        .collect();
329    let preimage = Datum::Node {
330        tag: Symbol::qualified("projection", "semantic-projection-digest-v1"),
331        fields: vec![
332            (
333                Symbol::new("kind"),
334                Datum::String(spec.kind.as_str().to_owned()),
335            ),
336            (
337                Symbol::new("package"),
338                Datum::String(spec.provider.name.clone()),
339            ),
340            (
341                Symbol::new("version"),
342                Datum::String(spec.provider.version.clone()),
343            ),
344            (
345                Symbol::new("implementation"),
346                content_id_datum(qualification.implementation()),
347            ),
348            (
349                Symbol::new("policy"),
350                content_id_datum(qualification.policy()),
351            ),
352            (Symbol::new("config"), spec.config.clone()),
353            (Symbol::new("inputs"), Datum::Vector(facts)),
354            (Symbol::new("projection"), output.clone()),
355        ],
356    };
357    preimage
358        .content_id()
359        .map(ProjectionDigest)
360        .map_err(|error| ProjectionError::Canonical(error.to_string()))
361}