use crate::contract::budget::BudgetProfile;
use crate::contract::capability::{Capability, SupportVerdict};
use crate::contract::host_control::HostControl;
use crate::contract::ids::BackendId;
use crate::contract::plan::BoundaryRequirement;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SupportMatrix {
best_case: BTreeMap<RequirementKind, SupportVerdict>,
}
impl SupportMatrix {
#[must_use]
pub fn from_best_case(best_case: BTreeMap<RequirementKind, SupportVerdict>) -> Self {
Self { best_case }
}
#[must_use]
pub fn best_case_for(&self, kind: RequirementKind) -> SupportVerdict {
self.best_case
.get(&kind)
.cloned()
.unwrap_or_else(SupportVerdict::unsupported)
}
#[must_use]
pub fn declared_kinds(&self) -> Vec<RequirementKind> {
self.best_case.keys().copied().collect()
}
#[must_use]
pub fn declares(&self, kind: RequirementKind) -> bool {
self.best_case.contains_key(&kind)
}
#[must_use]
pub fn classify(&self, req: &BoundaryRequirement, profile: &BackendProfile) -> SupportVerdict {
let kind = RequirementKind::of(req);
let best = self
.best_case
.get(&kind)
.cloned()
.unwrap_or_else(SupportVerdict::unsupported);
best.meet(&profile.ceiling_for(kind))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum RequirementKind {
Filesystem,
NetworkDenyAll,
NetworkAllowList,
ChildSpawnDenyNewTasks,
ChildSpawnAllowThreads,
ChildSpawnAllowDescendants,
Environment,
InheritedFdsNone,
InheritedFdsOnly,
LaunchWorkload,
CaptureStreams,
TempRoot,
ExposePath,
CommitArtifact,
DiscardArtifact,
Kill,
ListOutputs,
}
impl RequirementKind {
pub const ALL: [Self; 17] = [
Self::Filesystem,
Self::NetworkDenyAll,
Self::NetworkAllowList,
Self::ChildSpawnDenyNewTasks,
Self::ChildSpawnAllowThreads,
Self::ChildSpawnAllowDescendants,
Self::Environment,
Self::InheritedFdsNone,
Self::InheritedFdsOnly,
Self::LaunchWorkload,
Self::CaptureStreams,
Self::TempRoot,
Self::ExposePath,
Self::CommitArtifact,
Self::DiscardArtifact,
Self::Kill,
Self::ListOutputs,
];
#[must_use]
pub fn of(req: &BoundaryRequirement) -> Self {
match req {
BoundaryRequirement::Capability(cap) => Self::of_capability(cap),
BoundaryRequirement::HostControl(ctrl) => Self::of_control(ctrl),
}
}
fn of_capability(cap: &Capability) -> Self {
use crate::contract::capability::{FdPolicy, NetPolicy, SpawnPolicy};
match cap {
Capability::Filesystem { .. } => Self::Filesystem,
Capability::Network {
policy: NetPolicy::DenyAll,
} => Self::NetworkDenyAll,
Capability::Network {
policy: NetPolicy::AllowList(_),
} => Self::NetworkAllowList,
Capability::ChildSpawn {
policy: SpawnPolicy::DenyNewTasks,
} => Self::ChildSpawnDenyNewTasks,
Capability::ChildSpawn {
policy: SpawnPolicy::AllowThreadsWithinBoundary,
} => Self::ChildSpawnAllowThreads,
Capability::ChildSpawn {
policy: SpawnPolicy::AllowDescendantsWithinBoundary,
} => Self::ChildSpawnAllowDescendants,
Capability::Environment { .. } => Self::Environment,
Capability::InheritedFds {
policy: FdPolicy::None,
} => Self::InheritedFdsNone,
Capability::InheritedFds {
policy: FdPolicy::Only(_),
} => Self::InheritedFdsOnly,
}
}
#[cfg(test)]
pub(crate) fn of_capability_for_test(cap: &Capability) -> Self {
Self::of_capability(cap)
}
fn of_control(ctrl: &HostControl) -> Self {
match ctrl {
HostControl::LaunchWorkload => Self::LaunchWorkload,
HostControl::CaptureStreams { .. } => Self::CaptureStreams,
HostControl::TempRoot { .. } => Self::TempRoot,
HostControl::ExposePath { .. } => Self::ExposePath,
HostControl::CommitArtifact { .. } => Self::CommitArtifact,
HostControl::DiscardArtifact => Self::DiscardArtifact,
HostControl::Kill { .. } => Self::Kill,
HostControl::ListOutputs => Self::ListOutputs,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackendProfileSnapshot {
pub backend: BackendId,
pub probed: BTreeMap<String, String>,
pub budget: BudgetProfile,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BackendProfile {
ceiling: BTreeMap<RequirementKind, SupportVerdict>,
}
impl BackendProfile {
#[must_use]
pub fn from_ceiling(ceiling: BTreeMap<RequirementKind, SupportVerdict>) -> Self {
Self { ceiling }
}
#[must_use]
pub fn ceiling_for(&self, kind: RequirementKind) -> SupportVerdict {
self.ceiling
.get(&kind)
.cloned()
.unwrap_or_else(SupportVerdict::unsupported)
}
#[must_use]
pub fn enforced_kinds(&self) -> Vec<RequirementKind> {
RequirementKind::ALL
.into_iter()
.filter(|&k| {
self.ceiling_for(k).enforcement
== crate::contract::capability::Enforcement::Enforced
})
.collect()
}
}
#[cfg(test)]
mod requirement_kind_tests {
use super::RequirementKind;
#[test]
fn all_is_exhaustive() {
for kind in RequirementKind::ALL {
match kind {
RequirementKind::Filesystem
| RequirementKind::NetworkDenyAll
| RequirementKind::NetworkAllowList
| RequirementKind::ChildSpawnDenyNewTasks
| RequirementKind::ChildSpawnAllowThreads
| RequirementKind::ChildSpawnAllowDescendants
| RequirementKind::Environment
| RequirementKind::InheritedFdsNone
| RequirementKind::InheritedFdsOnly
| RequirementKind::LaunchWorkload
| RequirementKind::CaptureStreams
| RequirementKind::TempRoot
| RequirementKind::ExposePath
| RequirementKind::CommitArtifact
| RequirementKind::DiscardArtifact
| RequirementKind::Kill
| RequirementKind::ListOutputs => {}
}
}
let mut seen = std::collections::BTreeSet::new();
for kind in RequirementKind::ALL {
assert!(seen.insert(kind), "duplicate kind in ALL: {kind:?}");
}
assert_eq!(seen.len(), RequirementKind::ALL.len());
}
}
#[cfg(test)]
#[path = "support_injective_tests.rs"]
mod support_injective_tests;