Skip to main content

safe_chains/engine/
facet.rs

1//! The capability vocabulary — the 12 facets of v1.4 §2.
2//!
3//! A [`Capability`] is one point in facet-space; a [`Profile`] is the set of
4//! capabilities a resolved command line exhibits. Nothing here makes a decision —
5//! admissibility (a level predicate over profiles) arrives in a later commit.
6//!
7//! Two kinds of facet term:
8//! - **ordinal** — a severity/trust ladder; `#[derive(Ord)]` gives declaration
9//!   order = the ladder, so a level can ceiling it (`facet <= term`) or floor it
10//!   (`facet >= term`). The first-declared variant is the least-severe **zero term**
11//!   and the [`Default`].
12//! - **categorical** — a set with no severity ordering; admissibility is set
13//!   membership, never `<=`. Deliberately *not* `Ord`, so a comparison can't be
14//!   written by accident (the R25 bug: never order `kernel` vs `remote`).
15//!
16//! Compound facets (`locus`, `persistence`, `disclosure`, `secret`, `network`) are
17//! structs of independent axes, each its own term — never collapsed to one ordinal.
18
19/// A single facet term: the closed vocabulary of one axis, with its TOML spelling.
20pub trait FacetTerm: Copy + Eq + Sized + 'static {
21    /// Every term, in declaration order (for ordinals, least-severe first).
22    fn all() -> &'static [Self];
23    /// The term's canonical TOML spelling.
24    fn as_str(self) -> &'static str;
25    /// Parse a term from its TOML spelling.
26    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
77// ── 2.1 The act ────────────────────────────────────────────────────────────────
78
79categorical_term! {
80    /// The operation a capability performs (v1.4 §2.1). One per capability.
81    Operation {
82        Observe => "observe",
83        Create => "create",
84        Mutate => "mutate",
85        Destroy => "destroy",
86        Execute => "execute",
87        Communicate => "communicate",
88        Configure => "configure",   // change settings that alter future commands
89        Authorize => "authorize",   // change credentials/trust/access
90        Control => "control",       // start/stop/signal processes, services, devices
91    }
92}
93
94// ── 2.2 Reach ──────────────────────────────────────────────────────────────────
95
96ordinal_term! {
97    /// How deep into this host a capability reaches (v1.4 §2.2). `device`/`kernel`
98    /// void the abstractions the fs rungs assume and are deny-by-default everywhere.
99    LocalLocus {
100        Process => "process",
101        Temp => "temp",
102        SandboxScope => "sandbox-scope",
103        Worktree => "worktree",
104        Adjacent => "adjacent",                 // a SIBLING project's ORDINARY files (peer of the
105                                                // workspace under the same parent) — a co-located repo
106                                                // the agent reaches into. BELOW worktree-trusted: a peer
107                                                // source write (dev-loop) is LESS dangerous than a
108                                                // .git/hook write (which auto-executes), so a developer
109                                                // write clause `<= adjacent` must NOT reach the frozen tier.
110        WorktreeTrusted => "worktree-trusted",  // .git/, .envrc, hooks, CI configs — read-ok, WRITE-frozen
111        User => "user",                         // ~, keychain
112        Machine => "machine",                   // services, /etc app config, /usr/local — ordinary admin
113        SystemIntegrity => "system-integrity",  // identity/auth/boot/loader/system binaries — compromise-complete
114        Device => "device",                     // raw block/char devices
115        Kernel => "kernel",                     // module/extension load
116    }
117}
118
119ordinal_term! {
120    /// Which other host a capability reaches (v1.4 §2.2, the reach axis of `locus`).
121    RemoteReach {
122        None => "none",
123        Fixed => "fixed",
124        Arbitrary => "arbitrary",
125    }
126}
127
128categorical_term! {
129    /// Whether a remote target is named on the command line or taken from session
130    /// state — the pinned-vs-ambient bit `infra` gates on (v1.4 §2.2, HP-12).
131    RemoteBinding {
132        Na => "n/a",         // no remote reach
133        Pinned => "pinned",  // host/context/profile explicit on the command line
134        Ambient => "ambient",
135    }
136}
137
138ordinal_term! {
139    /// How the acted-on remote target was DESIGNATED — a *trust* ladder, orthogonal to
140    /// `RemoteReach` (breadth: one host vs any) and `RemoteBinding` (visibility: on the CLI
141    /// vs from session). Trust in a destination follows its provenance: what makes `git push`
142    /// safe is that the target is a pre-established root, not that data leaves. The
143    /// destination-aware resolver assigns it from the target argument (we do not read
144    /// `.git/config`); see `behavioral-taxonomy-exposure.md` §4.
145    Provenance {
146        Na => "n/a",                    // no designated remote target (a local op)
147        Established => "established",    // a stable handle set up by a prior deliberate act:
148                                         // a configured remote, a named context, a saved profile
149        Literal => "literal",           // spelled out in full at invocation (a URL/host typed
150                                         // now) — visible and reviewable, but injectable
151        Opaque => "opaque",             // from a variable / substitution — not visible in the
152                                         // command string, so unreviewable (fail-closed worst)
153    }
154}
155
156ordinal_term! {
157    /// Breadth of effect (v1.4 §2.2). Modifies `destroy` *and* `disclosure` (R23).
158    Scale {
159        Single => "single",
160        Bounded => "bounded",      // a glob/dir/explicit list
161        Unbounded => "unbounded",  // recursion / mass op
162    }
163}
164
165ordinal_term! {
166    /// Granularity of what a READ retrieves — orthogonal to `scale` (which counts items) and
167    /// `secret` (which flags credentials). Distinguishes a metadata/descriptor read from the
168    /// retrieval of opaque STORED CONTENT the classifier cannot assess. `bulk-content` is a
169    /// stored blob (an S3 object, an EBS block, a Glacier archive) — routinely a secrets file,
170    /// private key, or DB dump, but unknowable statically — so it earns a proportionate tier
171    /// (network-admin: elevated remote egress) WITHOUT being conflated with a credential read
172    /// (`secret = reads` → yolo). `record` is structured data you asked for (query results, a
173    /// db dump); `metadata` is a descriptor (describe/list/get-config). See
174    /// docs/design/behavioral-taxonomy-archetypes.md §5 (#1).
175    RetrievalGranularity {
176        Metadata => "metadata",         // a descriptor: describe/list/get-config
177        Record => "record",             // structured data requested: query results, a db dump
178        BulkContent => "bulk-content",  // opaque stored bytes: an object/block/archive body
179    }
180}
181
182ordinal_term! {
183    /// Privilege the capability runs with (v1.4 §2.2).
184    Authority {
185        User => "user",
186        Elevated => "elevated",      // sudo/doas
187        Root => "root",
188        OtherUser => "other-user",   // setuid/run-as
189    }
190}
191
192ordinal_term! {
193    /// Isolation strength of an enclosing frame (v1.4 §2.2). A frame clamps nested
194    /// `locus` to `sandbox-scope`; breach flags re-add loci (§3.2).
195    Isolation {
196        None => "none",
197        View => "view",             // chroot
198        Namespace => "namespace",
199        Userns => "userns",
200        Vm => "vm",
201        Ocap => "ocap",
202    }
203}
204
205// ── 2.3 Durability ─────────────────────────────────────────────────────────────
206
207ordinal_term! {
208    /// How hard the effect is to undo (v1.4 §2.3). Environment-dependent cases
209    /// resolve worst-case (HP-8).
210    Reversibility {
211        None => "none",              // pure observe
212        Trivial => "trivial",        // idempotent/undo
213        Recoverable => "recoverable",// VCS/recycle/snapshot
214        Effortful => "effortful",    // out-of-band backups only
215        Irreversible => "irreversible",
216    }
217}
218
219ordinal_term! {
220    /// What the capability leaves behind (v1.4 §2.3, the level axis of `persistence`).
221    PersistenceLevel {
222        Transient => "transient",
223        Data => "data",
224        Reconfiguring => "reconfiguring",  // alters future commands
225        Installing => "installing",        // adds executables/services/hooks
226    }
227}
228
229ordinal_term! {
230    /// How far execution escapes the check (v1.4 §2.3, R16/R24) — the part levels gate.
231    TriggerEscape {
232        Immediate => "immediate",  // done on return
233        Detached => "detached",    // one instance survives the session (nohup/setsid)
234        Recurring => "recurring",  // re-fires until removed
235        Boot => "boot",            // re-fires and survives reboot (systemctl enable, @reboot)
236    }
237}
238
239categorical_term! {
240    /// The kind of recurrence (v1.4 §2.3) — for the `because` string, not a severity
241    /// rung: a per-save `event` can fire more often than a monthly `clock`.
242    TriggerKind {
243        None => "none",     // not recurring
244        Clock => "clock",   // cron, at
245        Event => "event",   // watchexec, git hooks, .envrc on cd
246    }
247}
248
249// ── 2.4 Information exposure ────────────────────────────────────────────────────
250
251ordinal_term! {
252    /// Who ends up able to see disclosed output (v1.4 §2.4). `local-process` is
253    /// stdout → the agent/model provider — the HP-15 audience that gates secret reads.
254    DisclosureAudience {
255        None => "none",
256        LocalProcess => "local-process",       // stdout → the agent/model
257        LocalPersistent => "local-persistent", // other local users
258        TrustedRemote => "trusted-remote",
259        SharedRemote => "shared-remote",
260        Public => "public",
261    }
262}
263
264ordinal_term! {
265    /// A capability's relationship to secret material (v1.4 §2.4).
266    SecretLevel {
267        None => "none",
268        UsesAmbient => "uses-ambient",
269        Reads => "reads",
270        Writes => "writes",
271        Transmits => "transmits",
272    }
273}
274
275categorical_term! {
276    /// The channel a disclosure or secret flows over (v1.4 §2.4). An **open set**:
277    /// an unrecognized/covert channel is `Unknown` and worst-cased by the resolver.
278    Channel {
279        None => "none",
280        Filesystem => "filesystem",
281        StdoutToModel => "stdout-to-model",
282        Network => "network",
283        Clipboard => "clipboard",              // pbcopy/pbpaste
284        Ipc => "ipc",
285        CredentialStore => "credential-store", // keychain
286        CrossProcess => "cross-process",       // lldb -p, /proc/*/mem
287        Unknown => "unknown",
288    }
289}
290
291categorical_term! {
292    /// Whose data a read touches (v1.4 §2.4) — a read can cross a principal boundary
293    /// on the same host (another process's memory/argv) with no fs or network touch.
294    Principal {
295        Own => "own",
296        Cross => "cross",
297    }
298}
299
300// ── 2.5 Channel (network) ──────────────────────────────────────────────────────
301
302ordinal_term! {
303    /// Network direction (v1.4 §2.5).
304    NetDirection {
305        None => "none",
306        Loopback => "loopback",
307        Outbound => "outbound",
308        InboundListen => "inbound-listen",
309    }
310}
311
312ordinal_term! {
313    /// Network destination (v1.4 §2.5). Same axis as `locus.remote` reach.
314    NetDestination {
315        Na => "n/a",
316        Fixed => "fixed",
317        Arbitrary => "arbitrary",
318    }
319}
320
321ordinal_term! {
322    /// What a network capability carries (v1.4 §2.5).
323    NetPayload {
324        None => "none",
325        Fetches => "fetches",
326        SendsHostData => "sends-host-data",
327    }
328}
329
330// ── 2.6 Code provenance ────────────────────────────────────────────────────────
331
332ordinal_term! {
333    /// Where executed code comes from (v1.4 §2.6, local-trust ladder). When
334    /// `NetworkSourced`, the supply-chain sub-facets ([`SupplyChain`]) refine it.
335    ExecutionTrust {
336        None => "none",
337        SelfCode => "self",
338        CallerInline => "caller-inline",
339        CallerFile => "caller-file",
340        AmbientConfig => "ambient-config",   // Makefile/hooks/.envrc/plugins
341        NetworkSourced => "network-sourced",
342    }
343}
344
345categorical_term! {
346    /// Where network-sourced code came from (v1.4 §2.6). Categorical — a level lists
347    /// the sources it accepts rather than assuming a severity order.
348    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    /// How tightly a fetched artifact is pinned (v1.4 §2.6). A *trust* ladder: higher
359    /// is safer, so a level floors it (`>= version`) rather than ceilings it.
360    Pinning {
361        Floating => "floating",
362        Version => "version",
363        HashVerified => "hash-verified",
364        Digest => "digest",
365    }
366}
367
368categorical_term! {
369    /// When/what fetched code runs (v1.4 §2.6). Categorical — the risk order across
370    /// install-hook / build-script / call-time / run-artifact is genuinely unclear, so
371    /// a level lists the surfaces it accepts instead of ceiling-ing a false ladder.
372    ExecSurface {
373        None => "none",
374        InstallHook => "install-hook",   // code on install (npm lifecycle, pip setup.py)
375        BuildScript => "build-script",   // code on build (cargo build.rs, node-gyp)
376        CallTime => "call-time",         // deps' code runs only when your program runs
377        RunArtifact => "run-artifact",   // you execute the fetched binary/image
378    }
379}
380
381// ── 2.7 Resource ───────────────────────────────────────────────────────────────
382
383ordinal_term! {
384    /// Resource/billing cost (v1.4 §2.7). Populated for provisioning tools.
385    Cost {
386        None => "none",
387        LocalResource => "local-resource",
388        Metered => "metered",   // billable
389        Quota => "quota",
390    }
391}
392
393// ── 2.8 Compound facets & the capability record ────────────────────────────────
394
395/// Reach — two independent axes (v1.4 §2.2, R25).
396#[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/// Durability trigger — how far execution escapes, and (if recurring) what kind.
405#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
406pub struct Trigger {
407    pub escape: TriggerEscape,
408    pub kind: TriggerKind,
409}
410
411/// What a capability leaves behind, and when it re-fires (v1.4 §2.3).
412#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
413pub struct Persistence {
414    pub level: PersistenceLevel,
415    pub trigger: Trigger,
416}
417
418/// Where disclosed output goes, over which channel, whose data (v1.4 §2.4).
419#[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/// A capability's relationship to secrets, over which channel, whose (v1.4 §2.4).
427#[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/// A network capability's shape (v1.4 §2.5).
435#[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/// The provenance of network-sourced code (v1.4 §2.6).
443#[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/// Code provenance: the local-trust rung, plus supply-chain detail when the code is
451/// network-sourced (v1.4 §2.6). `supply_chain` is present only for network-sourced
452/// execution — a command running no downloaded code leaves it `None`, and a level's
453/// supply-chain constraints are then vacuously satisfied.
454#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
455pub struct Execution {
456    pub trust: ExecutionTrust,
457    pub supply_chain: Option<SupplyChain>,
458}
459
460/// One capability — a single point in facet-space (v1.4 §2.8). Facets left unset
461/// default to their zero term. `because` cites the discriminator (§5); the nested
462/// delegate profile and supply-chain sub-facets arrive with the mechanisms that
463/// need them.
464#[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    /// A capability performing `operation`, every other facet at its zero term.
484    pub fn new(operation: Operation) -> Self {
485        Self { operation, ..Self::default() }
486    }
487
488    /// A maximally-severe capability — every axis at its worst term, so no well-formed
489    /// level admits it (`locus.local = kernel` alone denies it everywhere, v1.4 §4.3).
490    /// The resolver returns this when it cannot certify something (§0), keeping the
491    /// engine from being *looser* than a strict classifier on a form it doesn't
492    /// understand. Ordinal worsts are the ladder tops; categorical worsts are the
493    /// hazardous term (`Channel::Unknown`, `Principal::Cross`).
494    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/// The set of capabilities a resolved command line exhibits (v1.4 §2.8, §4.1). A
535/// profile passes a level iff *every* capability is admissible.
536#[derive(Clone, Debug, Default, PartialEq, Eq)]
537pub struct Profile {
538    pub capabilities: Vec<Capability>,
539}
540
541impl Profile {
542    /// A profile of exactly these capabilities.
543    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}