1pub trait FacetTerm: Copy + Eq + Sized + 'static {
21 fn all() -> &'static [Self];
23 fn as_str(self) -> &'static str;
25 fn from_term(s: &str) -> Option<Self>;
27}
28
29macro_rules! ordinal_term {
30 (
31 $(#[$meta:meta])*
32 $name:ident { $first:ident => $fs:literal $(, $rest:ident => $rs:literal)* $(,)? }
33 ) => {
34 $(#[$meta])*
35 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
36 pub enum $name {
37 #[default]
38 $first,
39 $($rest),*
40 }
41 impl FacetTerm for $name {
42 fn all() -> &'static [Self] { &[Self::$first $(, Self::$rest)*] }
43 fn as_str(self) -> &'static str {
44 match self { Self::$first => $fs $(, Self::$rest => $rs)* }
45 }
46 fn from_term(s: &str) -> Option<Self> {
47 match s { $fs => Some(Self::$first), $($rs => Some(Self::$rest),)* _ => None }
48 }
49 }
50 };
51}
52
53macro_rules! categorical_term {
54 (
55 $(#[$meta:meta])*
56 $name:ident { $first:ident => $fs:literal $(, $rest:ident => $rs:literal)* $(,)? }
57 ) => {
58 $(#[$meta])*
59 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
60 pub enum $name {
61 #[default]
62 $first,
63 $($rest),*
64 }
65 impl FacetTerm for $name {
66 fn all() -> &'static [Self] { &[Self::$first $(, Self::$rest)*] }
67 fn as_str(self) -> &'static str {
68 match self { Self::$first => $fs $(, Self::$rest => $rs)* }
69 }
70 fn from_term(s: &str) -> Option<Self> {
71 match s { $fs => Some(Self::$first), $($rs => Some(Self::$rest),)* _ => None }
72 }
73 }
74 };
75}
76
77categorical_term! {
80 Operation {
82 Observe => "observe",
83 Create => "create",
84 Mutate => "mutate",
85 Destroy => "destroy",
86 Execute => "execute",
87 Communicate => "communicate",
88 Configure => "configure", Authorize => "authorize", Control => "control", }
92}
93
94ordinal_term! {
97 LocalLocus {
100 Process => "process",
101 Temp => "temp",
102 SandboxScope => "sandbox-scope",
103 Worktree => "worktree",
104 Adjacent => "adjacent", WorktreeTrusted => "worktree-trusted", User => "user", Machine => "machine", SystemIntegrity => "system-integrity", Device => "device", Kernel => "kernel", }
117}
118
119ordinal_term! {
120 RemoteReach {
122 None => "none",
123 Fixed => "fixed",
124 Arbitrary => "arbitrary",
125 }
126}
127
128categorical_term! {
129 RemoteBinding {
132 Na => "n/a", Pinned => "pinned", Ambient => "ambient",
135 }
136}
137
138ordinal_term! {
139 Provenance {
146 Na => "n/a", Established => "established", Literal => "literal", Opaque => "opaque", }
154}
155
156ordinal_term! {
157 Scale {
159 Single => "single",
160 Bounded => "bounded", Unbounded => "unbounded", }
163}
164
165ordinal_term! {
166 RetrievalGranularity {
176 Metadata => "metadata", Record => "record", BulkContent => "bulk-content", }
180}
181
182ordinal_term! {
183 Authority {
185 User => "user",
186 Elevated => "elevated", Root => "root",
188 OtherUser => "other-user", }
190}
191
192ordinal_term! {
193 Isolation {
196 None => "none",
197 View => "view", Namespace => "namespace",
199 Userns => "userns",
200 Vm => "vm",
201 Ocap => "ocap",
202 }
203}
204
205ordinal_term! {
208 Reversibility {
211 None => "none", Trivial => "trivial", Recoverable => "recoverable",Effortful => "effortful", Irreversible => "irreversible",
216 }
217}
218
219ordinal_term! {
220 PersistenceLevel {
222 Transient => "transient",
223 Data => "data",
224 Reconfiguring => "reconfiguring", Installing => "installing", }
227}
228
229ordinal_term! {
230 TriggerEscape {
232 Immediate => "immediate", Detached => "detached", Recurring => "recurring", Boot => "boot", }
237}
238
239categorical_term! {
240 TriggerKind {
243 None => "none", Clock => "clock", Event => "event", }
247}
248
249ordinal_term! {
252 DisclosureAudience {
255 None => "none",
256 LocalProcess => "local-process", LocalPersistent => "local-persistent", TrustedRemote => "trusted-remote",
259 SharedRemote => "shared-remote",
260 Public => "public",
261 }
262}
263
264ordinal_term! {
265 SecretLevel {
267 None => "none",
268 UsesAmbient => "uses-ambient",
269 Reads => "reads",
270 Writes => "writes",
271 Transmits => "transmits",
272 }
273}
274
275categorical_term! {
276 Channel {
279 None => "none",
280 Filesystem => "filesystem",
281 StdoutToModel => "stdout-to-model",
282 Network => "network",
283 Clipboard => "clipboard", Ipc => "ipc",
285 CredentialStore => "credential-store", CrossProcess => "cross-process", Unknown => "unknown",
288 }
289}
290
291categorical_term! {
292 Principal {
295 Own => "own",
296 Cross => "cross",
297 }
298}
299
300ordinal_term! {
303 NetDirection {
305 None => "none",
306 Loopback => "loopback",
307 Outbound => "outbound",
308 InboundListen => "inbound-listen",
309 }
310}
311
312ordinal_term! {
313 NetDestination {
315 Na => "n/a",
316 Fixed => "fixed",
317 Arbitrary => "arbitrary",
318 }
319}
320
321ordinal_term! {
322 NetPayload {
324 None => "none",
325 Fetches => "fetches",
326 SendsHostData => "sends-host-data",
327 }
328}
329
330ordinal_term! {
333 ExecutionTrust {
336 None => "none",
337 SelfCode => "self",
338 CallerInline => "caller-inline",
339 CallerFile => "caller-file",
340 AmbientConfig => "ambient-config", NetworkSourced => "network-sourced",
342 }
343}
344
345categorical_term! {
346 SupplySource {
349 UnverifiedUrl => "unverified-url",
350 PublicRegistry => "public-registry",
351 SignedRepo => "signed-repo",
352 PrivateRegistry => "private-registry",
353 Vendored => "vendored",
354 }
355}
356
357ordinal_term! {
358 Pinning {
361 Floating => "floating",
362 Version => "version",
363 HashVerified => "hash-verified",
364 Digest => "digest",
365 }
366}
367
368categorical_term! {
369 ExecSurface {
373 None => "none",
374 InstallHook => "install-hook", BuildScript => "build-script", CallTime => "call-time", RunArtifact => "run-artifact", }
379}
380
381ordinal_term! {
384 Cost {
386 None => "none",
387 LocalResource => "local-resource",
388 Metered => "metered", Quota => "quota",
390 }
391}
392
393#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
397pub struct Locus {
398 pub local: LocalLocus,
399 pub remote: RemoteReach,
400 pub binding: RemoteBinding,
401 pub provenance: Provenance,
402}
403
404#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
406pub struct Trigger {
407 pub escape: TriggerEscape,
408 pub kind: TriggerKind,
409}
410
411#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
413pub struct Persistence {
414 pub level: PersistenceLevel,
415 pub trigger: Trigger,
416}
417
418#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
420pub struct Disclosure {
421 pub audience: DisclosureAudience,
422 pub channel: Channel,
423 pub principal: Principal,
424}
425
426#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
428pub struct Secret {
429 pub level: SecretLevel,
430 pub channel: Channel,
431 pub principal: Principal,
432}
433
434#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
436pub struct Network {
437 pub direction: NetDirection,
438 pub destination: NetDestination,
439 pub payload: NetPayload,
440}
441
442#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
444pub struct SupplyChain {
445 pub source: SupplySource,
446 pub pinning: Pinning,
447 pub exec_surface: ExecSurface,
448}
449
450#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
455pub struct Execution {
456 pub trust: ExecutionTrust,
457 pub supply_chain: Option<SupplyChain>,
458}
459
460#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
465pub struct Capability {
466 pub operation: Operation,
467 pub locus: Locus,
468 pub scale: Scale,
469 pub retrieval: RetrievalGranularity,
470 pub authority: Authority,
471 pub isolation: Isolation,
472 pub reversibility: Reversibility,
473 pub persistence: Persistence,
474 pub disclosure: Disclosure,
475 pub secret: Secret,
476 pub network: Network,
477 pub execution: Execution,
478 pub cost: Cost,
479 pub because: String,
480}
481
482impl Capability {
483 pub fn new(operation: Operation) -> Self {
485 Self { operation, ..Self::default() }
486 }
487
488 pub fn worst(because: impl Into<String>) -> Self {
495 Self {
496 operation: Operation::Execute,
497 locus: Locus {
498 local: LocalLocus::Kernel,
499 remote: RemoteReach::Arbitrary,
500 binding: RemoteBinding::Ambient,
501 provenance: Provenance::Opaque,
502 },
503 scale: Scale::Unbounded,
504 retrieval: RetrievalGranularity::BulkContent,
505 authority: Authority::OtherUser,
506 isolation: Isolation::None,
507 reversibility: Reversibility::Irreversible,
508 persistence: Persistence {
509 level: PersistenceLevel::Installing,
510 trigger: Trigger { escape: TriggerEscape::Boot, kind: TriggerKind::None },
511 },
512 disclosure: Disclosure {
513 audience: DisclosureAudience::Public,
514 channel: Channel::Unknown,
515 principal: Principal::Cross,
516 },
517 secret: Secret {
518 level: SecretLevel::Transmits,
519 channel: Channel::Unknown,
520 principal: Principal::Cross,
521 },
522 network: Network {
523 direction: NetDirection::InboundListen,
524 destination: NetDestination::Arbitrary,
525 payload: NetPayload::SendsHostData,
526 },
527 execution: Execution { trust: ExecutionTrust::NetworkSourced, supply_chain: None },
528 cost: Cost::Quota,
529 because: because.into(),
530 }
531 }
532}
533
534#[derive(Clone, Debug, Default, PartialEq, Eq)]
537pub struct Profile {
538 pub capabilities: Vec<Capability>,
539}
540
541impl Profile {
542 pub fn of(capabilities: Vec<Capability>) -> Self {
544 Self { capabilities }
545 }
546}
547
548#[cfg(test)]
549mod tests {
550 use super::*;
551
552 fn assert_term_strings_roundtrip<T: FacetTerm + std::fmt::Debug>() {
553 for &term in T::all() {
554 assert_eq!(
555 T::from_term(term.as_str()),
556 Some(term),
557 "term {:?} did not round-trip through {:?}",
558 term,
559 term.as_str(),
560 );
561 }
562 for (i, &a) in T::all().iter().enumerate() {
563 for &b in &T::all()[i + 1..] {
564 assert_ne!(a.as_str(), b.as_str(), "two variants share a TOML spelling");
565 }
566 }
567 assert_eq!(T::from_term("definitely-not-a-term"), None);
568 }
569
570 fn assert_zero_is_minimum<T: FacetTerm + Ord + Default + std::fmt::Debug>() {
571 let zero = T::all()[0];
572 assert_eq!(T::default(), zero, "Default must be the zero term (first variant)");
573 for &term in T::all() {
574 assert!(zero <= term, "zero term {zero:?} is not <= {term:?}");
575 }
576 }
577
578 #[test]
579 fn every_term_roundtrips_and_is_uniquely_spelled() {
580 assert_term_strings_roundtrip::<Operation>();
581 assert_term_strings_roundtrip::<LocalLocus>();
582 assert_term_strings_roundtrip::<RemoteReach>();
583 assert_term_strings_roundtrip::<RemoteBinding>();
584 assert_term_strings_roundtrip::<Provenance>();
585 assert_term_strings_roundtrip::<Scale>();
586 assert_term_strings_roundtrip::<RetrievalGranularity>();
587 assert_term_strings_roundtrip::<Authority>();
588 assert_term_strings_roundtrip::<Isolation>();
589 assert_term_strings_roundtrip::<Reversibility>();
590 assert_term_strings_roundtrip::<PersistenceLevel>();
591 assert_term_strings_roundtrip::<TriggerEscape>();
592 assert_term_strings_roundtrip::<TriggerKind>();
593 assert_term_strings_roundtrip::<DisclosureAudience>();
594 assert_term_strings_roundtrip::<SecretLevel>();
595 assert_term_strings_roundtrip::<Channel>();
596 assert_term_strings_roundtrip::<Principal>();
597 assert_term_strings_roundtrip::<NetDirection>();
598 assert_term_strings_roundtrip::<NetDestination>();
599 assert_term_strings_roundtrip::<NetPayload>();
600 assert_term_strings_roundtrip::<ExecutionTrust>();
601 assert_term_strings_roundtrip::<SupplySource>();
602 assert_term_strings_roundtrip::<Pinning>();
603 assert_term_strings_roundtrip::<ExecSurface>();
604 assert_term_strings_roundtrip::<Cost>();
605 }
606
607 #[test]
608 fn ordinal_zero_terms_are_the_minimum() {
609 assert_zero_is_minimum::<LocalLocus>();
610 assert_zero_is_minimum::<RemoteReach>();
611 assert_zero_is_minimum::<Provenance>();
612 assert_zero_is_minimum::<Scale>();
613 assert_zero_is_minimum::<RetrievalGranularity>();
614 assert_zero_is_minimum::<Authority>();
615 assert_zero_is_minimum::<Isolation>();
616 assert_zero_is_minimum::<Reversibility>();
617 assert_zero_is_minimum::<PersistenceLevel>();
618 assert_zero_is_minimum::<TriggerEscape>();
619 assert_zero_is_minimum::<DisclosureAudience>();
620 assert_zero_is_minimum::<SecretLevel>();
621 assert_zero_is_minimum::<NetDirection>();
622 assert_zero_is_minimum::<NetDestination>();
623 assert_zero_is_minimum::<NetPayload>();
624 assert_zero_is_minimum::<ExecutionTrust>();
625 assert_zero_is_minimum::<Pinning>();
626 assert_zero_is_minimum::<Cost>();
627 }
628
629 #[test]
630 fn ordinal_ladders_match_the_spec() {
631 assert!(LocalLocus::Process < LocalLocus::Worktree);
632 assert!(LocalLocus::Worktree < LocalLocus::Adjacent);
633 assert!(LocalLocus::Adjacent < LocalLocus::WorktreeTrusted);
634 assert!(LocalLocus::WorktreeTrusted < LocalLocus::User);
635 assert!(LocalLocus::Worktree < LocalLocus::Machine);
636 assert!(LocalLocus::Machine < LocalLocus::SystemIntegrity);
637 assert!(LocalLocus::SystemIntegrity < LocalLocus::Device);
638 assert!(LocalLocus::Device < LocalLocus::Kernel);
639 assert!(Scale::Single < Scale::Bounded && Scale::Bounded < Scale::Unbounded);
640 assert!(RetrievalGranularity::Metadata < RetrievalGranularity::Record);
641 assert!(RetrievalGranularity::Record < RetrievalGranularity::BulkContent);
642 assert!(Authority::User < Authority::Root && Authority::Root < Authority::OtherUser);
643 assert!(Reversibility::Recoverable < Reversibility::Irreversible);
644 assert!(PersistenceLevel::Data < PersistenceLevel::Installing);
645 assert!(TriggerEscape::Immediate < TriggerEscape::Boot);
646 assert!(DisclosureAudience::LocalProcess < DisclosureAudience::Public);
647 assert!(Provenance::Na < Provenance::Established);
648 assert!(Provenance::Established < Provenance::Literal);
649 assert!(Provenance::Literal < Provenance::Opaque);
650 assert!(SecretLevel::Reads < SecretLevel::Transmits);
651 assert!(ExecutionTrust::SelfCode < ExecutionTrust::NetworkSourced);
652 assert!(Pinning::Floating < Pinning::HashVerified);
653 }
654
655 #[test]
656 fn capability_new_leaves_all_other_facets_at_zero() {
657 let cap = Capability::new(Operation::Destroy);
658 assert_eq!(cap.operation, Operation::Destroy);
659 assert_eq!(cap.locus, Locus::default());
660 assert_eq!(cap.locus.local, LocalLocus::Process);
661 assert_eq!(cap.scale, Scale::Single);
662 assert_eq!(cap.retrieval, RetrievalGranularity::Metadata);
663 assert_eq!(cap.authority, Authority::User);
664 assert_eq!(cap.reversibility, Reversibility::None);
665 assert_eq!(cap.secret.level, SecretLevel::None);
666 assert_eq!(cap.disclosure.audience, DisclosureAudience::None);
667 assert_eq!(cap.network.direction, NetDirection::None);
668 assert_eq!(cap.execution.trust, ExecutionTrust::None);
669 assert!(cap.execution.supply_chain.is_none());
670 assert_eq!(cap.cost, Cost::None);
671 assert!(cap.because.is_empty());
672 }
673
674 #[test]
675 fn default_capability_is_a_zero_observe() {
676 assert_eq!(Capability::default().operation, Operation::Observe);
677 assert_eq!(Capability::default(), Capability::new(Operation::Observe));
678 }
679}