areev_loop_adapter/
lib.rs1mod governance;
20mod substrate;
21
22pub use governance::LoopGovernance;
23pub use substrate::{BorrowedSubstrate, AreevSubstrate};
24
25use std::time::{SystemTime, UNIX_EPOCH};
26
27pub fn scopes_for(authz: &areev_core::authz::AuthzSet) -> areev_loop::ScopeSet {
35 use areev_loop::Scope;
36 use areev_loop::LOOP_NS;
37 use areev_core::authz::Verb;
38 let mut scopes = Vec::new();
39 for (verb, scope) in [
40 (Verb::Read, Scope::Read),
41 (Verb::Write, Scope::Write),
42 (Verb::LoopReview, Scope::Review),
43 (Verb::LoopApply, Scope::Apply),
44 (Verb::Admin, Scope::Admin),
45 ] {
46 if authz.allows(verb, LOOP_NS) {
47 scopes.push(scope);
48 }
49 }
50 areev_loop::ScopeSet::of(&scopes)
51}
52
53pub fn observer_for_principal(actor: &str) -> areev_loop::ObserverType {
59 match areev_core::authz::observer_kind(actor) {
60 "agent" => areev_loop::ObserverType::Agent,
61 _ => areev_loop::ObserverType::Human,
62 }
63}
64
65pub fn now_ms() -> i64 {
76 if let Ok(v) = std::env::var("AREEV_LOOP_NOW_MS") {
77 return v
78 .trim()
79 .parse()
80 .unwrap_or_else(|_| panic!("AREEV_LOOP_NOW_MS is set but not epoch milliseconds: {v:?}"));
81 }
82 SystemTime::now()
83 .duration_since(UNIX_EPOCH)
84 .map(|d| d.as_millis() as i64)
85 .unwrap_or(0)
86}
87
88#[cfg(test)]
89mod authz_mapping_tests {
90 use super::*;
91 use areev_core::authz::{AuthzSet, Grant, Verb};
92
93 #[test]
94 fn owner_maps_to_every_scope() {
95 let scopes = scopes_for(&AuthzSet::owner("user:local"));
96 for s in [
97 areev_loop::Scope::Read,
98 areev_loop::Scope::Write,
99 areev_loop::Scope::Review,
100 areev_loop::Scope::Apply,
101 areev_loop::Scope::Admin,
102 ] {
103 assert!(scopes.has(s), "{s:?} missing for owner");
104 }
105 }
106
107 #[test]
108 fn loop_verbs_map_one_to_one_and_namespace_scoping_holds() {
109 let reviewer = AuthzSet::restricted(
111 "user:rev",
112 vec![Grant { verbs: vec![Verb::LoopReview], namespaces: vec!["*".into()] }],
113 );
114 let scopes = scopes_for(&reviewer);
115 assert!(scopes.has(areev_loop::Scope::Review));
116 for s in [
117 areev_loop::Scope::Read,
118 areev_loop::Scope::Write,
119 areev_loop::Scope::Apply,
120 areev_loop::Scope::Admin,
121 ] {
122 assert!(!scopes.has(s), "{s:?} must not be granted");
123 }
124
125 let elsewhere = AuthzSet::restricted(
128 "user:misscoped",
129 vec![Grant { verbs: vec![Verb::LoopReview], namespaces: vec!["caller".into()] }],
130 );
131 assert!(!scopes_for(&elsewhere).has(areev_loop::Scope::Review));
132 let on_loop_ns = AuthzSet::restricted(
133 "user:scoped",
134 vec![Grant {
135 verbs: vec![Verb::LoopReview],
136 namespaces: vec![areev_loop::LOOP_NS.into()],
137 }],
138 );
139 assert!(scopes_for(&on_loop_ns).has(areev_loop::Scope::Review));
140 }
141
142 #[test]
143 fn observer_derives_from_the_actor_label() {
144 use areev_loop::ObserverType;
145 assert_eq!(observer_for_principal("agent:mcp"), ObserverType::Agent);
146 assert_eq!(observer_for_principal("bot:sweeper"), ObserverType::Agent);
147 assert_eq!(observer_for_principal("job:retention"), ObserverType::Agent);
148 assert_eq!(observer_for_principal("engine:loop.llm/1"), ObserverType::Agent);
149 assert_eq!(observer_for_principal("user:anna"), ObserverType::Human);
150 assert_eq!(observer_for_principal("anna"), ObserverType::Human);
151 }
152}