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 find_recommendation<'a>(
recs: &'a [areev_loop::Recommendation],
prefix: &str,
) -> Result<&'a areev_loop::Recommendation, String> {
let matches: Vec<&areev_loop::Recommendation> =
recs.iter().filter(|r| r.hash.starts_with(prefix)).collect();
match matches.as_slice() {
[] => Err(format!("no recommendation matches '{prefix}'")),
[one] => Ok(one),
many => Err(format!(
"'{prefix}' is ambiguous ({} matches) — use more characters",
many.len()
)),
}
}
pub fn recommendation_detail(r: &areev_loop::Recommendation) -> serde_json::Value {
let mut out = serde_json::json!({
"hash": r.hash,
"status": r.status.as_str(),
"severity": r.severity.as_str(),
"analyzer": r.analyzer,
"origin": r.origin,
"target_ref": r.target_ref,
"action_kind": r.action_kind,
"summary": r.summary.render(),
"destructive": r.destructive,
"rollbackable": r.rollbackable,
"evidence": r.evidence,
"dedup_key": r.dedup_key,
"confidence": r.confidence,
});
let o = out.as_object_mut().expect("json! object");
if let Some(pin) = &r.evalset_hash {
o.insert("evalset_hash".into(), serde_json::json!(pin));
}
if !r.near_duplicate_of.is_empty() {
o.insert("near_duplicate_of".into(), serde_json::json!(r.near_duplicate_of));
}
if let Some(replay) = &r.replay {
o.insert("replay".into(), replay.clone());
}
o.extend(proposal_fields(r));
if let Some(m) = r.metric.as_ref().and_then(|m| serde_json::to_value(m).ok()) {
o.insert("metric".into(), m);
}
if let Some(gd) = &r.guidance {
o.insert("guidance".into(), serde_json::Value::from(gd.clone()));
}
out
}
pub fn proposal_fields(r: &areev_loop::Recommendation) -> serde_json::Map<String, serde_json::Value> {
match serde_json::to_value(&r.proposal) {
Ok(serde_json::Value::Object(p)) => p,
_ => serde_json::Map::new(),
}
}
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);
}
}