//! Session state tracking for human mode. use crate::ac::AuthorizationContext; /// Tracks session state across the REPL loop. pub struct SessionState { pub ac_id: String, pub corpus_cid: String, pub principal: String, pub risk_level: String, pub started_at: chrono::DateTime, pub expires_at: Option>, pub governed_count: u32, pub free_count: u32, pub ungoverned_count: u32, pub denied_count: u32, } impl SessionState { pub fn from_ac(ac: &AuthorizationContext, corpus_cid: &str) -> Self { let principal = ac .principal .as_ref() .and_then(|p| p.did.clone()) .or_else(|| { ac.principal .as_ref() .and_then(|p| p.display_name.clone()) }) .unwrap_or_else(|| "unknown".to_string()); let expires_at = ac .expires_at .as_ref() .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok()) .map(|dt| dt.with_timezone(&chrono::Utc)); Self { ac_id: ac.context_id.clone(), corpus_cid: corpus_cid.to_string(), principal, risk_level: "standard".to_string(), // TODO: read from AC when broker embeds it started_at: chrono::Utc::now(), expires_at, governed_count: 0, free_count: 0, ungoverned_count: 0, denied_count: 0, } } /// Create a minimal session for ungoverned mode. pub fn ungoverned(corpus_cid: &str) -> Self { Self { ac_id: "ungoverned".to_string(), corpus_cid: corpus_cid.to_string(), principal: whoami(), risk_level: "ungoverned".to_string(), started_at: chrono::Utc::now(), expires_at: None, governed_count: 0, free_count: 0, ungoverned_count: 0, denied_count: 0, } } pub fn minutes_remaining(&self) -> i64 { match &self.expires_at { Some(exp) => (*exp - chrono::Utc::now()).num_minutes(), None => i64::MAX, } } pub fn total_commands(&self) -> u32 { self.governed_count + self.free_count + self.ungoverned_count + self.denied_count } } fn whoami() -> String { std::env::var("USER") .or_else(|_| std::env::var("USERNAME")) .unwrap_or_else(|_| "operator".to_string()) }