use std::{
collections::{BTreeMap, BTreeSet},
error::Error,
fmt,
sync::Mutex,
};
use sim_kernel::{ContentId, Datum};
macro_rules! string_id {
($name:ident, $doc:literal) => {
#[doc = $doc]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> Result<Self, ProjectionError> {
let value = value.into();
if value.trim().is_empty() {
return Err(ProjectionError::InvalidIdentifier(stringify!($name)));
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
};
}
string_id!(FactId, "Stable identity of one semantic observed fact.");
string_id!(
ConclusionId,
"Stable identity of a conclusion consuming facts."
);
string_id!(
ProjectionKindRef,
"Open identifier of a loaded projection kind."
);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PackageIdentity {
pub name: String,
pub version: String,
pub code: ContentId,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ObservedFact {
pub semantic: Datum,
pub envelope: Option<Datum>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ObservedWorld {
facts: BTreeMap<FactId, ObservedFact>,
}
impl ObservedWorld {
pub fn new(
facts: impl IntoIterator<Item = (FactId, ObservedFact)>,
) -> Result<Self, ProjectionError> {
let mut world = Self::default();
for (id, fact) in facts {
if world.facts.insert(id.clone(), fact).is_some() {
return Err(ProjectionError::DuplicateFact(id));
}
}
Ok(world)
}
pub(crate) fn select(
&self,
selector: &DeclaredInputSelector,
) -> Result<ProjectionInputs, ProjectionError> {
let mut selected = BTreeMap::new();
for id in &selector.facts {
let fact = self
.facts
.get(id)
.ok_or_else(|| ProjectionError::MissingFact(id.clone()))?;
selected.insert(id.clone(), fact.semantic.clone());
}
Ok(ProjectionInputs {
facts: selected,
accessed: Mutex::new(BTreeSet::new()),
})
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DeclaredInputSelector {
facts: BTreeSet<FactId>,
}
impl DeclaredInputSelector {
#[must_use]
pub fn new(facts: impl IntoIterator<Item = FactId>) -> Self {
Self {
facts: facts.into_iter().collect(),
}
}
pub fn facts(&self) -> impl ExactSizeIterator<Item = &FactId> {
self.facts.iter()
}
}
#[derive(Debug)]
pub struct ProjectionInputs {
facts: BTreeMap<FactId, Datum>,
accessed: Mutex<BTreeSet<FactId>>,
}
impl ProjectionInputs {
pub fn get(&self, id: &FactId) -> Option<&Datum> {
let value = self.facts.get(id)?;
self.accessed
.lock()
.expect("projection access mutex poisoned")
.insert(id.clone());
Some(value)
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&FactId, &Datum)> {
self.accessed
.lock()
.expect("projection access mutex poisoned")
.extend(self.facts.keys().cloned());
self.facts.iter()
}
pub(crate) fn accessed(&self) -> BTreeSet<FactId> {
self.accessed
.lock()
.expect("projection access mutex poisoned")
.clone()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProjectionOutput {
pub value: Datum,
pub dependencies: BTreeSet<FactId>,
}
pub trait ProjectionProvider: Send + Sync {
fn kind(&self) -> &ProjectionKindRef;
fn config_shape(&self) -> &ContentId;
fn project(
&self,
inputs: &ProjectionInputs,
config: &Datum,
) -> Result<ProjectionOutput, ProjectionError>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProjectionSpec {
pub id: ContentId,
pub kind: ProjectionKindRef,
pub config: Datum,
pub config_shape: ContentId,
pub provider: PackageIdentity,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProjectionBudget {
pub max_inputs: usize,
pub max_output_bytes: usize,
pub max_fuel: u64,
pub max_memory_bytes: usize,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DeterministicImportManifest {
pub imports: BTreeSet<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExecutionSemantics {
pub id: String,
pub canonical_nan: bool,
pub canonical_collections: bool,
pub fresh_instance: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProjectorPolicy {
pub input_shape: ContentId,
pub reads: DeclaredInputSelector,
pub imports: DeterministicImportManifest,
pub execution: ExecutionSemantics,
pub budgets: ProjectionBudget,
pub requires_confinement: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct QualifiedSourceClosure {
pub code: ContentId,
pub dependencies: ContentId,
pub review: ContentId,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct QualifiedRuntime {
pub code: ContentId,
pub semantics: ExecutionSemantics,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProjectorQualification {
TrustedNative {
source: QualifiedSourceClosure,
policy: ContentId,
},
ClosedWasm {
module: ContentId,
policy: ContentId,
runtime: QualifiedRuntime,
imports: DeterministicImportManifest,
admission: ContentId,
},
}
impl ProjectorQualification {
pub(crate) fn implementation(&self) -> &ContentId {
match self {
Self::TrustedNative { source, .. } => &source.code,
Self::ClosedWasm { module, .. } => module,
}
}
pub(crate) fn policy(&self) -> &ContentId {
match self {
Self::TrustedNative { policy, .. } | Self::ClosedWasm { policy, .. } => policy,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfinementEvidence {
pub membrane: String,
pub policy: ContentId,
pub live: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MediatedAccessWitness {
pub selected: BTreeSet<FactId>,
pub accessed: BTreeSet<FactId>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Explanation {
pub conclusion: ConclusionId,
pub fact: FactId,
pub path: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProjectionDigest(pub ContentId);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProjectionResult {
pub projection: Datum,
pub mediated_access: MediatedAccessWitness,
pub projector_qualification: ProjectorQualification,
pub confinement: Option<ConfinementEvidence>,
pub digest: ProjectionDigest,
pub affected: Vec<ConclusionId>,
pub explanations: Vec<Explanation>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProjectionError {
InvalidIdentifier(&'static str),
DuplicateFact(FactId),
MissingFact(FactId),
UnknownProvider(ProjectionKindRef),
DuplicateProvider(ProjectionKindRef),
ConfigShapeMismatch,
InvalidConfig(String),
InvalidPathSelection(String),
CodeIdentityMismatch,
UnqualifiedProjector(String),
UnavailableConfinement(String),
UndeclaredAccess {
accessed: FactId,
},
UnreadDependency(FactId),
BudgetExceeded(&'static str),
Canonical(String),
MissingExplanation {
conclusion: ConclusionId,
fact: FactId,
},
}
impl fmt::Display for ProjectionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{self:?}")
}
}
impl Error for ProjectionError {}