pub trait FacetTerm: Copy + Eq + Sized + 'static {
fn all() -> &'static [Self];
fn as_str(self) -> &'static str;
fn from_term(s: &str) -> Option<Self>;
fn hazard() -> Self;
}
macro_rules! ordinal_term {
(
$(#[$meta:meta])*
$name:ident { $first:ident => $fs:literal $(, $rest:ident => $rs:literal)* $(,)? }
$(hazard = $hz:ident;)?
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum $name {
#[default]
$first,
$($rest),*
}
impl FacetTerm for $name {
fn all() -> &'static [Self] { &[Self::$first $(, Self::$rest)*] }
fn as_str(self) -> &'static str {
match self { Self::$first => $fs $(, Self::$rest => $rs)* }
}
fn from_term(s: &str) -> Option<Self> {
match s { $fs => Some(Self::$first), $($rs => Some(Self::$rest),)* _ => None }
}
fn hazard() -> Self {
$( return Self::$hz; )?
#[allow(unreachable_code)]
{ *Self::all().last().expect("a facet has at least one term") }
}
}
};
}
macro_rules! categorical_term {
(
$(#[$meta:meta])*
$name:ident { $first:ident => $fs:literal $(, $rest:ident => $rs:literal)* $(,)? }
hazard = $hz:ident;
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub enum $name {
#[default]
$first,
$($rest),*
}
impl FacetTerm for $name {
fn all() -> &'static [Self] { &[Self::$first $(, Self::$rest)*] }
fn as_str(self) -> &'static str {
match self { Self::$first => $fs $(, Self::$rest => $rs)* }
}
fn from_term(s: &str) -> Option<Self> {
match s { $fs => Some(Self::$first), $($rs => Some(Self::$rest),)* _ => None }
}
fn hazard() -> Self { Self::$hz }
}
};
}
categorical_term! {
Operation {
Observe => "observe",
Create => "create",
Mutate => "mutate",
Destroy => "destroy",
Execute => "execute",
Communicate => "communicate",
Configure => "configure", Authorize => "authorize", Control => "control", }
hazard = Communicate;
}
ordinal_term! {
LocalLocus {
Process => "process",
Temp => "temp",
SandboxScope => "sandbox-scope",
Worktree => "worktree",
Adjacent => "adjacent", WorktreeTrusted => "worktree-trusted", User => "user", Machine => "machine", SystemIntegrity => "system-integrity", Device => "device", Kernel => "kernel", }
}
ordinal_term! {
RemoteReach {
None => "none",
Fixed => "fixed",
Arbitrary => "arbitrary",
}
}
categorical_term! {
RemoteBinding {
Na => "n/a", Pinned => "pinned", Ambient => "ambient",
}
hazard = Ambient;
}
ordinal_term! {
Provenance {
Na => "n/a", Established => "established", Literal => "literal", Opaque => "opaque", }
}
ordinal_term! {
Scale {
Single => "single",
Bounded => "bounded", Unbounded => "unbounded", }
}
ordinal_term! {
RetrievalGranularity {
Metadata => "metadata", Record => "record", BulkContent => "bulk-content", }
}
ordinal_term! {
Authority {
User => "user",
Elevated => "elevated", Root => "root",
OtherUser => "other-user", }
}
ordinal_term! {
Isolation {
None => "none",
View => "view", Namespace => "namespace",
Userns => "userns",
Vm => "vm",
Ocap => "ocap",
}
hazard = None;
}
ordinal_term! {
Reversibility {
None => "none", Trivial => "trivial", Recoverable => "recoverable", Effortful => "effortful", Irreversible => "irreversible",
}
}
ordinal_term! {
PersistenceLevel {
Transient => "transient",
Data => "data",
Reconfiguring => "reconfiguring", Installing => "installing", }
}
ordinal_term! {
TriggerEscape {
Immediate => "immediate", Detached => "detached", Recurring => "recurring", Boot => "boot", }
}
categorical_term! {
TriggerKind {
None => "none", Clock => "clock", Event => "event", }
hazard = Clock;
}
ordinal_term! {
DisclosureAudience {
None => "none",
LocalProcess => "local-process", LocalPersistent => "local-persistent", TrustedRemote => "trusted-remote",
SharedRemote => "shared-remote",
Public => "public",
}
}
ordinal_term! {
SecretLevel {
None => "none",
UsesAmbient => "uses-ambient",
Reads => "reads",
Writes => "writes",
Transmits => "transmits",
}
}
categorical_term! {
Channel {
None => "none",
Filesystem => "filesystem",
StdoutToModel => "stdout-to-model",
Network => "network",
Clipboard => "clipboard", Ipc => "ipc",
CredentialStore => "credential-store", CrossProcess => "cross-process", Unknown => "unknown",
}
hazard = Unknown;
}
categorical_term! {
Principal {
Own => "own",
Cross => "cross",
}
hazard = Cross;
}
ordinal_term! {
NetDirection {
None => "none",
Loopback => "loopback",
Outbound => "outbound",
InboundListen => "inbound-listen",
}
}
ordinal_term! {
NetDestination {
Na => "n/a",
Fixed => "fixed",
Arbitrary => "arbitrary",
}
}
ordinal_term! {
NetPayload {
None => "none",
Fetches => "fetches",
SendsHostData => "sends-host-data",
}
}
ordinal_term! {
ExecutionTrust {
None => "none",
SelfCode => "self",
CallerInline => "caller-inline",
CallerFile => "caller-file",
AmbientConfig => "ambient-config", NetworkSourced => "network-sourced",
}
}
categorical_term! {
SupplySource {
UnverifiedUrl => "unverified-url",
PublicRegistry => "public-registry",
SignedRepo => "signed-repo",
PrivateRegistry => "private-registry",
Vendored => "vendored",
}
hazard = UnverifiedUrl;
}
ordinal_term! {
Pinning {
Floating => "floating",
Version => "version",
HashVerified => "hash-verified",
Digest => "digest",
}
hazard = Floating;
}
categorical_term! {
ExecSurface {
None => "none",
InstallHook => "install-hook", BuildScript => "build-script", CallTime => "call-time", RunArtifact => "run-artifact", }
hazard = InstallHook;
}
ordinal_term! {
Cost {
None => "none",
LocalResource => "local-resource",
Metered => "metered", Quota => "quota",
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Locus {
pub local: LocalLocus,
pub remote: RemoteReach,
pub binding: RemoteBinding,
pub provenance: Provenance,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Trigger {
pub escape: TriggerEscape,
pub kind: TriggerKind,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Persistence {
pub level: PersistenceLevel,
pub trigger: Trigger,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Disclosure {
pub audience: DisclosureAudience,
pub channel: Channel,
pub principal: Principal,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Secret {
pub level: SecretLevel,
pub channel: Channel,
pub principal: Principal,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Network {
pub direction: NetDirection,
pub destination: NetDestination,
pub payload: NetPayload,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct SupplyChain {
pub source: SupplySource,
pub pinning: Pinning,
pub exec_surface: ExecSurface,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Execution {
pub trust: ExecutionTrust,
pub supply_chain: Option<SupplyChain>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Capability {
pub operation: Operation,
pub locus: Locus,
pub scale: Scale,
pub retrieval: RetrievalGranularity,
pub authority: Authority,
pub isolation: Isolation,
pub reversibility: Reversibility,
pub persistence: Persistence,
pub disclosure: Disclosure,
pub secret: Secret,
pub network: Network,
pub execution: Execution,
pub cost: Cost,
pub because: String,
}
impl Capability {
pub fn new(operation: Operation) -> Self {
Self { operation, ..Self::default() }
}
pub fn set_facets(&self) -> Vec<(&'static str, &'static str)> {
let d = Self::default();
let mut out: Vec<(&'static str, &'static str)> = Vec::new();
macro_rules! push {
($name:literal, $field:expr, $dflt:expr) => {
if $field != $dflt {
out.push(($name, $field.as_str()));
}
};
}
out.push(("operation", self.operation.as_str()));
push!("locus.local", self.locus.local, d.locus.local);
push!("locus.remote", self.locus.remote, d.locus.remote);
push!("locus.binding", self.locus.binding, d.locus.binding);
push!("locus.provenance", self.locus.provenance, d.locus.provenance);
push!("scale", self.scale, d.scale);
push!("retrieval", self.retrieval, d.retrieval);
push!("authority", self.authority, d.authority);
push!("isolation", self.isolation, d.isolation);
push!("reversibility", self.reversibility, d.reversibility);
push!("persistence.level", self.persistence.level, d.persistence.level);
push!("persistence.trigger.escape", self.persistence.trigger.escape, d.persistence.trigger.escape);
push!("persistence.trigger.kind", self.persistence.trigger.kind, d.persistence.trigger.kind);
push!("disclosure.audience", self.disclosure.audience, d.disclosure.audience);
push!("disclosure.channel", self.disclosure.channel, d.disclosure.channel);
push!("disclosure.principal", self.disclosure.principal, d.disclosure.principal);
push!("secret.level", self.secret.level, d.secret.level);
push!("secret.channel", self.secret.channel, d.secret.channel);
push!("secret.principal", self.secret.principal, d.secret.principal);
push!("network.direction", self.network.direction, d.network.direction);
push!("network.destination", self.network.destination, d.network.destination);
push!("network.payload", self.network.payload, d.network.payload);
push!("execution.trust", self.execution.trust, d.execution.trust);
push!("cost", self.cost, d.cost);
if let Some(sc) = self.execution.supply_chain {
out.push(("supply_chain.source", sc.source.as_str()));
out.push(("supply_chain.pinning", sc.pinning.as_str()));
out.push(("supply_chain.exec_surface", sc.exec_surface.as_str()));
}
out
}
pub fn worst(because: impl Into<String>) -> Self {
Self {
operation: Operation::hazard(),
locus: Locus {
local: LocalLocus::hazard(),
remote: RemoteReach::hazard(),
binding: RemoteBinding::hazard(),
provenance: Provenance::hazard(),
},
scale: Scale::hazard(),
retrieval: RetrievalGranularity::hazard(),
authority: Authority::hazard(),
isolation: Isolation::hazard(),
reversibility: Reversibility::hazard(),
persistence: Persistence {
level: PersistenceLevel::hazard(),
trigger: Trigger { escape: TriggerEscape::hazard(), kind: TriggerKind::hazard() },
},
disclosure: Disclosure {
audience: DisclosureAudience::hazard(),
channel: Channel::hazard(),
principal: Principal::hazard(),
},
secret: Secret {
level: SecretLevel::hazard(),
channel: Channel::hazard(),
principal: Principal::hazard(),
},
network: Network {
direction: NetDirection::hazard(),
destination: NetDestination::hazard(),
payload: NetPayload::hazard(),
},
execution: Execution {
trust: ExecutionTrust::hazard(),
supply_chain: Some(SupplyChain {
source: SupplySource::hazard(),
pinning: Pinning::hazard(),
exec_surface: ExecSurface::hazard(),
}),
},
cost: Cost::hazard(),
because: because.into(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Profile {
pub capabilities: Vec<Capability>,
}
impl Profile {
pub fn of(capabilities: Vec<Capability>) -> Self {
Self { capabilities }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_term_strings_roundtrip<T: FacetTerm + std::fmt::Debug>() {
for &term in T::all() {
assert_eq!(
T::from_term(term.as_str()),
Some(term),
"term {:?} did not round-trip through {:?}",
term,
term.as_str(),
);
}
for (i, &a) in T::all().iter().enumerate() {
for &b in &T::all()[i + 1..] {
assert_ne!(a.as_str(), b.as_str(), "two variants share a TOML spelling");
}
}
assert_eq!(T::from_term("definitely-not-a-term"), None);
}
fn assert_zero_is_minimum<T: FacetTerm + Ord + Default + std::fmt::Debug>() {
let zero = T::all()[0];
assert_eq!(T::default(), zero, "Default must be the zero term (first variant)");
for &term in T::all() {
assert!(zero <= term, "zero term {zero:?} is not <= {term:?}");
}
}
#[test]
fn every_term_roundtrips_and_is_uniquely_spelled() {
assert_term_strings_roundtrip::<Operation>();
assert_term_strings_roundtrip::<LocalLocus>();
assert_term_strings_roundtrip::<RemoteReach>();
assert_term_strings_roundtrip::<RemoteBinding>();
assert_term_strings_roundtrip::<Provenance>();
assert_term_strings_roundtrip::<Scale>();
assert_term_strings_roundtrip::<RetrievalGranularity>();
assert_term_strings_roundtrip::<Authority>();
assert_term_strings_roundtrip::<Isolation>();
assert_term_strings_roundtrip::<Reversibility>();
assert_term_strings_roundtrip::<PersistenceLevel>();
assert_term_strings_roundtrip::<TriggerEscape>();
assert_term_strings_roundtrip::<TriggerKind>();
assert_term_strings_roundtrip::<DisclosureAudience>();
assert_term_strings_roundtrip::<SecretLevel>();
assert_term_strings_roundtrip::<Channel>();
assert_term_strings_roundtrip::<Principal>();
assert_term_strings_roundtrip::<NetDirection>();
assert_term_strings_roundtrip::<NetDestination>();
assert_term_strings_roundtrip::<NetPayload>();
assert_term_strings_roundtrip::<ExecutionTrust>();
assert_term_strings_roundtrip::<SupplySource>();
assert_term_strings_roundtrip::<Pinning>();
assert_term_strings_roundtrip::<ExecSurface>();
assert_term_strings_roundtrip::<Cost>();
}
#[test]
fn ordinal_zero_terms_are_the_minimum() {
assert_zero_is_minimum::<LocalLocus>();
assert_zero_is_minimum::<RemoteReach>();
assert_zero_is_minimum::<Provenance>();
assert_zero_is_minimum::<Scale>();
assert_zero_is_minimum::<RetrievalGranularity>();
assert_zero_is_minimum::<Authority>();
assert_zero_is_minimum::<Isolation>();
assert_zero_is_minimum::<Reversibility>();
assert_zero_is_minimum::<PersistenceLevel>();
assert_zero_is_minimum::<TriggerEscape>();
assert_zero_is_minimum::<DisclosureAudience>();
assert_zero_is_minimum::<SecretLevel>();
assert_zero_is_minimum::<NetDirection>();
assert_zero_is_minimum::<NetDestination>();
assert_zero_is_minimum::<NetPayload>();
assert_zero_is_minimum::<ExecutionTrust>();
assert_zero_is_minimum::<Pinning>();
assert_zero_is_minimum::<Cost>();
}
#[test]
fn ordinal_ladders_match_the_spec() {
assert!(LocalLocus::Process < LocalLocus::Worktree);
assert!(LocalLocus::Worktree < LocalLocus::Adjacent);
assert!(LocalLocus::Adjacent < LocalLocus::WorktreeTrusted);
assert!(LocalLocus::WorktreeTrusted < LocalLocus::User);
assert!(LocalLocus::Worktree < LocalLocus::Machine);
assert!(LocalLocus::Machine < LocalLocus::SystemIntegrity);
assert!(LocalLocus::SystemIntegrity < LocalLocus::Device);
assert!(LocalLocus::Device < LocalLocus::Kernel);
assert!(Scale::Single < Scale::Bounded && Scale::Bounded < Scale::Unbounded);
assert!(RetrievalGranularity::Metadata < RetrievalGranularity::Record);
assert!(RetrievalGranularity::Record < RetrievalGranularity::BulkContent);
assert!(Authority::User < Authority::Root && Authority::Root < Authority::OtherUser);
assert!(Reversibility::Recoverable < Reversibility::Irreversible);
assert!(PersistenceLevel::Data < PersistenceLevel::Installing);
assert!(TriggerEscape::Immediate < TriggerEscape::Boot);
assert!(DisclosureAudience::LocalProcess < DisclosureAudience::Public);
assert!(Provenance::Na < Provenance::Established);
assert!(Provenance::Established < Provenance::Literal);
assert!(Provenance::Literal < Provenance::Opaque);
assert!(SecretLevel::Reads < SecretLevel::Transmits);
assert!(ExecutionTrust::SelfCode < ExecutionTrust::NetworkSourced);
assert!(Pinning::Floating < Pinning::HashVerified);
}
#[test]
fn capability_new_leaves_all_other_facets_at_zero() {
let cap = Capability::new(Operation::Destroy);
assert_eq!(cap.operation, Operation::Destroy);
assert_eq!(cap.locus, Locus::default());
assert_eq!(cap.locus.local, LocalLocus::Process);
assert_eq!(cap.scale, Scale::Single);
assert_eq!(cap.retrieval, RetrievalGranularity::Metadata);
assert_eq!(cap.authority, Authority::User);
assert_eq!(cap.reversibility, Reversibility::None);
assert_eq!(cap.secret.level, SecretLevel::None);
assert_eq!(cap.disclosure.audience, DisclosureAudience::None);
assert_eq!(cap.network.direction, NetDirection::None);
assert_eq!(cap.execution.trust, ExecutionTrust::None);
assert!(cap.execution.supply_chain.is_none());
assert_eq!(cap.cost, Cost::None);
assert!(cap.because.is_empty());
}
#[test]
fn default_capability_is_a_zero_observe() {
assert_eq!(Capability::default().operation, Operation::Observe);
assert_eq!(Capability::default(), Capability::new(Operation::Observe));
}
}