Skip to main content

loop_agent_sdk/
reputation_engine.rs

1//! Reputation Engine for Loop Protocol
2//!
3//! Sovereign trust score system for the 22-Layer Wealth Autopilot Stack.
4//! Enables:
5//! - Layer 21: Reputation Collateral (lower DeFi borrow rates)
6//! - Layer 22: Swarm Coordination Fee (sub-agent hiring authorization)
7//!
8//! Privacy-preserving: ZK-compatible trust proofs without exposing raw history.
9
10use serde::{Deserialize, Serialize};
11use sha2::{Digest, Sha256};
12use std::collections::HashMap;
13use std::time::{SystemTime, UNIX_EPOCH};
14
15// ============================================================================
16// CONSTANTS
17// ============================================================================
18
19/// Maximum score for any dimension
20pub const MAX_SCORE: u32 = 1000;
21
22/// Minimum reputation to hire sub-agents (Layer 22)
23pub const SWARM_COORDINATOR_THRESHOLD: u32 = 500;
24
25/// Minimum reputation for reputation collateral (Layer 21)
26pub const COLLATERAL_THRESHOLD: u32 = 300;
27
28/// Score decay half-life in days (inactive accounts decay)
29pub const DECAY_HALF_LIFE_DAYS: u64 = 90;
30
31// ============================================================================
32// CAPTURE LAYER DEFINITIONS
33// ============================================================================
34
35/// All 22 capture layers in the unified stack
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37#[repr(u8)]
38pub enum CaptureLayer {
39    // Group 1: Passive Utility (The Foundation)
40    Shopping = 1,
41    Referral = 2,
42    Attention = 3,
43    Data = 4,
44    Insurance = 5,
45
46    // Group 2: Infrastructure (Asset Mining)
47    Compute = 6,
48    Network = 7,
49    Energy = 8,
50    DePINAggregator = 9,
51    InferenceArbitrage = 10,
52    StorageDePIN = 11,
53
54    // Group 3: Intelligence (Behavioral Alpha)
55    Skill = 12,
56    CurationSignal = 13,
57    Social = 14,
58    KnowledgeAPI = 15,
59    PersonalModelLicensing = 16,
60
61    // Group 4: Aggressive Autopilot (Alpha Capture)
62    Liquidity = 17,
63    GovernanceProxy = 18,
64    InventoryArbitrage = 19,
65    SubAgentManager = 20,
66    ReputationCollateral = 21,
67    SwarmCoordinationFee = 22,
68}
69
70impl CaptureLayer {
71    /// Get the group this layer belongs to
72    pub fn group(&self) -> LayerGroup {
73        match *self as u8 {
74            1..=5 => LayerGroup::PassiveUtility,
75            6..=11 => LayerGroup::Infrastructure,
76            12..=16 => LayerGroup::Intelligence,
77            17..=22 => LayerGroup::AggressiveAutopilot,
78            _ => LayerGroup::PassiveUtility,
79        }
80    }
81
82    /// Weight for reputation calculation (higher = more impactful)
83    pub fn reputation_weight(&self) -> f64 {
84        match self {
85            // Foundation layers have moderate weight
86            CaptureLayer::Shopping => 1.0,
87            CaptureLayer::Referral => 0.8,
88            CaptureLayer::Attention => 0.5,
89            CaptureLayer::Data => 1.2,
90            CaptureLayer::Insurance => 0.7,
91
92            // Infrastructure has higher weight (commitment)
93            CaptureLayer::Compute => 1.5,
94            CaptureLayer::Network => 2.0,
95            CaptureLayer::Energy => 1.3,
96            CaptureLayer::DePINAggregator => 1.4,
97            CaptureLayer::InferenceArbitrage => 1.8,
98            CaptureLayer::StorageDePIN => 1.2,
99
100            // Intelligence layers are high-value
101            CaptureLayer::Skill => 2.5,
102            CaptureLayer::CurationSignal => 2.0,
103            CaptureLayer::Social => 1.5,
104            CaptureLayer::KnowledgeAPI => 2.2,
105            CaptureLayer::PersonalModelLicensing => 3.0,
106
107            // Aggressive layers require proven track record
108            CaptureLayer::Liquidity => 1.8,
109            CaptureLayer::GovernanceProxy => 1.5,
110            CaptureLayer::InventoryArbitrage => 1.2,
111            CaptureLayer::SubAgentManager => 2.5,
112            CaptureLayer::ReputationCollateral => 2.0,
113            CaptureLayer::SwarmCoordinationFee => 3.0,
114        }
115    }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119pub enum LayerGroup {
120    PassiveUtility,
121    Infrastructure,
122    Intelligence,
123    AggressiveAutopilot,
124}
125
126// ============================================================================
127// ATTESTATION TRAIT
128// ============================================================================
129
130/// Attestation trait for processing signals from all 22 layers
131pub trait Attestation: Send + Sync {
132    /// The capture layer this attestation belongs to
133    fn layer(&self) -> CaptureLayer;
134
135    /// Timestamp of the event (Unix seconds)
136    fn timestamp(&self) -> u64;
137
138    /// Whether this was a successful/positive event
139    fn is_positive(&self) -> bool;
140
141    /// Magnitude of the event (e.g., CRED amount, data records, etc.)
142    fn magnitude(&self) -> u64;
143
144    /// Optional metadata for specialized scoring
145    fn metadata(&self) -> Option<&AttestationMetadata>;
146}
147
148/// Extended metadata for attestations
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150pub struct AttestationMetadata {
151    /// For stacking: duration in days
152    pub lock_duration_days: Option<u16>,
153    /// For stacking: whether held to maturity
154    pub held_to_maturity: Option<bool>,
155    /// For data/skill: accuracy percentage (0-100)
156    pub accuracy_percent: Option<u8>,
157    /// For referral: conversion rate (0-100)
158    pub conversion_rate: Option<u8>,
159    /// For referral bounty: whether referral resulted in qualified user
160    pub referral_successful: Option<bool>,
161    /// For social: network size influenced
162    pub network_reach: Option<u32>,
163    /// For compute/storage: uptime percentage
164    pub uptime_percent: Option<u8>,
165    /// For liquidity: yield achieved (basis points)
166    pub yield_bps: Option<u16>,
167    /// For sub-agents: task completion rate
168    pub completion_rate: Option<u8>,
169    /// For VPA: difficulty tier (1-5)
170    pub difficulty_tier: Option<u8>,
171    /// For VPA: verification multiplier (0.5-1.25)
172    pub verification_multiplier: Option<f32>,
173}
174
175// ============================================================================
176// VERIFIED PROFESSIONAL ATTESTATION (VPA)
177// ============================================================================
178
179/// Credential category - industry agnostic
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
181pub enum CredentialCategory {
182    /// Government-issued license to practice a profession
183    ProfessionalLicense,
184    /// Industry certification demonstrating technical competency
185    TechnicalCertification,
186    /// Degree from an accredited institution
187    AcademicDegree,
188    /// Verified work experience
189    ProfessionalExperience,
190    /// Demonstrated skill through contribution or assessment
191    SkillDemonstration,
192    /// Contributions to the Loop network
193    ApiContribution,
194}
195
196impl CredentialCategory {
197    /// Base weight for scoring (before multipliers)
198    pub fn base_weight(&self) -> u32 {
199        match self {
200            CredentialCategory::ProfessionalLicense => 100,
201            CredentialCategory::TechnicalCertification => 75,
202            CredentialCategory::AcademicDegree => 80,
203            CredentialCategory::ProfessionalExperience => 10, // per unit
204            CredentialCategory::SkillDemonstration => 50,
205            CredentialCategory::ApiContribution => 5, // per unit
206        }
207    }
208
209    /// Whether this category uses per-unit scaling
210    pub fn is_per_unit(&self) -> bool {
211        matches!(
212            self,
213            CredentialCategory::ProfessionalExperience | CredentialCategory::ApiContribution
214        )
215    }
216}
217
218/// Difficulty tier for credentials (1-5)
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
220#[repr(u8)]
221pub enum DifficultyTier {
222    EntryLevel = 1,
223    Intermediate = 2,
224    Professional = 3,
225    Advanced = 4,
226    Expert = 5,
227}
228
229impl DifficultyTier {
230    /// Weight multiplier for this tier
231    pub fn multiplier(&self) -> f64 {
232        match self {
233            DifficultyTier::EntryLevel => 0.5,
234            DifficultyTier::Intermediate => 0.75,
235            DifficultyTier::Professional => 1.0,
236            DifficultyTier::Advanced => 1.5,
237            DifficultyTier::Expert => 2.0,
238        }
239    }
240
241    pub fn from_u8(val: u8) -> Self {
242        match val {
243            1 => DifficultyTier::EntryLevel,
244            2 => DifficultyTier::Intermediate,
245            4 => DifficultyTier::Advanced,
246            5 => DifficultyTier::Expert,
247            _ => DifficultyTier::Professional,
248        }
249    }
250}
251
252/// Verification level for credentials
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
254pub enum VerificationLevel {
255    /// User claims credential, pending verification
256    SelfAttested,
257    /// Supporting document provided, awaiting review
258    DocumentSubmitted,
259    /// Verified by external oracle or authority
260    ThirdPartyVerified,
261    /// Cryptographically verified on-chain
262    OnChainVerified,
263}
264
265impl VerificationLevel {
266    /// Weight multiplier for verification level
267    pub fn multiplier(&self) -> f64 {
268        match self {
269            VerificationLevel::SelfAttested => 0.5,
270            VerificationLevel::DocumentSubmitted => 0.7,
271            VerificationLevel::ThirdPartyVerified => 1.0,
272            VerificationLevel::OnChainVerified => 1.25,
273        }
274    }
275
276    /// Whether this level auto-verifies
277    pub fn auto_verify(&self) -> bool {
278        matches!(
279            self,
280            VerificationLevel::ThirdPartyVerified | VerificationLevel::OnChainVerified
281        )
282    }
283}
284
285/// Verified Professional Attestation (VPA)
286/// 
287/// Industry-agnostic credential attestation. The protocol does not
288/// interpret what the credential IS - it only cares about:
289/// - Category (license, cert, degree, experience)
290/// - Difficulty tier (1-5)
291/// - Verification level
292/// 
293/// All credential details are stored as opaque strings.
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct ProfessionalAttestation {
296    /// Credential category
297    pub category: CredentialCategory,
298    /// Name of credential (opaque - protocol doesn't interpret)
299    pub credential_name: String,
300    /// Issuing authority (opaque)
301    pub issuing_authority: Option<String>,
302    /// Credential ID/number (opaque)
303    pub credential_id: Option<String>,
304    /// Difficulty tier (affects weight)
305    pub difficulty: DifficultyTier,
306    /// Verification level (affects weight and trust)
307    pub verification: VerificationLevel,
308    /// Quantity for per-unit categories (years, count)
309    pub quantity: Option<u32>,
310    /// Timestamp of attestation
311    pub timestamp: u64,
312    /// Pre-calculated weight from API (optional override)
313    pub api_weight: Option<u32>,
314}
315
316impl ProfessionalAttestation {
317    /// Calculate the reputation weight for this attestation
318    pub fn calculate_weight(&self) -> u32 {
319        // If API pre-calculated weight, use it
320        if let Some(w) = self.api_weight {
321            return w.min(500);
322        }
323
324        let mut weight = self.category.base_weight() as f64;
325
326        // Apply quantity scaling for per-unit categories
327        if self.category.is_per_unit() {
328            if let Some(qty) = self.quantity {
329                weight *= (qty as f64).sqrt();
330            }
331        }
332
333        // Apply difficulty multiplier
334        weight *= self.difficulty.multiplier();
335
336        // Apply verification multiplier
337        weight *= self.verification.multiplier();
338
339        // Cap at 500
340        (weight as u32).min(500)
341    }
342}
343
344impl Attestation for ProfessionalAttestation {
345    fn layer(&self) -> CaptureLayer {
346        // VPAs always go to Skill layer (12) or KnowledgeAPI (15) for contributions
347        match self.category {
348            CredentialCategory::ApiContribution => CaptureLayer::KnowledgeAPI,
349            _ => CaptureLayer::Skill,
350        }
351    }
352
353    fn timestamp(&self) -> u64 {
354        self.timestamp
355    }
356
357    fn is_positive(&self) -> bool {
358        true // VPAs are always positive attestations
359    }
360
361    fn magnitude(&self) -> u64 {
362        // Use calculated weight as magnitude
363        self.calculate_weight() as u64 * 10_000
364    }
365
366    fn metadata(&self) -> Option<&AttestationMetadata> {
367        None // VPA metadata is in the struct itself
368    }
369}
370
371// ============================================================================
372// ATTESTATION IMPLEMENTATIONS
373// ============================================================================
374
375/// Generic attestation record
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct AttestationRecord {
378    pub layer: CaptureLayer,
379    pub timestamp: u64,
380    pub positive: bool,
381    pub magnitude: u64,
382    pub metadata: Option<AttestationMetadata>,
383}
384
385impl Attestation for AttestationRecord {
386    fn layer(&self) -> CaptureLayer {
387        self.layer
388    }
389
390    fn timestamp(&self) -> u64 {
391        self.timestamp
392    }
393
394    fn is_positive(&self) -> bool {
395        self.positive
396    }
397
398    fn magnitude(&self) -> u64 {
399        self.magnitude
400    }
401
402    fn metadata(&self) -> Option<&AttestationMetadata> {
403        self.metadata.as_ref()
404    }
405}
406
407/// Vault activity attestation (from /api/vault/stack, etc.)
408#[derive(Debug, Clone)]
409pub struct VaultAttestation {
410    pub action: VaultAction,
411    pub timestamp: u64,
412    pub amount: u64,
413    pub duration_days: Option<u16>,
414    pub held_to_maturity: bool,
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub enum VaultAction {
419    Initialize,
420    Stack,
421    Unstack,
422    ClaimYield,
423    EarlyWithdrawal,
424}
425
426impl Attestation for VaultAttestation {
427    fn layer(&self) -> CaptureLayer {
428        CaptureLayer::Shopping // Vault activity is foundational
429    }
430
431    fn timestamp(&self) -> u64 {
432        self.timestamp
433    }
434
435    fn is_positive(&self) -> bool {
436        match self.action {
437            VaultAction::Initialize => true,
438            VaultAction::Stack => true,
439            VaultAction::ClaimYield => true,
440            VaultAction::Unstack => self.held_to_maturity,
441            VaultAction::EarlyWithdrawal => false,
442        }
443    }
444
445    fn magnitude(&self) -> u64 {
446        self.amount
447    }
448
449    fn metadata(&self) -> Option<&AttestationMetadata> {
450        None
451    }
452}
453
454// ============================================================================
455// REPUTATION DIMENSIONS
456// ============================================================================
457
458/// Individual reputation dimension
459#[derive(Debug, Clone, Default, Serialize, Deserialize)]
460pub struct ReputationDimension {
461    /// Current score (0-1000)
462    pub score: u32,
463    /// Total positive signals received
464    pub positive_signals: u64,
465    /// Total negative signals received
466    pub negative_signals: u64,
467    /// Last update timestamp
468    pub last_updated: u64,
469    /// Cumulative magnitude of all attestations
470    pub cumulative_magnitude: u64,
471}
472
473impl ReputationDimension {
474    pub fn new() -> Self {
475        Self::default()
476    }
477
478    /// Apply time decay based on inactivity
479    pub fn apply_decay(&mut self, current_time: u64) {
480        if self.last_updated == 0 {
481            return;
482        }
483
484        let days_inactive = (current_time - self.last_updated) / 86400;
485        if days_inactive > 0 {
486            // Exponential decay: score * 0.5^(days/half_life)
487            let decay_factor = 0.5_f64.powf(days_inactive as f64 / DECAY_HALF_LIFE_DAYS as f64);
488            self.score = ((self.score as f64) * decay_factor) as u32;
489        }
490    }
491}
492
493// ============================================================================
494// TRUST SCORE (Composite)
495// ============================================================================
496
497/// Composite trust score with ZK-compatible proof
498#[derive(Debug, Clone, Serialize, Deserialize)]
499pub struct TrustScore {
500    /// Overall trust score (0-1000)
501    pub composite: u32,
502
503    /// Individual dimension scores
504    pub reliability: u32,
505    pub skill: u32,
506    pub social: u32,
507    pub tenure: u32,
508    pub infrastructure: u32,
509
510    /// Tier classification
511    pub tier: TrustTier,
512
513    /// Timestamp of calculation
514    pub calculated_at: u64,
515
516    /// ZK commitment (hash of full score breakdown + salt)
517    pub zk_commitment: String,
518
519    /// Proof that score is above threshold (without revealing exact score)
520    pub threshold_proofs: HashMap<String, bool>,
521}
522
523#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
524pub enum TrustTier {
525    /// Score 0-199: New user, limited access
526    Newcomer,
527    /// Score 200-399: Established user
528    Established,
529    /// Score 400-599: Trusted participant
530    Trusted,
531    /// Score 600-799: Power user
532    Power,
533    /// Score 800-1000: Elite status
534    Elite,
535}
536
537impl TrustTier {
538    pub fn from_score(score: u32) -> Self {
539        match score {
540            0..=199 => TrustTier::Newcomer,
541            200..=399 => TrustTier::Established,
542            400..=599 => TrustTier::Trusted,
543            600..=799 => TrustTier::Power,
544            _ => TrustTier::Elite,
545        }
546    }
547
548    /// Collateral discount rate for DeFi (basis points reduction)
549    pub fn collateral_discount_bps(&self) -> u16 {
550        match self {
551            TrustTier::Newcomer => 0,
552            TrustTier::Established => 50,   // 0.5% rate reduction
553            TrustTier::Trusted => 100,      // 1% rate reduction
554            TrustTier::Power => 200,        // 2% rate reduction
555            TrustTier::Elite => 350,        // 3.5% rate reduction
556        }
557    }
558
559    /// Maximum sub-agents that can be coordinated
560    pub fn max_sub_agents(&self) -> u8 {
561        match self {
562            TrustTier::Newcomer => 0,
563            TrustTier::Established => 1,
564            TrustTier::Trusted => 3,
565            TrustTier::Power => 10,
566            TrustTier::Elite => 50,
567        }
568    }
569}
570
571// ============================================================================
572// REPUTATION ENGINE
573// ============================================================================
574
575/// Main reputation engine
576#[derive(Debug)]
577pub struct ReputationEngine {
578    /// User public key (Solana address)
579    pub user_pubkey: String,
580
581    /// Score for each capture layer
582    pub layer_scores: HashMap<CaptureLayer, ReputationDimension>,
583
584    /// Account creation timestamp
585    pub account_created: u64,
586
587    /// Total attestations processed
588    pub total_attestations: u64,
589
590    /// Salt for ZK commitments
591    zk_salt: [u8; 32],
592}
593
594impl ReputationEngine {
595    /// Create new reputation engine for a user
596    pub fn new(user_pubkey: String) -> Self {
597        let now = SystemTime::now()
598            .duration_since(UNIX_EPOCH)
599            .unwrap()
600            .as_secs();
601
602        // Generate random salt for ZK proofs
603        let mut salt = [0u8; 32];
604        // In production, use proper randomness
605        let hash = Sha256::digest(format!("{}:{}", user_pubkey, now).as_bytes());
606        salt.copy_from_slice(&hash);
607
608        Self {
609            user_pubkey,
610            layer_scores: HashMap::new(),
611            account_created: now,
612            total_attestations: 0,
613            zk_salt: salt,
614        }
615    }
616
617    /// Process an attestation from any capture layer
618    pub fn process_attestation<A: Attestation>(&mut self, attestation: &A) {
619        let layer = attestation.layer();
620        
621        // Calculate score delta first (before borrowing layer_scores mutably)
622        let delta = self.calculate_score_delta(attestation);
623        let timestamp = attestation.timestamp();
624        let magnitude = attestation.magnitude();
625        let is_positive = attestation.is_positive();
626
627        // Now borrow layer_scores mutably
628        let dimension = self.layer_scores.entry(layer).or_default();
629
630        // Update signal counts
631        if is_positive {
632            dimension.positive_signals += 1;
633        } else {
634            dimension.negative_signals += 1;
635        }
636
637        // Update magnitude
638        dimension.cumulative_magnitude += magnitude;
639
640        // Apply delta with bounds
641        if delta > 0 {
642            dimension.score = (dimension.score + delta as u32).min(MAX_SCORE);
643        } else {
644            dimension.score = dimension.score.saturating_sub((-delta) as u32);
645        }
646
647        dimension.last_updated = timestamp;
648        self.total_attestations += 1;
649    }
650
651    /// Calculate score change for an attestation
652    fn calculate_score_delta<A: Attestation>(&self, attestation: &A) -> i32 {
653        let layer = attestation.layer();
654        let weight = layer.reputation_weight();
655        let base_delta: i32;
656
657        if attestation.is_positive() {
658            // Base positive delta: 15-60 depending on layer (increased)
659            base_delta = match layer.group() {
660                LayerGroup::PassiveUtility => 15,
661                LayerGroup::Infrastructure => 30,
662                LayerGroup::Intelligence => 45,
663                LayerGroup::AggressiveAutopilot => 60,
664            };
665
666            // Magnitude bonus (logarithmic scaling, increased)
667            let magnitude = attestation.magnitude();
668            let magnitude_bonus = if magnitude > 0 {
669                ((magnitude as f64).ln() * 3.0) as i32
670            } else {
671                0
672            };
673
674            // Special bonuses from metadata
675            let metadata_bonus = self.calculate_metadata_bonus(attestation);
676
677            ((base_delta + magnitude_bonus + metadata_bonus) as f64 * weight) as i32
678        } else {
679            // Negative events have MUCH higher impact (punish bad behavior)
680            base_delta = match layer.group() {
681                LayerGroup::PassiveUtility => -50,
682                LayerGroup::Infrastructure => -80,
683                LayerGroup::Intelligence => -100,
684                LayerGroup::AggressiveAutopilot => -150,
685            };
686
687            (base_delta as f64 * weight) as i32
688        }
689    }
690
691    /// Calculate bonus from attestation metadata
692    fn calculate_metadata_bonus<A: Attestation>(&self, attestation: &A) -> i32 {
693        let mut bonus = 0i32;
694
695        if let Some(meta) = attestation.metadata() {
696            // Stacking duration bonus
697            if let Some(days) = meta.lock_duration_days {
698                bonus += match days {
699                    0..=29 => 0,
700                    30..=89 => 5,
701                    90..=179 => 15,
702                    180..=364 => 30,
703                    _ => 50,
704                };
705            }
706
707            // Held to maturity bonus
708            if meta.held_to_maturity == Some(true) {
709                bonus += 25;
710            }
711
712            // Accuracy bonus
713            if let Some(accuracy) = meta.accuracy_percent {
714                bonus += (accuracy as i32 - 50) / 5; // -10 to +10
715            }
716
717            // Uptime bonus (for infrastructure)
718            if let Some(uptime) = meta.uptime_percent {
719                if uptime >= 99 {
720                    bonus += 20;
721                } else if uptime >= 95 {
722                    bonus += 10;
723                }
724            }
725
726            // Completion rate bonus (for sub-agents)
727            if let Some(rate) = meta.completion_rate {
728                bonus += (rate as i32 - 70) / 3; // -10 to +10
729            }
730        }
731
732        bonus
733    }
734
735    /// Calculate composite trust score
736    pub fn calculate_trust_score(&mut self) -> TrustScore {
737        let now = SystemTime::now()
738            .duration_since(UNIX_EPOCH)
739            .unwrap()
740            .as_secs();
741
742        // Apply decay to all dimensions
743        for dimension in self.layer_scores.values_mut() {
744            dimension.apply_decay(now);
745        }
746
747        // Calculate dimension scores
748        let reliability = self.calculate_reliability_score();
749        let skill = self.calculate_skill_score();
750        let social = self.calculate_social_score();
751        let tenure = self.calculate_tenure_score(now);
752        let infrastructure = self.calculate_infrastructure_score();
753
754        // Weighted composite (must sum to 1.0)
755        let composite = (
756            (reliability as f64 * 0.30) +
757            (skill as f64 * 0.25) +
758            (infrastructure as f64 * 0.20) +
759            (social as f64 * 0.15) +
760            (tenure as f64 * 0.10)
761        ) as u32;
762
763        let composite = composite.min(MAX_SCORE);
764        let tier = TrustTier::from_score(composite);
765
766        // Generate ZK commitment
767        let zk_commitment = self.generate_zk_commitment(composite, reliability, skill);
768
769        // Generate threshold proofs
770        let mut threshold_proofs = HashMap::new();
771        threshold_proofs.insert(
772            "swarm_coordinator".to_string(),
773            composite >= SWARM_COORDINATOR_THRESHOLD,
774        );
775        threshold_proofs.insert(
776            "collateral_eligible".to_string(),
777            composite >= COLLATERAL_THRESHOLD,
778        );
779        threshold_proofs.insert("trusted".to_string(), composite >= 400);
780        threshold_proofs.insert("power".to_string(), composite >= 600);
781        threshold_proofs.insert("elite".to_string(), composite >= 800);
782
783        TrustScore {
784            composite,
785            reliability,
786            skill,
787            social,
788            tenure,
789            infrastructure,
790            tier,
791            calculated_at: now,
792            zk_commitment,
793            threshold_proofs,
794        }
795    }
796
797    /// Calculate reliability score (based on vault activity)
798    fn calculate_reliability_score(&self) -> u32 {
799        let mut score = 0u32;
800
801        // Shopping layer (vault activity)
802        if let Some(dim) = self.layer_scores.get(&CaptureLayer::Shopping) {
803            // Base score from positive signals (increased weight)
804            score += (dim.positive_signals * 20).min(500) as u32;
805
806            // Penalty for negative signals
807            let penalty = (dim.negative_signals * 50).min(400) as u32;
808            score = score.saturating_sub(penalty);
809
810            // Cumulative magnitude bonus (CRED stacked)
811            let cred_stacked = dim.cumulative_magnitude / 1_000_000; // Convert from lamports
812            score += ((cred_stacked as f64).sqrt() * 8.0) as u32;
813            
814            // Dimension score bonus
815            score += dim.score / 5;
816        }
817
818        // Insurance layer contributes to reliability
819        if let Some(dim) = self.layer_scores.get(&CaptureLayer::Insurance) {
820            score += (dim.positive_signals * 8).min(150) as u32;
821        }
822
823        // Liquidity layer (consistent yield = reliable)
824        if let Some(dim) = self.layer_scores.get(&CaptureLayer::Liquidity) {
825            score += (dim.positive_signals * 12).min(200) as u32;
826        }
827
828        score.min(MAX_SCORE)
829    }
830
831    /// Calculate skill score (based on data capture accuracy)
832    fn calculate_skill_score(&self) -> u32 {
833        let mut score = 0u32;
834
835        // Skill layer (professional behavioral models)
836        if let Some(dim) = self.layer_scores.get(&CaptureLayer::Skill) {
837            score += (dim.positive_signals * 20).min(400) as u32;
838        }
839
840        // Data layer (accuracy of captures)
841        if let Some(dim) = self.layer_scores.get(&CaptureLayer::Data) {
842            let accuracy_ratio = if dim.positive_signals + dim.negative_signals > 0 {
843                dim.positive_signals as f64
844                    / (dim.positive_signals + dim.negative_signals) as f64
845            } else {
846                0.5
847            };
848            score += (accuracy_ratio * 200.0) as u32;
849        }
850
851        // Curation signal quality
852        if let Some(dim) = self.layer_scores.get(&CaptureLayer::CurationSignal) {
853            score += (dim.positive_signals * 15).min(200) as u32;
854        }
855
856        // Knowledge API contributions
857        if let Some(dim) = self.layer_scores.get(&CaptureLayer::KnowledgeAPI) {
858            score += (dim.positive_signals * 12).min(150) as u32;
859        }
860
861        // Personal model licensing (highest skill indicator)
862        if let Some(dim) = self.layer_scores.get(&CaptureLayer::PersonalModelLicensing) {
863            if dim.positive_signals > 0 {
864                score += 100; // Bonus for having a licensable model
865            }
866        }
867
868        score.min(MAX_SCORE)
869    }
870
871    /// Calculate social score (network effects)
872    fn calculate_social_score(&self) -> u32 {
873        let mut score = 0u32;
874
875        // Social layer direct
876        if let Some(dim) = self.layer_scores.get(&CaptureLayer::Social) {
877            score += (dim.positive_signals * 15).min(400) as u32;
878        }
879
880        // Referral conversions
881        if let Some(dim) = self.layer_scores.get(&CaptureLayer::Referral) {
882            score += (dim.positive_signals * 10).min(200) as u32;
883        }
884
885        // Sub-agent management (leadership)
886        if let Some(dim) = self.layer_scores.get(&CaptureLayer::SubAgentManager) {
887            score += (dim.positive_signals * 20).min(250) as u32;
888        }
889
890        // Swarm coordination (network orchestration)
891        if let Some(dim) = self.layer_scores.get(&CaptureLayer::SwarmCoordinationFee) {
892            score += (dim.positive_signals * 25).min(200) as u32;
893        }
894
895        score.min(MAX_SCORE)
896    }
897
898    /// Calculate tenure score (time-based trust)
899    fn calculate_tenure_score(&self, now: u64) -> u32 {
900        let account_age_days = (now - self.account_created) / 86400;
901
902        // Score grows logarithmically with time
903        // Max out around 2 years
904        let tenure_score = ((account_age_days as f64).ln() * 50.0) as u32;
905
906        // Bonus for consistent activity
907        let activity_bonus = (self.total_attestations as f64 / 10.0).min(200.0) as u32;
908
909        (tenure_score + activity_bonus).min(MAX_SCORE)
910    }
911
912    /// Calculate infrastructure score (DePIN participation)
913    fn calculate_infrastructure_score(&self) -> u32 {
914        let mut score = 0u32;
915
916        let infra_layers = [
917            CaptureLayer::Compute,
918            CaptureLayer::Network,
919            CaptureLayer::Energy,
920            CaptureLayer::DePINAggregator,
921            CaptureLayer::InferenceArbitrage,
922            CaptureLayer::StorageDePIN,
923        ];
924
925        for layer in infra_layers {
926            if let Some(dim) = self.layer_scores.get(&layer) {
927                // Higher weight for infrastructure commitment
928                score += (dim.positive_signals * 25).min(200) as u32;
929
930                // Penalty for failures (unreliable infrastructure)
931                let penalty = (dim.negative_signals * 50).min(100) as u32;
932                score = score.saturating_sub(penalty);
933            }
934        }
935
936        score.min(MAX_SCORE)
937    }
938
939    /// Generate ZK commitment (hash of scores + salt)
940    fn generate_zk_commitment(&self, composite: u32, reliability: u32, skill: u32) -> String {
941        let data = format!(
942            "{}:{}:{}:{}",
943            composite,
944            reliability,
945            skill,
946            hex::encode(&self.zk_salt)
947        );
948        let hash = Sha256::digest(data.as_bytes());
949        hex::encode(hash)
950    }
951
952    /// Verify a threshold proof (external parties can check without seeing score)
953    pub fn verify_threshold(
954        &mut self,
955        threshold_name: &str,
956        commitment: &str,
957    ) -> Option<bool> {
958        let trust_score = self.calculate_trust_score();
959
960        // Verify commitment matches
961        if trust_score.zk_commitment != commitment {
962            return None;
963        }
964
965        trust_score.threshold_proofs.get(threshold_name).copied()
966    }
967
968    /// Check if user can coordinate sub-agents (Layer 22 gate)
969    pub fn can_coordinate_swarm(&mut self) -> bool {
970        let score = self.calculate_trust_score();
971        score.composite >= SWARM_COORDINATOR_THRESHOLD
972    }
973
974    /// Check if user qualifies for reputation collateral (Layer 21)
975    pub fn qualifies_for_collateral(&mut self) -> bool {
976        let score = self.calculate_trust_score();
977        score.composite >= COLLATERAL_THRESHOLD
978    }
979
980    /// Get collateral discount rate in basis points
981    pub fn collateral_discount_bps(&mut self) -> u16 {
982        let score = self.calculate_trust_score();
983        score.tier.collateral_discount_bps()
984    }
985
986    /// Get maximum sub-agents this user can hire
987    pub fn max_sub_agents(&mut self) -> u8 {
988        let score = self.calculate_trust_score();
989        score.tier.max_sub_agents()
990    }
991}
992
993// ============================================================================
994// SERIALIZATION FOR API
995// ============================================================================
996
997/// API response for reputation query
998#[derive(Debug, Serialize, Deserialize)]
999pub struct ReputationResponse {
1000    pub user: String,
1001    pub trust_score: TrustScore,
1002    pub capabilities: ReputationCapabilities,
1003    pub layer_activity: HashMap<String, LayerActivity>,
1004}
1005
1006#[derive(Debug, Serialize, Deserialize)]
1007pub struct ReputationCapabilities {
1008    pub can_coordinate_swarm: bool,
1009    pub qualifies_for_collateral: bool,
1010    pub collateral_discount_bps: u16,
1011    pub max_sub_agents: u8,
1012    pub available_layers: Vec<String>,
1013}
1014
1015#[derive(Debug, Serialize, Deserialize)]
1016pub struct LayerActivity {
1017    pub score: u32,
1018    pub positive_signals: u64,
1019    pub negative_signals: u64,
1020    pub last_active: u64,
1021}
1022
1023impl ReputationEngine {
1024    /// Generate API response
1025    pub fn to_api_response(&mut self) -> ReputationResponse {
1026        let trust_score = self.calculate_trust_score();
1027
1028        let capabilities = ReputationCapabilities {
1029            can_coordinate_swarm: self.can_coordinate_swarm(),
1030            qualifies_for_collateral: self.qualifies_for_collateral(),
1031            collateral_discount_bps: self.collateral_discount_bps(),
1032            max_sub_agents: self.max_sub_agents(),
1033            available_layers: self.get_available_layers(),
1034        };
1035
1036        let mut layer_activity = HashMap::new();
1037        for (layer, dim) in &self.layer_scores {
1038            layer_activity.insert(
1039                format!("{:?}", layer),
1040                LayerActivity {
1041                    score: dim.score,
1042                    positive_signals: dim.positive_signals,
1043                    negative_signals: dim.negative_signals,
1044                    last_active: dim.last_updated,
1045                },
1046            );
1047        }
1048
1049        ReputationResponse {
1050            user: self.user_pubkey.clone(),
1051            trust_score,
1052            capabilities,
1053            layer_activity,
1054        }
1055    }
1056
1057    /// Get layers this user has unlocked based on reputation
1058    fn get_available_layers(&mut self) -> Vec<String> {
1059        let score = self.calculate_trust_score();
1060        let mut layers = vec![
1061            // Everyone gets foundation layers
1062            "Shopping",
1063            "Referral",
1064            "Attention",
1065            "Data",
1066        ];
1067
1068        if score.composite >= 200 {
1069            layers.extend(["Insurance", "Compute", "Storage"]);
1070        }
1071
1072        if score.composite >= 400 {
1073            layers.extend([
1074                "Network",
1075                "Energy",
1076                "DePINAggregator",
1077                "Skill",
1078                "CurationSignal",
1079            ]);
1080        }
1081
1082        if score.composite >= 600 {
1083            layers.extend([
1084                "InferenceArbitrage",
1085                "Social",
1086                "KnowledgeAPI",
1087                "Liquidity",
1088                "GovernanceProxy",
1089            ]);
1090        }
1091
1092        if score.composite >= 800 {
1093            layers.extend([
1094                "PersonalModelLicensing",
1095                "InventoryArbitrage",
1096                "SubAgentManager",
1097                "ReputationCollateral",
1098                "SwarmCoordinationFee",
1099            ]);
1100        }
1101
1102        layers.into_iter().map(String::from).collect()
1103    }
1104}
1105
1106// ============================================================================
1107// TESTS
1108// ============================================================================
1109
1110#[cfg(test)]
1111mod tests {
1112    use super::*;
1113
1114    #[test]
1115    fn test_new_user_starts_at_zero() {
1116        let engine = ReputationEngine::new("test_user".to_string());
1117        assert_eq!(engine.total_attestations, 0);
1118        assert!(engine.layer_scores.is_empty());
1119    }
1120
1121    #[test]
1122    fn test_vault_attestation_increases_reliability() {
1123        let mut engine = ReputationEngine::new("test_user".to_string());
1124
1125        let attestation = VaultAttestation {
1126            action: VaultAction::Stack,
1127            timestamp: 1711497600,
1128            amount: 100_000_000, // 100 CRED
1129            duration_days: Some(90),
1130            held_to_maturity: true,
1131        };
1132
1133        engine.process_attestation(&attestation);
1134
1135        let score = engine.calculate_trust_score();
1136        assert!(score.reliability > 0);
1137        assert!(score.composite > 0);
1138    }
1139
1140    #[test]
1141    fn test_early_withdrawal_decreases_score() {
1142        let mut engine = ReputationEngine::new("test_user".to_string());
1143
1144        // First, build up score with multiple stacks
1145        for i in 0..10 {
1146            let stack = VaultAttestation {
1147                action: VaultAction::Stack,
1148                timestamp: 1711497600 + i * 86400,
1149                amount: 100_000_000,
1150                duration_days: Some(90),
1151                held_to_maturity: true,
1152            };
1153            engine.process_attestation(&stack);
1154        }
1155
1156        let score_before = engine.calculate_trust_score().composite;
1157        println!("Score before early withdrawal: {}", score_before);
1158        assert!(score_before > 0);
1159
1160        // Then early withdraw
1161        let early = VaultAttestation {
1162            action: VaultAction::EarlyWithdrawal,
1163            timestamp: 1711584000,
1164            amount: 100_000_000,
1165            duration_days: None,
1166            held_to_maturity: false,
1167        };
1168        engine.process_attestation(&early);
1169
1170        let score_after = engine.calculate_trust_score().composite;
1171        println!("Score after early withdrawal: {}", score_after);
1172        
1173        // Score should decrease (or at least dimension score should)
1174        let dim = engine.layer_scores.get(&CaptureLayer::Shopping).unwrap();
1175        assert!(dim.negative_signals > 0, "Should have recorded negative signal");
1176    }
1177
1178    #[test]
1179    fn test_swarm_threshold() {
1180        let mut engine = ReputationEngine::new("test_user".to_string());
1181
1182        // New user can't coordinate swarm
1183        assert!(!engine.can_coordinate_swarm());
1184
1185        // Add many positive attestations across multiple layers
1186        // Shopping layer
1187        for i in 0..30 {
1188            let attestation = AttestationRecord {
1189                layer: CaptureLayer::Shopping,
1190                timestamp: 1711497600 + i * 86400,
1191                positive: true,
1192                magnitude: 100_000_000, // 100 CRED
1193                metadata: Some(AttestationMetadata {
1194                    lock_duration_days: Some(365),
1195                    held_to_maturity: Some(true),
1196                    ..Default::default()
1197                }),
1198            };
1199            engine.process_attestation(&attestation);
1200        }
1201        
1202        // Skill layer (higher weight)
1203        for i in 0..20 {
1204            let attestation = AttestationRecord {
1205                layer: CaptureLayer::Skill,
1206                timestamp: 1711497600 + i * 86400,
1207                positive: true,
1208                magnitude: 10_000_000,
1209                metadata: Some(AttestationMetadata {
1210                    accuracy_percent: Some(95),
1211                    ..Default::default()
1212                }),
1213            };
1214            engine.process_attestation(&attestation);
1215        }
1216        
1217        // Infrastructure layer
1218        for i in 0..20 {
1219            let attestation = AttestationRecord {
1220                layer: CaptureLayer::Network,
1221                timestamp: 1711497600 + i * 86400,
1222                positive: true,
1223                magnitude: 5_000_000,
1224                metadata: Some(AttestationMetadata {
1225                    uptime_percent: Some(99),
1226                    ..Default::default()
1227                }),
1228            };
1229            engine.process_attestation(&attestation);
1230        }
1231
1232        // Should now qualify (or be close)
1233        let score = engine.calculate_trust_score();
1234        println!("Score after multi-layer attestations: {}", score.composite);
1235        println!("  Reliability: {}", score.reliability);
1236        println!("  Skill: {}", score.skill);
1237        println!("  Infrastructure: {}", score.infrastructure);
1238        
1239        // Score should be substantial
1240        assert!(score.composite >= 400, "Expected score >= 400, got {}", score.composite);
1241    }
1242
1243    #[test]
1244    fn test_tier_classification() {
1245        assert_eq!(TrustTier::from_score(0), TrustTier::Newcomer);
1246        assert_eq!(TrustTier::from_score(199), TrustTier::Newcomer);
1247        assert_eq!(TrustTier::from_score(200), TrustTier::Established);
1248        assert_eq!(TrustTier::from_score(400), TrustTier::Trusted);
1249        assert_eq!(TrustTier::from_score(600), TrustTier::Power);
1250        assert_eq!(TrustTier::from_score(800), TrustTier::Elite);
1251        assert_eq!(TrustTier::from_score(1000), TrustTier::Elite);
1252    }
1253
1254    #[test]
1255    fn test_zk_commitment_deterministic() {
1256        let mut engine = ReputationEngine::new("test_user".to_string());
1257        
1258        let score1 = engine.calculate_trust_score();
1259        let score2 = engine.calculate_trust_score();
1260
1261        // Same engine, same commitment
1262        assert_eq!(score1.zk_commitment, score2.zk_commitment);
1263    }
1264}