sim_incremental_core/projection/
engine.rs1use 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
21pub trait ConfigShapeVerifier {
23 fn verify(&self, shape: &ContentId, config: &Datum) -> Result<(), String>;
25}
26
27#[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
39struct BootstrapAdmission {
46 #[allow(dead_code)] owner: LiveCheckerOwner,
48 authority: LiveCheckerAuthority,
49 receipt: LiveCheckerReceipt,
50 dependencies: ContentId,
51}
52
53impl ProjectionRegistry {
54 #[must_use]
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 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 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, ®istered.identity.code, &dependencies)?;
115 registered.bootstrap = Some(BootstrapAdmission {
116 owner: checker_owner,
117 authority,
118 receipt,
119 dependencies,
120 });
121 Ok(())
122 }
123
124 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 #[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| (®istered.identity, ®istered.provider))
166 }
167
168 pub fn kinds(&self) -> impl ExactSizeIterator<Item = &ProjectionKindRef> {
170 self.providers.keys()
171 }
172}
173
174pub struct ProjectionEngine<'a> {
176 registry: &'a ProjectionRegistry,
177 shapes: &'a dyn ConfigShapeVerifier,
178 closure: &'a FederatedClosure,
179}
180
181impl<'a> ProjectionEngine<'a> {
182 #[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 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}