mod governance;
mod substrate;
pub use governance::LoopGovernance;
pub use substrate::{loop_state_of, AreevSubstrate, BorrowedSubstrate};
use std::time::{SystemTime, UNIX_EPOCH};
pub fn scopes_for(authz: &areev_core::authz::AuthzSet) -> areev_loop::ScopeSet {
use areev_loop::Scope;
use areev_loop::LOOP_NS;
use areev_core::authz::Verb;
let mut scopes = Vec::new();
for (verb, scope) in [
(Verb::Read, Scope::Read),
(Verb::Write, Scope::Write),
(Verb::LoopReview, Scope::Review),
(Verb::LoopApply, Scope::Apply),
(Verb::Admin, Scope::Admin),
] {
if authz.allows(verb, LOOP_NS) {
scopes.push(scope);
}
}
areev_loop::ScopeSet::of(&scopes)
}
pub fn scopes_for_rec(
authz: &areev_core::authz::AuthzSet,
scope: &[String],
) -> areev_loop::ScopeSet {
use areev_core::authz::Verb;
use areev_loop::Scope;
use areev_loop::LOOP_NS;
let whole_queue = scopes_for(authz);
if scope.is_empty() {
return whole_queue;
}
let mut scopes = Vec::new();
for (verb, s) in [
(Verb::Read, Scope::Read),
(Verb::Write, Scope::Write),
(Verb::LoopReview, Scope::Review),
(Verb::LoopApply, Scope::Apply),
(Verb::Admin, Scope::Admin),
] {
if authz.allows(verb, LOOP_NS) || scope.iter().all(|ns| authz.allows(verb, ns)) {
scopes.push(s);
}
}
areev_loop::ScopeSet::of(&scopes)
}
pub fn covers_rec(authz: &areev_core::authz::AuthzSet, scope: &[String]) -> bool {
scopes_for_rec(authz, scope).has(areev_loop::Scope::Read)
}
pub fn visible_recommendations<S: areev_loop::OmsSubstrate>(
engine: &areev_loop::Engine,
sub: &S,
authz: &areev_core::authz::AuthzSet,
status: Option<areev_loop::RecStatus>,
) -> areev_loop::Result<Vec<areev_loop::Recommendation>> {
let all = engine.recommendations(sub, status)?;
Ok(all.into_iter().filter(|r| covers_rec(authz, &r.scope)).collect())
}
pub fn observer_for_principal(actor: &str) -> areev_loop::ObserverType {
match areev_core::authz::observer_kind(actor) {
"agent" => areev_loop::ObserverType::Agent,
_ => areev_loop::ObserverType::Human,
}
}
pub fn now_ms() -> i64 {
if let Ok(v) = std::env::var("AREEV_LOOP_NOW_MS") {
return v
.trim()
.parse()
.unwrap_or_else(|_| panic!("AREEV_LOOP_NOW_MS is set but not epoch milliseconds: {v:?}"));
}
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0)
}
#[cfg(test)]
mod authz_mapping_tests {
use super::*;
use areev_core::authz::{AuthzSet, Grant, Verb};
fn granted(principal: &str, verbs: Vec<Verb>, ns: &[&str]) -> AuthzSet {
AuthzSet::restricted(
principal,
vec![Grant {
verbs,
namespaces: ns.iter().map(|s| (*s).to_string()).collect(),
}],
)
}
#[test]
fn a_namespace_grant_covers_a_finding_derived_from_that_namespace() {
let amy = granted("user:amy", vec![Verb::Read, Verb::LoopReview], &["a"]);
let s = scopes_for_rec(&amy, &["a".into()]);
assert!(s.has(areev_loop::Scope::Read));
assert!(s.has(areev_loop::Scope::Review));
assert!(covers_rec(&amy, &["a".into()]));
}
#[test]
fn a_namespace_grant_does_not_cover_another_namespaces_finding() {
let amy = granted("user:amy", vec![Verb::Read, Verb::LoopReview], &["a"]);
assert!(!covers_rec(&amy, &["b".into()]));
let s = scopes_for_rec(&amy, &["b".into()]);
assert!(!s.has(areev_loop::Scope::Read));
assert!(!s.has(areev_loop::Scope::Review));
}
#[test]
fn coverage_needs_every_namespace_a_finding_was_derived_from() {
let amy = granted("user:amy", vec![Verb::Read], &["a"]);
assert!(!covers_rec(&amy, &["a".into(), "b".into()]));
let both = granted("user:both", vec![Verb::Read], &["a", "b"]);
assert!(covers_rec(&both, &["a".into(), "b".into()]));
}
#[test]
fn a_whole_queue_grant_still_means_the_whole_queue() {
let op = granted(
"user:op",
vec![Verb::Read, Verb::LoopReview],
&[areev_loop::LOOP_NS],
);
assert!(covers_rec(&op, &["a".into()]));
assert!(covers_rec(&op, &["a".into(), "b".into()]));
assert!(scopes_for_rec(&op, &["zzz".into()]).has(areev_loop::Scope::Review));
assert!(covers_rec(&AuthzSet::owner("user:local"), &["a".into()]));
}
#[test]
fn an_unscoped_finding_needs_a_whole_queue_grant() {
let amy = granted("user:amy", vec![Verb::Read, Verb::LoopReview], &["a"]);
assert!(!covers_rec(&amy, &[]));
let op = granted("user:op", vec![Verb::Read], &[areev_loop::LOOP_NS]);
assert!(covers_rec(&op, &[]));
assert!(covers_rec(&AuthzSet::owner("user:local"), &[]));
}
#[test]
fn coverage_is_per_verb() {
let amy = granted("user:amy", vec![Verb::Read], &["a"]);
let s = scopes_for_rec(&amy, &["a".into()]);
assert!(s.has(areev_loop::Scope::Read));
assert!(!s.has(areev_loop::Scope::Review));
assert!(!s.has(areev_loop::Scope::Apply));
}
#[test]
fn scope_normalization_is_order_and_duplicate_independent() {
use areev_loop::normalize_scope;
assert_eq!(
normalize_scope(&["b".into(), "a".into(), "a".into(), " ".into()]),
vec!["a".to_string(), "b".to_string()]
);
}
#[test]
fn owner_maps_to_every_scope() {
let scopes = scopes_for(&AuthzSet::owner("user:local"));
for s in [
areev_loop::Scope::Read,
areev_loop::Scope::Write,
areev_loop::Scope::Review,
areev_loop::Scope::Apply,
areev_loop::Scope::Admin,
] {
assert!(scopes.has(s), "{s:?} missing for owner");
}
}
#[test]
fn loop_verbs_map_one_to_one_and_namespace_scoping_holds() {
let reviewer = AuthzSet::restricted(
"user:rev",
vec![Grant { verbs: vec![Verb::LoopReview], namespaces: vec!["*".into()] }],
);
let scopes = scopes_for(&reviewer);
assert!(scopes.has(areev_loop::Scope::Review));
for s in [
areev_loop::Scope::Read,
areev_loop::Scope::Write,
areev_loop::Scope::Apply,
areev_loop::Scope::Admin,
] {
assert!(!scopes.has(s), "{s:?} must not be granted");
}
let elsewhere = AuthzSet::restricted(
"user:misscoped",
vec![Grant { verbs: vec![Verb::LoopReview], namespaces: vec!["caller".into()] }],
);
assert!(!scopes_for(&elsewhere).has(areev_loop::Scope::Review));
let on_loop_ns = AuthzSet::restricted(
"user:scoped",
vec![Grant {
verbs: vec![Verb::LoopReview],
namespaces: vec![areev_loop::LOOP_NS.into()],
}],
);
assert!(scopes_for(&on_loop_ns).has(areev_loop::Scope::Review));
}
#[test]
fn observer_derives_from_the_actor_label() {
use areev_loop::ObserverType;
assert_eq!(observer_for_principal("agent:mcp"), ObserverType::Agent);
assert_eq!(observer_for_principal("bot:sweeper"), ObserverType::Agent);
assert_eq!(observer_for_principal("job:retention"), ObserverType::Agent);
assert_eq!(observer_for_principal("engine:loop.llm/1"), ObserverType::Agent);
assert_eq!(observer_for_principal("user:anna"), ObserverType::Human);
assert_eq!(observer_for_principal("anna"), ObserverType::Human);
}
}