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>
230 lines
7.4 KiB
Rust
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);
|
|
}
|
|
}
|