1use std::collections::BTreeMap;
15use std::sync::LazyLock;
16
17use serde::{Deserialize, Serialize};
18
19use super::facet::FacetTerm;
20use super::level::{Clause, Level, OrdBound};
21
22pub fn default_levels() -> &'static [Level] {
24 static LEVELS: LazyLock<Vec<Level>> =
25 LazyLock::new(|| build_level_set(include_str!("../../levels/default.toml")).expect("embedded levels/default.toml must compile"));
26 &LEVELS
27}
28
29pub fn build_level_set(source: &str) -> Result<Vec<Level>, String> {
31 let set: TomlLevelSet = toml::from_str(source).map_err(|e| e.to_string())?;
32 let mut pending: Vec<(String, TomlLevel)> = set.level.into_iter().collect();
33 let mut built: Vec<Level> = Vec::new();
34 let mut by_name: BTreeMap<String, usize> = BTreeMap::new();
35
36 while !pending.is_empty() {
37 let before = pending.len();
38 let mut still = Vec::new();
39 for (name, tl) in pending {
40 let ready = tl.extends.as_ref().is_none_or(|base| by_name.contains_key(base));
41 if ready {
42 let level = compile_level(name.clone(), tl, &built, &by_name)?;
43 by_name.insert(name, built.len());
44 built.push(level);
45 } else {
46 still.push((name, tl));
47 }
48 }
49 if still.len() == before {
50 let names: Vec<&String> = still.iter().map(|(n, _)| n).collect();
51 return Err(format!("unresolved `extends` (cycle or missing base) among {names:?}"));
52 }
53 pending = still;
54 }
55 Ok(built)
56}
57
58fn compile_level(name: String, tl: TomlLevel, built: &[Level], by_name: &BTreeMap<String, usize>) -> Result<Level, String> {
59 let allow = tl
60 .allow
61 .into_iter()
62 .map(build_clause)
63 .collect::<Result<Vec<_>, _>>()
64 .map_err(|e| format!("level `{name}`: {e}"))?;
65 let deny = tl
66 .deny
67 .into_iter()
68 .map(build_clause)
69 .collect::<Result<Vec<_>, _>>()
70 .map_err(|e| format!("level `{name}`: {e}"))?;
71
72 match tl.extends {
73 Some(base_name) => {
74 if !deny.is_empty() {
75 return Err(format!(
76 "level `{name}` extends `{base_name}` and declares `deny` — extends only \
77 loosens (R27); author a stricter level from a lower base instead"
78 ));
79 }
80 let idx = *by_name.get(&base_name).ok_or_else(|| format!("level `{name}`: unknown base `{base_name}`"))?;
81 let base = built.get(idx).ok_or_else(|| format!("level `{name}`: base index out of range"))?;
82 Ok(Level::extend(base, name, allow))
83 }
84 None => Ok(Level { name, allow, deny }),
85 }
86}
87
88fn build_clause(tc: TomlClause) -> Result<Clause, String> {
89 let mut c = Clause::default();
90 if let Some(v) = tc.operation {
91 c.operation = Some(parse_set(&v)?);
92 }
93 if let Some(l) = tc.locus {
94 c.local_locus = opt_bound(l.local.as_deref())?;
95 c.remote_reach = opt_bound(l.remote.as_deref())?;
96 c.provenance = opt_bound(l.provenance.as_deref())?;
97 if let Some(b) = l.binding {
98 c.remote_binding = Some(parse_set(&b)?);
99 }
100 }
101 c.scale = opt_bound(tc.scale.as_deref())?;
102 c.retrieval = opt_bound(tc.retrieval.as_deref())?;
103 c.authority = opt_bound(tc.authority.as_deref())?;
104 c.isolation = opt_bound(tc.isolation.as_deref())?;
105 c.reversibility = opt_bound(tc.reversibility.as_deref())?;
106 if let Some(p) = tc.persistence {
107 c.persistence_level = opt_bound(p.level.as_deref())?;
108 if let Some(t) = p.trigger {
109 c.trigger_escape = opt_bound(t.escape.as_deref())?;
110 if let Some(k) = t.kind {
111 c.trigger_kind = Some(parse_set(&k)?);
112 }
113 }
114 }
115 if let Some(d) = tc.disclosure {
116 c.disclosure_audience = opt_bound(d.audience.as_deref())?;
117 if let Some(ch) = d.channel {
118 c.disclosure_channel = Some(parse_set(&ch)?);
119 }
120 if let Some(pr) = d.principal {
121 c.disclosure_principal = Some(parse_set(&pr)?);
122 }
123 }
124 if let Some(s) = tc.secret {
125 c.secret_level = opt_bound(s.level.as_deref())?;
126 if let Some(ch) = s.channel {
127 c.secret_channel = Some(parse_set(&ch)?);
128 }
129 if let Some(pr) = s.principal {
130 c.secret_principal = Some(parse_set(&pr)?);
131 }
132 }
133 if let Some(n) = tc.network {
134 c.net_direction = opt_bound(n.direction.as_deref())?;
135 c.net_destination = opt_bound(n.destination.as_deref())?;
136 c.net_payload = opt_bound(n.payload.as_deref())?;
137 }
138 c.execution_trust = opt_bound(tc.execution.as_deref())?;
139 if let Some(sc) = tc.supply_chain {
140 if let Some(s) = sc.source {
141 c.supply_source = Some(parse_set(&s)?);
142 }
143 c.pinning = opt_bound(sc.pinning.as_deref())?;
144 if let Some(e) = sc.exec_surface {
145 c.exec_surface = Some(parse_set(&e)?);
146 }
147 }
148 c.cost = opt_bound(tc.cost.as_deref())?;
149 Ok(c)
150}
151
152fn opt_bound<T: FacetTerm + Ord>(s: Option<&str>) -> Result<Option<OrdBound<T>>, String> {
153 s.map(parse_bound).transpose()
154}
155
156fn parse_bound<T: FacetTerm + Ord>(s: &str) -> Result<OrdBound<T>, String> {
162 let parts: Vec<&str> = s.split(',').map(str::trim).collect();
163 if parts.len() == 1 {
164 let p = parts[0];
165 return if let Some(rest) = p.strip_prefix("<=") {
166 Ok(OrdBound::at_most(parse_term(rest)?))
167 } else if let Some(rest) = p.strip_prefix(">=") {
168 Ok(OrdBound::at_least(parse_term(rest)?))
169 } else {
170 Ok(OrdBound::exactly(parse_term(p.strip_prefix('=').unwrap_or(p))?))
171 };
172 }
173 let (mut min, mut max) = (None, None);
174 for p in parts {
175 if let Some(rest) = p.strip_prefix("<=") {
176 if max.replace(parse_term(rest)?).is_some() {
177 return Err(format!("bound `{s}` sets `<=` more than once"));
178 }
179 } else if let Some(rest) = p.strip_prefix(">=") {
180 if min.replace(parse_term(rest)?).is_some() {
181 return Err(format!("bound `{s}` sets `>=` more than once"));
182 }
183 } else {
184 return Err(format!("bound `{s}`: each part of a range must be `<=`/`>=`"));
185 }
186 }
187 Ok(OrdBound { min, max })
188}
189
190fn parse_set<T: FacetTerm>(v: &StringOrVec) -> Result<Vec<T>, String> {
191 v.as_slice().iter().map(|s| parse_term(s)).collect()
192}
193
194fn parse_term<T: FacetTerm>(s: &str) -> Result<T, String> {
195 T::from_term(s.trim()).ok_or_else(|| format!("unknown term `{}`", s.trim()))
196}
197
198#[derive(Deserialize, Serialize)]
204struct TomlLevelSet {
205 #[serde(default)]
206 level: BTreeMap<String, TomlLevel>,
207}
208
209#[derive(Deserialize, Serialize)]
210#[serde(deny_unknown_fields)]
211struct TomlLevel {
212 #[serde(skip_serializing_if = "Option::is_none")]
213 extends: Option<String>,
214 #[serde(default, skip_serializing_if = "Vec::is_empty")]
215 allow: Vec<TomlClause>,
216 #[serde(default, skip_serializing_if = "Vec::is_empty")]
217 deny: Vec<TomlClause>,
218}
219
220#[derive(Deserialize, Serialize, Default)]
221#[serde(deny_unknown_fields)]
222struct TomlClause {
223 #[serde(skip_serializing_if = "Option::is_none")]
224 operation: Option<StringOrVec>,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 locus: Option<TomlLocus>,
227 #[serde(skip_serializing_if = "Option::is_none")]
228 scale: Option<String>,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 retrieval: Option<String>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 authority: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 isolation: Option<String>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 reversibility: Option<String>,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 persistence: Option<TomlPersistence>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 disclosure: Option<TomlDisclosure>,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 secret: Option<TomlSecret>,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 network: Option<TomlNetwork>,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 execution: Option<String>,
247 #[serde(skip_serializing_if = "Option::is_none")]
248 supply_chain: Option<TomlSupplyChain>,
249 #[serde(skip_serializing_if = "Option::is_none")]
250 cost: Option<String>,
251}
252
253#[derive(Deserialize, Serialize)]
254#[serde(deny_unknown_fields)]
255struct TomlLocus {
256 #[serde(skip_serializing_if = "Option::is_none")]
257 local: Option<String>,
258 #[serde(skip_serializing_if = "Option::is_none")]
259 remote: Option<String>,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 binding: Option<StringOrVec>,
262 #[serde(skip_serializing_if = "Option::is_none")]
263 provenance: Option<String>,
264}
265
266#[derive(Deserialize, Serialize)]
267#[serde(deny_unknown_fields)]
268struct TomlPersistence {
269 #[serde(skip_serializing_if = "Option::is_none")]
270 level: Option<String>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 trigger: Option<TomlTrigger>,
273}
274
275#[derive(Deserialize, Serialize)]
276#[serde(deny_unknown_fields)]
277struct TomlTrigger {
278 #[serde(skip_serializing_if = "Option::is_none")]
279 escape: Option<String>,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 kind: Option<StringOrVec>,
282}
283
284#[derive(Deserialize, Serialize)]
285#[serde(deny_unknown_fields)]
286struct TomlDisclosure {
287 #[serde(skip_serializing_if = "Option::is_none")]
288 audience: Option<String>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 channel: Option<StringOrVec>,
291 #[serde(skip_serializing_if = "Option::is_none")]
292 principal: Option<StringOrVec>,
293}
294
295#[derive(Deserialize, Serialize)]
296#[serde(deny_unknown_fields)]
297struct TomlSecret {
298 #[serde(skip_serializing_if = "Option::is_none")]
299 level: Option<String>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 channel: Option<StringOrVec>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 principal: Option<StringOrVec>,
304}
305
306#[derive(Deserialize, Serialize)]
307#[serde(deny_unknown_fields)]
308struct TomlNetwork {
309 #[serde(skip_serializing_if = "Option::is_none")]
310 direction: Option<String>,
311 #[serde(skip_serializing_if = "Option::is_none")]
312 destination: Option<String>,
313 #[serde(skip_serializing_if = "Option::is_none")]
314 payload: Option<String>,
315}
316
317#[derive(Deserialize, Serialize)]
318#[serde(deny_unknown_fields)]
319struct TomlSupplyChain {
320 #[serde(skip_serializing_if = "Option::is_none")]
321 source: Option<StringOrVec>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 pinning: Option<String>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 exec_surface: Option<StringOrVec>,
326}
327
328#[derive(Deserialize, Serialize)]
329#[serde(untagged)]
330enum StringOrVec {
331 One(String),
332 Many(Vec<String>),
333}
334
335impl StringOrVec {
336 fn as_slice(&self) -> &[String] {
337 match self {
338 StringOrVec::One(s) => std::slice::from_ref(s),
339 StringOrVec::Many(v) => v,
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use crate::engine::facet::*;
348
349 fn level<'a>(levels: &'a [Level], name: &str) -> &'a Level {
350 levels.iter().find(|l| l.name == name).expect("level exists")
351 }
352
353 fn observe_at(local: LocalLocus) -> Profile {
354 let mut c = Capability::new(Operation::Observe);
355 c.locus.local = local;
356 Profile::of(vec![c])
357 }
358
359 #[test]
360 fn the_default_ladder_compiles() {
361 let levels = default_levels();
362 let mut names: Vec<&str> = levels.iter().map(|l| l.name.as_str()).collect();
363 names.sort_unstable();
364 assert_eq!(names, ["developer", "editor", "local-admin", "network-admin", "paranoid", "reader", "yolo"],);
365 let raw: Vec<&str> = levels.iter().map(|l| l.name.as_str()).collect();
369 let pos = |n| raw.iter().position(|&x| x == n).expect("level present");
370 assert!(
371 pos("paranoid") < pos("reader") && pos("reader") < pos("editor") && pos("editor") < pos("developer"),
372 "mapped band out of order: {raw:?}",
373 );
374 }
375
376 #[test]
377 fn inert_admits_a_version_probe_but_not_reading_the_worktree() {
378 let levels = default_levels();
379 let inert = level(levels, "paranoid");
380 assert!(inert.admits(&observe_at(LocalLocus::Process)), "node --version");
381 assert!(!inert.admits(&observe_at(LocalLocus::Worktree)), "cat ./notes is above paranoid");
382 }
383
384 #[test]
385 fn read_local_reads_the_worktree_but_refuses_home_extraction_and_writes() {
386 let levels = default_levels();
387 let read_local = level(levels, "reader");
388 assert!(read_local.admits(&observe_at(LocalLocus::Worktree)), "cat ./notes");
389 assert!(read_local.admits(&observe_at(LocalLocus::WorktreeTrusted)), "git status reads .git");
390
391 assert!(read_local.admits(&observe_at(LocalLocus::User)), "cat ~/notes.txt");
397
398 let extraction = {
401 let mut c = Capability::new(Operation::Observe);
402 c.secret.level = SecretLevel::Reads;
403 Profile::of(vec![c])
404 };
405 assert!(!read_local.admits(&extraction), "keychain extraction");
406
407 assert!(!read_local.admits(&Profile::of(vec![Capability::new(Operation::Create)])), "a write");
408 }
409
410 #[test]
413 fn reader_admits_a_pure_remote_fetch_but_not_exfil_or_remote_writes() {
414 let reader = level(default_levels(), "reader");
415
416 let fetch = {
417 let mut c = Capability::new(Operation::Observe);
418 c.locus.remote = RemoteReach::Arbitrary;
419 c.network.direction = NetDirection::Outbound;
420 c.network.payload = NetPayload::Fetches;
421 c.disclosure.audience = DisclosureAudience::LocalProcess;
422 Profile::of(vec![c])
423 };
424 assert!(reader.admits(&fetch), "curl GET / koyeb list — a pure remote fetch");
425
426 let exfil = {
428 let mut c = Capability::new(Operation::Observe);
429 c.locus.remote = RemoteReach::Arbitrary;
430 c.network.direction = NetDirection::Outbound;
431 c.network.payload = NetPayload::SendsHostData;
432 Profile::of(vec![c])
433 };
434 assert!(!reader.admits(&exfil), "sends-host-data (curl -d @secret) is not a read");
435
436 let remote_write = {
438 let mut c = Capability::new(Operation::Mutate);
439 c.locus.remote = RemoteReach::Fixed;
440 c.network.direction = NetDirection::Outbound;
441 Profile::of(vec![c])
442 };
443 assert!(!reader.admits(&remote_write), "a remote write is network-admin, not reader");
444
445 assert!(!level(default_levels(), "paranoid").admits(&fetch), "paranoid blocks all network");
447 }
448
449 #[test]
450 fn write_local_writes_the_worktree_but_not_installs_or_mass_ops() {
451 let levels = default_levels();
452 let write_local = level(levels, "editor");
453
454 let touch = {
455 let mut c = Capability::new(Operation::Create);
456 c.locus.local = LocalLocus::Worktree;
457 Profile::of(vec![c])
458 };
459 assert!(write_local.admits(&touch), "touch build/out");
460 assert!(write_local.admits(&observe_at(LocalLocus::Worktree)));
462
463 let install = {
464 let mut c = Capability::new(Operation::Create);
465 c.locus.local = LocalLocus::Worktree;
466 c.persistence.level = PersistenceLevel::Installing;
467 Profile::of(vec![c])
468 };
469 assert!(!write_local.admits(&install), "installing is above write-local");
470 }
471
472 #[test]
473 fn developer_deletes_within_the_worktree_but_not_beyond_it() {
474 let levels = default_levels();
475 let (write_local, developer) = (level(levels, "editor"), level(levels, "developer"));
476
477 let destroy_at = |local| {
478 let mut c = Capability::new(Operation::Destroy);
479 c.locus.local = local;
480 c.scale = Scale::Unbounded; c.reversibility = Reversibility::Effortful;
482 Profile::of(vec![c])
483 };
484 assert!(!write_local.admits(&destroy_at(LocalLocus::Worktree)), "rm waits for developer");
486 assert!(developer.admits(&destroy_at(LocalLocus::Worktree)), "rm -rf ./node_modules");
487 assert!(!developer.admits(&destroy_at(LocalLocus::WorktreeTrusted)), "rm -rf .git");
489 assert!(!developer.admits(&destroy_at(LocalLocus::User)), "rm -rf ~");
490 assert!(!developer.admits(&destroy_at(LocalLocus::Machine)), "rm -rf /");
491
492 let overwrite = {
495 let mut c = Capability::new(Operation::Create);
496 c.locus.local = LocalLocus::Worktree;
497 c.reversibility = Reversibility::Recoverable;
498 c.persistence.level = PersistenceLevel::Data;
499 Profile::of(vec![c])
500 };
501 assert!(write_local.admits(&overwrite), "cp ./a ./b is write-local (create), not developer");
502 let touch = {
504 let mut c = Capability::new(Operation::Create);
505 c.locus.local = LocalLocus::Worktree;
506 Profile::of(vec![c])
507 };
508 assert!(developer.admits(&touch), "developer ⊇ write-local");
509 }
510
511 #[test]
519 fn developer_runs_worktree_code_but_not_foreign_or_system() {
520 let levels = default_levels();
521 let developer = level(levels, "developer");
522 let exec_at = |local| {
523 let mut c = Capability::new(Operation::Execute);
524 c.locus.local = local;
525 c.execution.trust = ExecutionTrust::CallerFile; Profile::of(vec![c])
527 };
528 for local in [LocalLocus::SandboxScope, LocalLocus::Worktree, LocalLocus::Adjacent, LocalLocus::WorktreeTrusted] {
530 assert!(developer.admits(&exec_at(local)), "developer runs worktree-scope code: {local:?}");
531 }
532 assert!(!developer.admits(&exec_at(LocalLocus::Temp)), "bash /tmp/x.sh is foreign");
534 assert!(!developer.admits(&exec_at(LocalLocus::Process)), "inline `python -c` is below the band");
535 assert!(!developer.admits(&exec_at(LocalLocus::User)), "~/x.sh waits for a higher level");
537 assert!(!developer.admits(&exec_at(LocalLocus::Machine)), "/usr/local/bin/x waits for a higher level");
538 }
539
540 #[test]
541 fn the_ladder_nests() {
542 let levels = default_levels();
543 let (inert, read, write) = (level(levels, "paranoid"), level(levels, "reader"), level(levels, "editor"));
544 for local in [LocalLocus::Process, LocalLocus::Temp] {
546 let p = observe_at(local);
547 assert!(inert.admits(&p) && read.admits(&p) && write.admits(&p));
548 }
549 }
550
551 #[test]
556 fn the_admin_flavors_flex_disjoint_regions_and_only_yolo_is_irreversible() {
557 let levels = default_levels();
558 let developer = level(levels, "developer");
559 let local_admin = level(levels, "local-admin");
560 let network_admin = level(levels, "network-admin");
561 let yolo = level(levels, "yolo");
562
563 let sudo = {
565 let mut c = Capability::new(Operation::Mutate);
566 c.locus.local = LocalLocus::Machine;
567 c.authority = Authority::Root;
568 Profile::of(vec![c])
569 };
570 assert!(!developer.admits(&sudo), "sudo is above developer");
571 assert!(local_admin.admits(&sudo), "local-admin runs this machine");
572 assert!(!network_admin.admits(&sudo), "network-admin never sudo's the box");
573
574 let remote = {
576 let mut c = Capability::new(Operation::Mutate);
577 c.locus.remote = RemoteReach::Arbitrary;
578 c.network.direction = NetDirection::Outbound;
579 Profile::of(vec![c])
580 };
581 assert!(!developer.admits(&remote), "remote reach is above developer");
582 assert!(network_admin.admits(&remote), "network-admin operates remotes");
583 assert!(!local_admin.admits(&remote), "local-admin never reaches the network");
584
585 let irreversible = |local, remote| {
587 let mut c = Capability::new(Operation::Destroy);
588 c.locus.local = local;
589 c.locus.remote = remote;
590 c.reversibility = Reversibility::Irreversible;
591 Profile::of(vec![c])
592 };
593 let mkfs = irreversible(LocalLocus::Device, RemoteReach::None); let tf_destroy = irreversible(LocalLocus::Process, RemoteReach::Fixed); assert!(!local_admin.admits(&mkfs), "mkfs (irreversible) is above local-admin");
596 assert!(!network_admin.admits(&tf_destroy), "terraform destroy (irreversible) is above network-admin");
597 assert!(yolo.admits(&mkfs) && yolo.admits(&tf_destroy), "irreversible destroy is reserved for yolo");
598
599 let effortful_machine = {
601 let mut c = Capability::new(Operation::Destroy);
602 c.locus.local = LocalLocus::Machine;
603 c.scale = Scale::Unbounded;
604 c.reversibility = Reversibility::Effortful;
605 Profile::of(vec![c])
606 };
607 assert!(local_admin.admits(&effortful_machine), "sudo rm -rf /var (recoverable) is local-admin");
608 }
609
610 #[test]
615 fn yolo_denies_only_unbounded_irreversible_destroy() {
616 let levels = default_levels();
617 let yolo = level(levels, "yolo");
618
619 let destroy = |scale, rev| {
620 let mut c = Capability::new(Operation::Destroy);
621 c.scale = scale;
622 c.reversibility = rev;
623 c.locus.local = LocalLocus::Machine;
624 Profile::of(vec![c])
625 };
626 assert!(!yolo.admits(&destroy(Scale::Unbounded, Reversibility::Irreversible)), "rm -rf / is denied even at yolo",);
628 assert!(yolo.admits(&destroy(Scale::Bounded, Reversibility::Irreversible)), "terraform destroy (bounded)");
630 assert!(yolo.admits(&destroy(Scale::Single, Reversibility::Irreversible)), "mkfs (single device)");
631 assert!(yolo.admits(&destroy(Scale::Unbounded, Reversibility::Effortful)), "rm -rf ./x (recoverable)");
632 let wild = {
634 let mut c = Capability::new(Operation::Execute);
635 c.execution.trust = ExecutionTrust::NetworkSourced;
636 c.locus.local = LocalLocus::Kernel;
637 Profile::of(vec![c])
638 };
639 assert!(yolo.admits(&wild), "yolo still admits everything but the catastrophe corner");
640 }
641
642 #[test]
643 fn unknown_term_is_a_compile_error() {
644 let src = r#"
645 [level.x]
646 [[level.x.allow]]
647 scale = "<= enormous"
648 "#;
649 let err = build_level_set(src).unwrap_err();
650 assert!(err.contains("enormous"), "{err}");
651 }
652
653 #[test]
654 fn unknown_facet_key_is_a_compile_error() {
655 let src = r#"
656 [level.x]
657 [[level.x.allow]]
658 operashun = ["observe"]
659 "#;
660 assert!(build_level_set(src).is_err());
661 }
662
663 #[test]
664 fn deny_on_an_extending_level_is_rejected() {
665 let src = r#"
666 [level.base]
667 [[level.base.allow]]
668 operation = ["observe"]
669
670 [level.child]
671 extends = "base"
672 [[level.child.deny]]
673 operation = ["destroy"]
674 "#;
675 let err = build_level_set(src).unwrap_err();
676 assert!(err.contains("R27"), "{err}");
677 }
678
679 #[test]
680 fn scalar_facet_values_parse() {
681 let src = r#"
683 [level.x]
684 [[level.x.allow]]
685 operation = "observe"
686 locus = { binding = "pinned" }
687 "#;
688 let levels = build_level_set(src).expect("compiles");
689 let c = &level(&levels, "x").allow[0];
690 assert_eq!(c.operation, Some(vec![Operation::Observe]));
691 assert_eq!(c.remote_binding, Some(vec![RemoteBinding::Pinned]));
692 }
693
694 #[test]
695 fn a_mutual_extends_cycle_is_a_compile_error() {
696 let src = r#"
697 [level.a]
698 extends = "b"
699 [level.b]
700 extends = "a"
701 "#;
702 assert!(build_level_set(src).is_err());
703 }
704
705 #[test]
706 fn missing_base_is_a_compile_error() {
707 let src = r#"
708 [level.child]
709 extends = "ghost"
710 [[level.child.allow]]
711 operation = ["observe"]
712 "#;
713 assert!(build_level_set(src).is_err());
714 }
715
716 #[test]
717 fn ordinal_operators_parse() {
718 let src = r#"
719 [level.x]
720 [[level.x.allow]]
721 scale = ">= bounded"
722 reversibility = "<= recoverable"
723 authority = "root"
724 "#;
725 let levels = build_level_set(src).expect("compiles");
726 let clause = &level(&levels, "x").allow[0];
727 assert_eq!(clause.scale, Some(OrdBound::at_least(Scale::Bounded)));
728 assert_eq!(clause.reversibility, Some(OrdBound::at_most(Reversibility::Recoverable)));
729 assert_eq!(clause.authority, Some(OrdBound::exactly(Authority::Root)));
730 }
731
732 use crate::engine::testgen::{arb_capability, arb_profile, lowered_variants, predecessor};
740 use proptest::prelude::*;
741
742 fn assert_monotone_from(lvl: &Level, boundary: Capability) {
743 assert!(lvl.admits(&Profile::of(vec![boundary.clone()])), "{}: boundary capability should be admitted", lvl.name,);
744 for lowered in lowered_variants(&boundary) {
745 assert!(
746 lvl.admits(&Profile::of(vec![lowered.clone()])),
747 "{}: admitted a boundary cap but denied it after lowering one facet:\n {:?}\n {:?}",
748 lvl.name,
749 boundary,
750 lowered,
751 );
752 }
753 }
754
755 #[test]
756 fn authored_levels_are_monotone_at_their_ceilings() {
757 let levels = default_levels();
758
759 let mut inert_cap = Capability::new(Operation::Observe);
760 inert_cap.locus.local = LocalLocus::Temp;
761 inert_cap.disclosure.audience = DisclosureAudience::LocalProcess;
762 inert_cap.execution.trust = ExecutionTrust::SelfCode;
763 assert_monotone_from(level(levels, "paranoid"), inert_cap);
764
765 let mut read_cap = Capability::new(Operation::Observe);
766 read_cap.locus.local = LocalLocus::WorktreeTrusted;
767 read_cap.secret.level = SecretLevel::UsesAmbient;
768 read_cap.network.direction = NetDirection::Loopback;
769 read_cap.disclosure.audience = DisclosureAudience::LocalProcess;
770 read_cap.execution.trust = ExecutionTrust::SelfCode;
771 assert_monotone_from(level(levels, "reader"), read_cap);
772
773 let mut write_cap = Capability::new(Operation::Mutate);
774 write_cap.locus.local = LocalLocus::Worktree;
775 write_cap.scale = Scale::Bounded;
776 write_cap.reversibility = Reversibility::Recoverable;
777 write_cap.persistence.level = PersistenceLevel::Data;
778 write_cap.secret.level = SecretLevel::UsesAmbient;
779 write_cap.disclosure.audience = DisclosureAudience::LocalProcess;
780 write_cap.execution.trust = ExecutionTrust::CallerInline;
781 assert_monotone_from(level(levels, "editor"), write_cap);
782 }
783
784 type LevelGap = (&'static str, fn(&Capability) -> bool);
786
787 proptest! {
796 #[test]
797 fn union_levels_admit_everything_but_their_declared_gap(cap in arb_capability()) {
798 let gaps: &[LevelGap] = &[
799 ("yolo", |c: &Capability| {
802 c.operation == Operation::Destroy
803 && c.reversibility == Reversibility::Irreversible
804 && c.scale == Scale::Unbounded
805 }),
806 ];
807 let levels = default_levels();
808 for (name, gap) in gaps {
809 let lvl = levels.iter().find(|l| &l.name == name).expect("level present");
810 let admitted = lvl.admits(&Profile::of(vec![cap.clone()]));
811 prop_assert_eq!(
812 admitted, !gap(&cap),
813 "level `{}`: capability {:?} admitted={} but intended_admit={}",
814 name, cap, admitted, !gap(&cap),
815 );
816 }
817 }
818 }
819
820 proptest! {
821 #[test]
824 fn authored_levels_are_facet_monotone(profile in arb_profile()) {
825 for lvl in default_levels() {
826 if !lvl.admits(&profile) {
827 continue;
828 }
829 for (i, cap) in profile.capabilities.iter().enumerate() {
830 for lowered in lowered_variants(cap) {
831 let mut lowered_profile = profile.clone();
832 lowered_profile.capabilities[i] = lowered;
833 prop_assert!(
834 lvl.admits(&lowered_profile),
835 "{} broke facet-monotonicity",
836 lvl.name,
837 );
838 }
839 }
840 }
841 }
842 }
843
844 #[test]
852 fn execute_locus_is_monotone_except_the_intended_origin_bands() {
853 let intended: &[&str] = &["developer", "network-admin"];
863 for lvl in default_levels() {
864 if intended.contains(&lvl.name.as_str()) {
865 continue;
866 }
867 let exec_at = |local| {
868 let mut c = Capability::new(Operation::Execute);
869 c.locus.local = local;
870 c.execution.trust = ExecutionTrust::CallerFile;
871 Profile::of(vec![c])
872 };
873 for local in LocalLocus::all() {
874 let Some(lower) = predecessor(*local) else { continue };
875 if lvl.admits(&exec_at(*local)) {
876 assert!(
877 lvl.admits(&exec_at(lower)),
878 "level `{}`: admits execute at {:?} but denies it one rung lower at {:?} — a \
879 non-monotone execute band. If deliberate, add `{}` to `intended` WITH an edge \
880 test; otherwise widen or remove the floor.",
881 lvl.name,
882 local,
883 lower,
884 lvl.name,
885 );
886 }
887 }
888 }
889 }
890
891 fn bound_str<T: FacetTerm>(b: OrdBound<T>) -> String {
898 match (b.min, b.max) {
899 (Some(lo), Some(hi)) if lo == hi => lo.as_str().to_string(),
900 (Some(lo), Some(hi)) => format!(">= {}, <= {}", lo.as_str(), hi.as_str()),
901 (None, Some(hi)) => format!("<= {}", hi.as_str()),
902 (Some(lo), None) => format!(">= {}", lo.as_str()),
903 (None, None) => panic!("empty bound has no representation"),
904 }
905 }
906
907 fn opt_bound_str<T: FacetTerm>(b: Option<OrdBound<T>>) -> Option<String> {
908 b.map(bound_str)
909 }
910
911 fn set_str<T: FacetTerm>(v: &[T]) -> StringOrVec {
912 StringOrVec::Many(v.iter().map(|t| t.as_str().to_string()).collect())
913 }
914
915 fn clause_to_toml(c: &Clause) -> TomlClause {
916 let locus =
917 (c.local_locus.is_some() || c.remote_reach.is_some() || c.remote_binding.is_some() || c.provenance.is_some()).then(|| {
918 TomlLocus {
919 local: opt_bound_str(c.local_locus),
920 remote: opt_bound_str(c.remote_reach),
921 binding: c.remote_binding.as_deref().map(set_str),
922 provenance: opt_bound_str(c.provenance),
923 }
924 });
925 let persistence =
926 (c.persistence_level.is_some() || c.trigger_escape.is_some() || c.trigger_kind.is_some()).then(|| TomlPersistence {
927 level: opt_bound_str(c.persistence_level),
928 trigger: (c.trigger_escape.is_some() || c.trigger_kind.is_some())
929 .then(|| TomlTrigger { escape: opt_bound_str(c.trigger_escape), kind: c.trigger_kind.as_deref().map(set_str) }),
930 });
931 let disclosure =
932 (c.disclosure_audience.is_some() || c.disclosure_channel.is_some() || c.disclosure_principal.is_some()).then(|| {
933 TomlDisclosure {
934 audience: opt_bound_str(c.disclosure_audience),
935 channel: c.disclosure_channel.as_deref().map(set_str),
936 principal: c.disclosure_principal.as_deref().map(set_str),
937 }
938 });
939 let secret = (c.secret_level.is_some() || c.secret_channel.is_some() || c.secret_principal.is_some()).then(|| TomlSecret {
940 level: opt_bound_str(c.secret_level),
941 channel: c.secret_channel.as_deref().map(set_str),
942 principal: c.secret_principal.as_deref().map(set_str),
943 });
944 let network = (c.net_direction.is_some() || c.net_destination.is_some() || c.net_payload.is_some()).then(|| TomlNetwork {
945 direction: opt_bound_str(c.net_direction),
946 destination: opt_bound_str(c.net_destination),
947 payload: opt_bound_str(c.net_payload),
948 });
949 let supply_chain = (c.supply_source.is_some() || c.pinning.is_some() || c.exec_surface.is_some()).then(|| TomlSupplyChain {
950 source: c.supply_source.as_deref().map(set_str),
951 pinning: opt_bound_str(c.pinning),
952 exec_surface: c.exec_surface.as_deref().map(set_str),
953 });
954 TomlClause {
955 operation: c.operation.as_deref().map(set_str),
956 locus,
957 scale: opt_bound_str(c.scale),
958 retrieval: opt_bound_str(c.retrieval),
959 authority: opt_bound_str(c.authority),
960 isolation: opt_bound_str(c.isolation),
961 reversibility: opt_bound_str(c.reversibility),
962 persistence,
963 disclosure,
964 secret,
965 network,
966 execution: opt_bound_str(c.execution_trust),
967 supply_chain,
968 cost: opt_bound_str(c.cost),
969 }
970 }
971
972 fn round_trip(levels: &[Level]) -> Vec<Level> {
973 let level = levels
974 .iter()
975 .map(|l| {
976 let tl = TomlLevel {
977 extends: None,
978 allow: l.allow.iter().map(clause_to_toml).collect(),
979 deny: l.deny.iter().map(clause_to_toml).collect(),
980 };
981 (l.name.clone(), tl)
982 })
983 .collect();
984 let source = toml::to_string(&TomlLevelSet { level }).expect("serialize");
985 build_level_set(&source).expect("re-parse serialized levels")
986 }
987
988 fn assert_round_trips(levels: &[Level]) {
989 let round = round_trip(levels);
990 for original in levels {
991 let back = round.iter().find(|l| l.name == original.name).expect("level survives");
992 assert_eq!(original.allow, back.allow, "{} allow clauses", original.name);
993 assert_eq!(original.deny, back.deny, "{} deny clauses", original.name);
994 }
995 }
996
997 #[test]
998 fn authored_levels_round_trip() {
999 assert_round_trips(default_levels());
1000 }
1001
1002 #[test]
1003 fn every_facet_round_trips() {
1004 let src = r#"
1006 [level.sink]
1007 [[level.sink.allow]]
1008 operation = ["observe", "create", "destroy"]
1009 locus = { local = "<= machine", remote = "<= fixed", binding = ["pinned", "ambient"] }
1010 scale = "<= bounded"
1011 authority = "<= root"
1012 isolation = "<= vm"
1013 reversibility = "<= effortful"
1014 persistence = { level = "<= installing", trigger = { escape = "<= boot", kind = ["clock", "event"] } }
1015 disclosure = { audience = "<= public", channel = ["filesystem", "network"], principal = ["own"] }
1016 secret = { level = ">= reads", channel = ["credential-store"], principal = ["cross"] }
1017 network = { direction = "<= outbound", destination = "<= arbitrary", payload = "<= sends-host-data" }
1018 execution = "<= network-sourced"
1019 supply_chain = { source = ["public-registry", "signed-repo"], pinning = ">= version", exec_surface = ["build-script", "install-hook"] }
1020 cost = "<= quota"
1021 [[level.sink.deny]]
1022 operation = ["destroy"]
1023 reversibility = ">= irreversible"
1024 "#;
1025 let levels = build_level_set(src).expect("compiles");
1026 assert_round_trips(&levels);
1027 }
1028}