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