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 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 #[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#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct PackageIdentity {
54 pub name: String,
56 pub version: String,
58 pub code: ContentId,
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct ObservedFact {
68 pub semantic: Datum,
70 pub envelope: Option<Datum>,
72}
73
74#[derive(Clone, Debug, Default, Eq, PartialEq)]
76pub struct ObservedWorld {
77 facts: BTreeMap<FactId, ObservedFact>,
78}
79
80impl ObservedWorld {
81 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#[derive(Clone, Debug, Default, Eq, PartialEq)]
115pub struct DeclaredInputSelector {
116 facts: BTreeSet<FactId>,
117}
118
119impl DeclaredInputSelector {
120 #[must_use]
122 pub fn new(facts: impl IntoIterator<Item = FactId>) -> Self {
123 Self {
124 facts: facts.into_iter().collect(),
125 }
126 }
127
128 pub fn facts(&self) -> impl ExactSizeIterator<Item = &FactId> {
130 self.facts.iter()
131 }
132}
133
134#[derive(Debug)]
139pub struct ProjectionInputs {
140 facts: BTreeMap<FactId, Datum>,
141 accessed: Mutex<BTreeSet<FactId>>,
142}
143
144impl ProjectionInputs {
145 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 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#[derive(Clone, Debug, Eq, PartialEq)]
174pub struct ProjectionOutput {
175 pub value: Datum,
177 pub dependencies: BTreeSet<FactId>,
179}
180
181pub trait ProjectionProvider: Send + Sync {
183 fn kind(&self) -> &ProjectionKindRef;
185 fn config_shape(&self) -> &ContentId;
187 fn project(
189 &self,
190 inputs: &ProjectionInputs,
191 config: &Datum,
192 ) -> Result<ProjectionOutput, ProjectionError>;
193}
194
195#[derive(Clone, Debug, Eq, PartialEq)]
197pub struct ProjectionSpec {
198 pub id: ContentId,
200 pub kind: ProjectionKindRef,
202 pub config: Datum,
204 pub config_shape: ContentId,
206 pub provider: PackageIdentity,
208}
209
210#[derive(Clone, Copy, Debug, Eq, PartialEq)]
212pub struct ProjectionBudget {
213 pub max_inputs: usize,
215 pub max_output_bytes: usize,
217 pub max_fuel: u64,
219 pub max_memory_bytes: usize,
221}
222
223#[derive(Clone, Debug, Default, Eq, PartialEq)]
225pub struct DeterministicImportManifest {
226 pub imports: BTreeSet<String>,
228}
229
230#[derive(Clone, Debug, Eq, PartialEq)]
232pub struct ExecutionSemantics {
233 pub id: String,
235 pub canonical_nan: bool,
237 pub canonical_collections: bool,
239 pub fresh_instance: bool,
241}
242
243#[derive(Clone, Debug, Eq, PartialEq)]
245pub struct ProjectorPolicy {
246 pub input_shape: ContentId,
248 pub reads: DeclaredInputSelector,
250 pub imports: DeterministicImportManifest,
252 pub execution: ExecutionSemantics,
254 pub budgets: ProjectionBudget,
256 pub requires_confinement: bool,
258}
259
260#[derive(Clone, Debug, Eq, PartialEq)]
262pub struct QualifiedSourceClosure {
263 pub code: ContentId,
265 pub dependencies: ContentId,
267 pub review: ContentId,
269}
270
271#[derive(Clone, Debug, Eq, PartialEq)]
273pub struct QualifiedRuntime {
274 pub code: ContentId,
276 pub semantics: ExecutionSemantics,
278}
279
280#[derive(Clone, Debug, Eq, PartialEq)]
282pub enum ProjectorQualification {
283 TrustedNative {
285 source: QualifiedSourceClosure,
287 policy: ContentId,
289 },
290 ClosedWasm {
292 module: ContentId,
294 policy: ContentId,
296 runtime: QualifiedRuntime,
298 imports: DeterministicImportManifest,
300 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#[derive(Clone, Debug, Eq, PartialEq)]
322pub struct ConfinementEvidence {
323 pub membrane: String,
325 pub policy: ContentId,
327 pub live: bool,
329}
330
331#[derive(Clone, Debug, Eq, PartialEq)]
333pub struct MediatedAccessWitness {
334 pub selected: BTreeSet<FactId>,
336 pub accessed: BTreeSet<FactId>,
338}
339
340#[derive(Clone, Debug, Eq, PartialEq)]
342pub struct Explanation {
343 pub conclusion: ConclusionId,
345 pub fact: FactId,
347 pub path: Vec<String>,
349}
350
351#[derive(Clone, Debug, Eq, PartialEq)]
353pub struct ProjectionDigest(pub ContentId);
354
355#[derive(Clone, Debug, Eq, PartialEq)]
357pub struct ProjectionResult {
358 pub projection: Datum,
360 pub mediated_access: MediatedAccessWitness,
362 pub projector_qualification: ProjectorQualification,
364 pub confinement: Option<ConfinementEvidence>,
366 pub digest: ProjectionDigest,
368 pub affected: Vec<ConclusionId>,
370 pub explanations: Vec<Explanation>,
372}
373
374#[derive(Clone, Debug, Eq, PartialEq)]
376pub enum ProjectionError {
377 InvalidIdentifier(&'static str),
379 DuplicateFact(FactId),
381 MissingFact(FactId),
383 UnknownProvider(ProjectionKindRef),
385 DuplicateProvider(ProjectionKindRef),
387 ConfigShapeMismatch,
389 InvalidConfig(String),
391 InvalidPathSelection(String),
393 CodeIdentityMismatch,
395 UnqualifiedProjector(String),
397 UnavailableConfinement(String),
399 UndeclaredAccess {
401 accessed: FactId,
403 },
404 UnreadDependency(FactId),
406 BudgetExceeded(&'static str),
408 Canonical(String),
410 MissingExplanation {
412 conclusion: ConclusionId,
414 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 {}