use crate::errors::LitError;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TrustEvent {
Commit { hash: String },
Review { target_hash: String },
Merge { branch: String },
DelegationCompleted { task_id: String },
DelegationFailed { task_id: String },
Violation { description: String },
TokenRevoked { reason: String },
PeerVouch { voucher_did: String },
}
impl TrustEvent {
fn score_delta(&self) -> f64 {
match self {
TrustEvent::Commit { .. } => 1.0,
TrustEvent::Review { .. } => 2.0,
TrustEvent::Merge { .. } => 1.5,
TrustEvent::DelegationCompleted { .. } => 3.0,
TrustEvent::DelegationFailed { .. } => -2.0,
TrustEvent::Violation { .. } => -10.0,
TrustEvent::TokenRevoked { .. } => -5.0,
TrustEvent::PeerVouch { .. } => 2.5,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustScore {
pub did: String,
pub score: f64,
pub total_events: u64,
pub level: TrustLevel,
pub events: Vec<TrustEventRecord>,
pub updated: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustEventRecord {
pub event: TrustEvent,
pub timestamp: String,
pub score_delta: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum TrustLevel {
Untrusted,
Newcomer,
Contributor,
Trusted,
Maintainer,
}
impl std::fmt::Display for TrustLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TrustLevel::Untrusted => write!(f, "Untrusted"),
TrustLevel::Newcomer => write!(f, "Newcomer"),
TrustLevel::Contributor => write!(f, "Contributor"),
TrustLevel::Trusted => write!(f, "Trusted"),
TrustLevel::Maintainer => write!(f, "Maintainer"),
}
}
}
fn level_for_score(score: f64) -> TrustLevel {
if score < 10.0 {
TrustLevel::Untrusted
} else if score < 30.0 {
TrustLevel::Newcomer
} else if score < 60.0 {
TrustLevel::Contributor
} else if score < 85.0 {
TrustLevel::Trusted
} else {
TrustLevel::Maintainer
}
}
pub struct TrustEngine {
repo_root: std::path::PathBuf,
}
impl TrustEngine {
pub fn new(repo_root: &Path) -> Self {
TrustEngine {
repo_root: repo_root.to_path_buf(),
}
}
fn trust_dir(&self) -> std::path::PathBuf {
self.repo_root.join(".lit").join("trust")
}
fn score_path(&self, did: &str) -> std::path::PathBuf {
let safe_name: String = did
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect();
self.trust_dir().join(format!("{}.json", safe_name))
}
pub fn get_score(&self, did: &str) -> Result<TrustScore, LitError> {
let path = self.score_path(did);
if path.exists() {
let json = fs::read_to_string(&path)
.map_err(|e| LitError::io(format!("Failed to read trust score: {}", e)))?;
serde_json::from_str(&json)
.map_err(|e| LitError::general(format!("Failed to parse trust score: {}", e)))
} else {
Ok(TrustScore {
did: did.to_string(),
score: 25.0, total_events: 0,
level: TrustLevel::Newcomer,
events: Vec::new(),
updated: chrono::Utc::now().to_rfc3339(),
})
}
}
pub fn record_event(&self, did: &str, event: TrustEvent) -> Result<TrustScore, LitError> {
let mut score = self.get_score(did)?;
let delta = event.score_delta();
score.events.push(TrustEventRecord {
event,
timestamp: chrono::Utc::now().to_rfc3339(),
score_delta: delta,
});
score.score = (score.score + delta).clamp(0.0, 100.0);
score.total_events += 1;
score.level = level_for_score(score.score);
score.updated = chrono::Utc::now().to_rfc3339();
self.save_score(&score)?;
Ok(score)
}
pub fn list_agents(&self) -> Result<Vec<TrustScore>, LitError> {
let dir = self.trust_dir();
if !dir.exists() {
return Ok(Vec::new());
}
let mut scores = Vec::new();
for entry in fs::read_dir(&dir).map_err(|e| LitError::io(format!("IO error: {}", e)))? {
let entry = entry.map_err(|e| LitError::io(format!("IO error: {}", e)))?;
if entry.path().extension().is_some_and(|e| e == "json") {
if let Ok(json) = fs::read_to_string(entry.path()) {
if let Ok(score) = serde_json::from_str::<TrustScore>(&json) {
scores.push(score);
}
}
}
}
scores.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(scores)
}
fn save_score(&self, score: &TrustScore) -> Result<(), LitError> {
let dir = self.trust_dir();
fs::create_dir_all(&dir)
.map_err(|e| LitError::io(format!("Failed to create trust dir: {}", e)))?;
let path = self.score_path(&score.did);
let json = serde_json::to_string_pretty(score)
.map_err(|e| LitError::general(format!("Failed to serialize trust score: {}", e)))?;
fs::write(&path, json)
.map_err(|e| LitError::io(format!("Failed to write trust score: {}", e)))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
fn tmp_dir() -> PathBuf {
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("lit_trust_test_{}_{}", std::process::id(), n));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn test_trust_score_default() {
let dir = tmp_dir();
let engine = TrustEngine::new(&dir);
let score = engine.get_score("did:lit:agent1").unwrap();
assert_eq!(score.score, 25.0);
assert_eq!(score.level, TrustLevel::Newcomer);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_trust_score_events() {
let dir = tmp_dir();
let engine = TrustEngine::new(&dir);
let score = engine
.record_event("did:lit:x", TrustEvent::Commit { hash: "abc".into() })
.unwrap();
assert_eq!(score.score, 26.0);
let score = engine
.record_event(
"did:lit:x",
TrustEvent::Violation {
description: "force-push".into(),
},
)
.unwrap();
assert_eq!(score.score, 16.0);
assert_eq!(score.level, TrustLevel::Newcomer);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_trust_level_boundaries() {
assert_eq!(level_for_score(0.0), TrustLevel::Untrusted);
assert_eq!(level_for_score(25.0), TrustLevel::Newcomer);
assert_eq!(level_for_score(50.0), TrustLevel::Contributor);
assert_eq!(level_for_score(75.0), TrustLevel::Trusted);
assert_eq!(level_for_score(95.0), TrustLevel::Maintainer);
}
}