stemedb/crates/stemedb-storage/src/admission_store/model.rs
jordan d3a88585fe feat: Phase 6 UAT - Admission control, HLC recency, cluster coordination
This commit includes comprehensive work on Phase 6 features:

## Admission Control (Phase 6 admission middleware)
- AdmissionStore implementation backed by TrustRankStore
- PoW verification with tier-based difficulty computation
- Trust tier progression (Newcomer → Established → Trusted → Authority)
- API integration with admission status endpoints

## HLC Recency Lens (Phase 6C)
- HlcRecencyLens for distributed system ordering
- Hybrid logical clock integration with causality preservation

## Cluster Coordination (Phase 6C)
- Multi-node cluster tests (availability, partition tolerance)
- CRDT convergence tests for anti-entropy sync
- Gateway handler improvements

## Aphoria Code Linter (Phase 2A)
- RFC/OWASP corpus builders with network fetching and caching
- Concept hierarchy with auto-alias creation on conflict detection
- Multiple security extractors (TLS, JWT, CORS, secrets, rate limiting)

## Code Organization
- Split large files into modules to comply with 500-line limit
- Improved test organization with separate test modules
- Fixed rkyv serialization for EigenTrustState (AgentScore struct)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 00:43:37 -07:00

230 lines
7.4 KiB
Rust

//! 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);
}
}