//! Admission control data models. //! //! These types represent the admission status of an agent and the result of //! admission checks. They are designed to be easily serialized for API responses. use stemedb_core::types::{PowError, TrustTier, BASE_QUOTA_LIMIT}; /// Current admission status for an agent. /// /// This snapshot represents the agent's standing in the admission control system /// at a specific point in time. It includes all information needed by clients /// to understand their quotas and PoW requirements. #[derive(Debug, Clone, PartialEq)] pub struct AdmissionStatus { /// The agent's trust tier based on their reputation score. pub tier: TrustTier, /// The agent's current trust score (0.0 to 1.0). pub trust_score: f32, /// Total number of assertions made by this agent. pub assertions_count: u64, /// Required PoW difficulty in bits (0 = exempt). pub pow_difficulty: u8, /// Whether PoW is required for this agent's next submission. pub pow_required: bool, /// Base quota limit (before tier multiplier). pub base_quota_limit: u64, /// Effective quota limit after tier multiplier. pub effective_quota_limit: u64, /// Quota multiplier for this tier. pub quota_multiplier: f32, } impl AdmissionStatus { /// Create a new admission status from trust rank data. /// /// # Arguments /// * `trust_score` - Agent's trust score (0.0-1.0) /// * `assertions_count` - Number of assertions made /// * `pow_difficulty` - Required PoW difficulty in bits pub fn new(trust_score: f32, assertions_count: u64, pow_difficulty: u8) -> Self { let tier = TrustTier::from_score(trust_score); let pow_required = pow_difficulty > 0; let quota_multiplier = tier.quota_multiplier(); let base_quota_limit = BASE_QUOTA_LIMIT; let effective_quota_limit = tier.effective_quota_limit(); Self { tier, trust_score, assertions_count, pow_difficulty, pow_required, base_quota_limit, effective_quota_limit, quota_multiplier, } } /// Create a status for a new/unknown agent with default values. /// /// New agents start at: /// - Trust score: 0.5 (Verified tier) /// - Assertions: 0 /// - PoW difficulty: 16 bits (first 10 assertions) pub fn new_agent(initial_difficulty: u8) -> Self { Self::new(0.5, 0, initial_difficulty) } } /// Result of an admission check. /// /// This enum represents the three possible outcomes when checking admission: /// 1. Admitted - The agent can proceed (no PoW required, or valid PoW provided) /// 2. PowRequired - The agent must provide a PoW proof (HTTP 428) /// 3. PowFailed - The agent provided an invalid PoW proof (HTTP 428 with error) #[derive(Debug, Clone)] pub enum AdmissionStatusResult { /// Agent is admitted, can proceed with the request. Admitted(AdmissionStatus), /// Agent must provide proof-of-work to proceed. /// The status contains the required difficulty. PowRequired(AdmissionStatus), /// Agent provided an invalid proof-of-work. /// The status contains the required difficulty for retry. PowFailed { /// The agent's current status (for building retry response). status: AdmissionStatus, /// The specific error that caused verification to fail. error: PowError, }, } impl AdmissionStatusResult { /// Check if the agent is admitted. #[must_use] pub fn is_admitted(&self) -> bool { matches!(self, AdmissionStatusResult::Admitted(_)) } /// Check if proof-of-work is required. #[must_use] pub fn requires_pow(&self) -> bool { matches!(self, AdmissionStatusResult::PowRequired(_)) } /// Check if the proof-of-work verification failed. #[must_use] pub fn pow_failed(&self) -> bool { matches!(self, AdmissionStatusResult::PowFailed { .. }) } /// Get the admission status regardless of outcome. #[must_use] pub fn status(&self) -> &AdmissionStatus { match self { AdmissionStatusResult::Admitted(s) => s, AdmissionStatusResult::PowRequired(s) => s, AdmissionStatusResult::PowFailed { status, .. } => status, } } /// Get the PoW error if verification failed. #[must_use] pub fn pow_error(&self) -> Option<&PowError> { match self { AdmissionStatusResult::PowFailed { error, .. } => Some(error), _ => None, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_admission_status_new() { let status = AdmissionStatus::new(0.5, 10, 1); assert_eq!(status.tier, TrustTier::Verified); assert!((status.trust_score - 0.5).abs() < f32::EPSILON); assert_eq!(status.assertions_count, 10); assert_eq!(status.pow_difficulty, 1); assert!(status.pow_required); assert_eq!(status.base_quota_limit, 10_000); assert_eq!(status.effective_quota_limit, 10_000); assert!((status.quota_multiplier - 1.0).abs() < f32::EPSILON); } #[test] fn test_admission_status_new_agent() { let status = AdmissionStatus::new_agent(16); assert_eq!(status.tier, TrustTier::Verified); assert!((status.trust_score - 0.5).abs() < f32::EPSILON); assert_eq!(status.assertions_count, 0); assert_eq!(status.pow_difficulty, 16); assert!(status.pow_required); } #[test] fn test_admission_status_no_pow_required() { let status = AdmissionStatus::new(0.7, 100, 0); assert_eq!(status.tier, TrustTier::Trusted); assert!(!status.pow_required); assert_eq!(status.pow_difficulty, 0); assert_eq!(status.effective_quota_limit, 20_000); } #[test] fn test_admission_status_result_is_admitted() { let status = AdmissionStatus::new(0.5, 50, 0); let result = AdmissionStatusResult::Admitted(status); assert!(result.is_admitted()); assert!(!result.requires_pow()); assert!(!result.pow_failed()); } #[test] fn test_admission_status_result_pow_required() { let status = AdmissionStatus::new(0.3, 5, 16); let result = AdmissionStatusResult::PowRequired(status); assert!(!result.is_admitted()); assert!(result.requires_pow()); assert!(!result.pow_failed()); } #[test] fn test_admission_status_result_pow_failed() { let status = AdmissionStatus::new(0.3, 5, 16); let error = PowError::InsufficientDifficulty { required: 16, found: 8 }; let result = AdmissionStatusResult::PowFailed { status, error }; assert!(!result.is_admitted()); assert!(!result.requires_pow()); assert!(result.pow_failed()); assert!(matches!( result.pow_error(), Some(PowError::InsufficientDifficulty { required: 16, found: 8 }) )); } #[test] fn test_tier_quota_calculation() { // Untrusted: 0.1x = 1,000 let status = AdmissionStatus::new(0.1, 0, 16); assert_eq!(status.effective_quota_limit, 1_000); // Limited: 0.5x = 5,000 let status = AdmissionStatus::new(0.4, 0, 16); assert_eq!(status.effective_quota_limit, 5_000); // Authority: 10.0x = 100,000 let status = AdmissionStatus::new(0.95, 0, 0); assert_eq!(status.effective_quota_limit, 100_000); } }