use crate::trust::{
effect_requirement, is_net_effect, Dimension, Grant, GrantId, Level, TrustError,
};
use crate::types::{EffectArg, EffectKind};
use crate::EffectSet;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Authority {
pub grant: Grant,
pub egress: Vec<String>,
pub unscoped_net: bool,
pub fs_read: Vec<String>,
pub fs_write: Vec<String>,
pub effects: Vec<String>,
pub off_lattice: Vec<String>,
}
impl Authority {
pub fn grant_id(&self) -> GrantId {
self.grant.content_id()
}
pub fn minimality_witness(&self, effects: &EffectSet) -> Vec<MinimalityWitness> {
let mut out = Vec::new();
for dim in Dimension::ALL {
let level = self.grant.level(dim);
let Some(lowered_to) = next_level_down(dim, level) else {
continue; };
let mut probe = self.grant;
set_level(&mut probe, dim, lowered_to);
if let Err(TrustError::EffectNotPermitted { effect, .. }) =
probe.permits_effects(effects)
{
out.push(MinimalityWitness {
dimension: dim,
level,
lowered_to,
rejected_effect: effect,
});
}
}
out
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MinimalityWitness {
pub dimension: Dimension,
pub level: Level,
pub lowered_to: Level,
pub rejected_effect: String,
}
fn set_level(g: &mut Grant, dim: Dimension, level: Level) {
match dim {
Dimension::Filesystem => g.filesystem = level,
Dimension::Network => g.network = level,
Dimension::Exec => g.exec = level,
}
}
pub fn next_level_down(dim: Dimension, level: Level) -> Option<Level> {
let ladder = dim.levels();
let idx = ladder.iter().position(|l| l.rank() == level.rank())?;
idx.checked_sub(1).map(|i| ladder[i])
}
pub fn derive_from_effects(effects: &EffectSet) -> Result<Authority, TrustError> {
let (mut filesystem, mut network, mut exec) = (Level::None, Level::None, Level::None);
let mut egress = BTreeSet::new();
let mut fs_read = BTreeSet::new();
let mut fs_write = BTreeSet::new();
let mut kinds = BTreeSet::new();
let mut off_lattice = BTreeSet::new();
let mut unscoped_net = false;
for e in &effects.concrete {
kinds.insert(e.name.clone());
match effect_requirement(&e.name) {
Some((Dimension::Filesystem, required)) => filesystem = filesystem.join(required),
Some((Dimension::Network, required)) => network = network.join(required),
Some((Dimension::Exec, required)) => exec = exec.join(required),
None => {
off_lattice.insert(e.name.clone());
}
}
if is_net_effect(&e.name) {
match scope_arg(e) {
Some(host) => {
egress.insert(host.to_string());
}
None => unscoped_net = true,
}
}
match (e.name.as_str(), scope_arg(e)) {
("fs_read" | "fs_walk", Some(p)) => {
fs_read.insert(p.to_string());
}
("fs_write", Some(p)) => {
fs_write.insert(p.to_string());
}
_ => {}
}
}
Ok(Authority {
grant: Grant::try_new(filesystem, network, exec)?,
egress: egress.into_iter().collect(),
unscoped_net,
fs_read: fs_read.into_iter().collect(),
fs_write: fs_write.into_iter().collect(),
effects: kinds.into_iter().collect(),
off_lattice: off_lattice.into_iter().collect(),
})
}
fn scope_arg(e: &EffectKind) -> Option<&str> {
match &e.arg {
Some(EffectArg::Str(s)) => Some(s.as_str()),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Verdict {
Unchanged,
Narrowing,
Widening,
}
impl Verdict {
pub fn as_str(self) -> &'static str {
match self {
Verdict::Unchanged => "unchanged",
Verdict::Narrowing => "narrowing",
Verdict::Widening => "widening",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DimensionDelta {
pub dimension: Dimension,
pub from: Level,
pub to: Level,
}
impl DimensionDelta {
pub fn widens(&self) -> bool {
self.to.rank() > self.from.rank()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthorityDiff {
pub dimensions: Vec<DimensionDelta>,
pub egress_added: Vec<String>,
pub egress_removed: Vec<String>,
pub fs_read_added: Vec<String>,
pub fs_read_removed: Vec<String>,
pub fs_write_added: Vec<String>,
pub fs_write_removed: Vec<String>,
pub off_lattice_added: Vec<String>,
pub off_lattice_removed: Vec<String>,
pub lost_net_precision: bool,
pub verdict: Verdict,
}
impl AuthorityDiff {
pub fn is_empty(&self) -> bool {
self.verdict == Verdict::Unchanged
}
}
pub fn diff(base: &Authority, head: &Authority) -> AuthorityDiff {
let mut dimensions = Vec::new();
for dim in Dimension::ALL {
let (from, to) = (base.grant.level(dim), head.grant.level(dim));
if from.rank() != to.rank() {
dimensions.push(DimensionDelta {
dimension: dim,
from,
to,
});
}
}
let egress_added = added(&base.egress, &head.egress);
let egress_removed = added(&head.egress, &base.egress);
let fs_read_added = added(&base.fs_read, &head.fs_read);
let fs_read_removed = added(&head.fs_read, &base.fs_read);
let fs_write_added = added(&base.fs_write, &head.fs_write);
let fs_write_removed = added(&head.fs_write, &base.fs_write);
let off_lattice_added = added(&base.off_lattice, &head.off_lattice);
let off_lattice_removed = added(&head.off_lattice, &base.off_lattice);
let lost_net_precision = head.unscoped_net && !base.unscoped_net && !base.egress.is_empty();
let widens = dimensions.iter().any(DimensionDelta::widens)
|| !egress_added.is_empty()
|| !fs_read_added.is_empty()
|| !fs_write_added.is_empty()
|| !off_lattice_added.is_empty()
|| lost_net_precision;
let narrows = dimensions.iter().any(|d| !d.widens())
|| !egress_removed.is_empty()
|| !fs_read_removed.is_empty()
|| !fs_write_removed.is_empty()
|| !off_lattice_removed.is_empty()
|| (base.unscoped_net && !head.unscoped_net);
let verdict = if widens {
Verdict::Widening
} else if narrows {
Verdict::Narrowing
} else {
Verdict::Unchanged
};
AuthorityDiff {
dimensions,
egress_added,
egress_removed,
fs_read_added,
fs_read_removed,
fs_write_added,
fs_write_removed,
off_lattice_added,
off_lattice_removed,
lost_net_precision,
verdict,
}
}
fn added(a: &[String], b: &[String]) -> Vec<String> {
let have: BTreeSet<&str> = a.iter().map(String::as_str).collect();
b.iter()
.filter(|x| !have.contains(x.as_str()))
.cloned()
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn effects(rows: &[(&str, Option<&str>)]) -> EffectSet {
let mut set = EffectSet::empty();
for (name, arg) in rows {
let kind = match arg {
Some(v) => EffectKind::with_str(name.to_string(), v.to_string()),
None => EffectKind::bare(name.to_string()),
};
set.concrete.insert(kind);
}
set
}
#[test]
fn pure_code_needs_nothing() {
let e = effects(&[]);
let a = derive_from_effects(&e).unwrap();
assert_eq!(a.grant, Grant::bottom());
assert!(a.minimality_witness(&e).is_empty());
}
#[test]
fn off_lattice_effects_claim_no_dimension() {
let e = effects(&[("io", None), ("env", None), ("sql", None)]);
let a = derive_from_effects(&e).unwrap();
assert_eq!(a.grant, Grant::bottom());
assert_eq!(a.off_lattice, vec!["env", "io", "sql"]);
Grant::bottom().permits_effects(&e).expect("permitted");
}
#[test]
fn the_level_is_the_join_over_the_effects_touching_a_dimension() {
let read = derive_from_effects(&effects(&[("fs_read", None)])).unwrap();
assert_eq!(read.grant.filesystem, Level::ReadOnly);
let both = derive_from_effects(&effects(&[("fs_read", None), ("fs_write", None)])).unwrap();
assert_eq!(both.grant.filesystem, Level::ReadWrite);
}
#[test]
fn derived_grants_are_minimal() {
for rows in [
vec![],
vec![("io", None)],
vec![("fs_read", Some("/etc/hosts"))],
vec![("fs_write", Some("/tmp/out")), ("net", None)],
vec![("proc", None), ("llm_cloud", None)],
vec![("net", Some("api.example.com")), ("fs_walk", None)],
] {
let e = effects(&rows);
let a = derive_from_effects(&e).unwrap();
a.grant
.permits_effects(&e)
.expect("permits what it derived");
for dim in Dimension::ALL {
if let Some(lower) = next_level_down(dim, a.grant.level(dim)) {
let mut probe = a.grant;
set_level(&mut probe, dim, lower);
assert!(
probe.permits_effects(&e).is_err(),
"{dim} at {} is a rank too generous for {rows:?}",
a.grant.level(dim)
);
}
}
let non_none = Dimension::ALL
.iter()
.filter(|d| a.grant.level(**d) != Level::None)
.count();
assert_eq!(a.minimality_witness(&e).len(), non_none);
}
}
#[test]
fn scopes_are_collected_per_kind() {
let a = derive_from_effects(&effects(&[
("net", Some("a.example")),
("net", Some("b.example")),
("fs_read", Some("/in")),
("fs_write", Some("/out")),
]))
.unwrap();
assert_eq!(a.egress, vec!["a.example", "b.example"]);
assert_eq!(a.fs_read, vec!["/in"]);
assert_eq!(a.fs_write, vec!["/out"]);
assert!(!a.unscoped_net, "every net effect named a host");
}
#[test]
fn a_bare_net_is_recorded_as_unscoped() {
let a = derive_from_effects(&effects(&[("net", None)])).unwrap();
assert!(a.unscoped_net);
assert!(a.egress.is_empty());
}
#[test]
fn adding_a_host_is_a_widening_and_removing_one_is_a_narrowing() {
let one = derive_from_effects(&effects(&[("net", Some("a.example"))])).unwrap();
let two = derive_from_effects(&effects(&[
("net", Some("a.example")),
("net", Some("b.example")),
]))
.unwrap();
assert_eq!(diff(&one, &two).verdict, Verdict::Widening);
assert_eq!(diff(&two, &one).verdict, Verdict::Narrowing);
assert_eq!(diff(&one, &one).verdict, Verdict::Unchanged);
}
#[test]
fn losing_host_precision_is_a_widening_even_at_the_same_level() {
let scoped = derive_from_effects(&effects(&[("net", Some("a.example"))])).unwrap();
let bare =
derive_from_effects(&effects(&[("net", Some("a.example")), ("net", None)])).unwrap();
assert_eq!(scoped.grant, bare.grant, "same coarse level");
let d = diff(&scoped, &bare);
assert!(d.lost_net_precision);
assert_eq!(d.verdict, Verdict::Widening);
}
#[test]
fn no_network_to_a_bare_net_is_not_reported_twice() {
let none = derive_from_effects(&effects(&[])).unwrap();
let bare = derive_from_effects(&effects(&[("net", None)])).unwrap();
let d = diff(&none, &bare);
assert_eq!(d.verdict, Verdict::Widening);
assert!(
!d.lost_net_precision,
"the dimension widening already says it"
);
}
#[test]
fn a_widening_dominates_a_narrowing_in_the_same_change() {
let base = derive_from_effects(&effects(&[("fs_write", None)])).unwrap();
let head = derive_from_effects(&effects(&[("fs_read", None), ("net", None)])).unwrap();
let d = diff(&base, &head);
assert!(d.dimensions.iter().any(|x| !x.widens()), "filesystem fell");
assert!(d.dimensions.iter().any(|x| x.widens()), "network rose");
assert_eq!(d.verdict, Verdict::Widening);
}
#[test]
fn off_lattice_movement_is_reported_though_no_grant_would_catch_it() {
let base = derive_from_effects(&effects(&[])).unwrap();
let head = derive_from_effects(&effects(&[("env", None)])).unwrap();
let d = diff(&base, &head);
assert_eq!(d.verdict, Verdict::Widening);
assert_eq!(d.off_lattice_added, vec!["env"]);
assert!(d.dimensions.is_empty(), "no dimension moved");
}
}