use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
pub enum Confidence {
Speculative,
Uncertain,
Medium,
High,
Certain,
}
impl Confidence {
pub fn as_str(&self) -> &'static str {
match self {
Confidence::Speculative => "speculative",
Confidence::Uncertain => "uncertain",
Confidence::Medium => "medium",
Confidence::High => "high",
Confidence::Certain => "certain",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"speculative" => Some(Confidence::Speculative),
"uncertain" => Some(Confidence::Uncertain),
"medium" => Some(Confidence::Medium),
"high" => Some(Confidence::High),
"certain" => Some(Confidence::Certain),
_ => None,
}
}
pub fn factor(&self) -> f64 {
match self {
Confidence::Speculative => 0.15,
Confidence::Uncertain => 0.4,
Confidence::Medium => 0.7,
Confidence::High => 0.9,
Confidence::Certain => 1.0,
}
}
pub fn search_boost(&self) -> f64 {
match self {
Confidence::Speculative => 0.5,
Confidence::Uncertain => 0.8,
Confidence::Medium => 1.0,
Confidence::High => 1.2,
Confidence::Certain => 1.5,
}
}
pub fn confirm(&self) -> (Confidence, bool) {
match self {
Confidence::Speculative => (Confidence::Uncertain, true),
Confidence::Uncertain => (Confidence::Medium, true),
Confidence::Medium => (Confidence::High, true),
Confidence::High => (Confidence::Certain, true),
Confidence::Certain => (Confidence::Certain, false), }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Provenance {
pub source: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub derived_from: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub confirmed_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub confirmed_at: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub auto_generated: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub needs_review: bool,
}
impl Provenance {
pub fn new(source: &str) -> Self {
Provenance {
source: source.to_string(),
derived_from: None,
confirmed_by: None,
confirmed_at: None,
auto_generated: false,
needs_review: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dispute {
pub id: String,
pub reason: String,
#[serde(rename = "disputedAt")]
pub disputed_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "resolvedAt")]
pub resolved_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
pub action: String,
pub by: String,
pub at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "mergedFrom")]
pub merged_from: Option<String>,
}
impl AuditEntry {
pub fn new(action: &str, by: &str, at: &str) -> Self {
AuditEntry {
action: action.to_string(),
by: by.to_string(),
at: at.to_string(),
detail: None,
merged_from: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustData {
pub confidence: Confidence,
pub provenance: Provenance,
#[serde(skip_serializing_if = "Option::is_none")]
pub disputes: Option<Vec<Dispute>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "auditLog")]
pub audit_log: Option<Vec<AuditEntry>>,
}
impl TrustData {
pub fn new(confidence: Confidence, source: &str) -> Self {
TrustData {
confidence,
provenance: Provenance::new(source),
disputes: None,
audit_log: None,
}
}
pub fn append_audit(&mut self, entry: AuditEntry) {
if let Some(ref mut log) = self.audit_log {
log.push(entry);
} else {
self.audit_log = Some(vec![entry]);
}
}
pub fn dispute_decay(&self) -> f64 {
match &self.disputes {
Some(d) => d.len() as f64 * 0.15,
None => 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_confidence_ladder() {
let (next, changed) = Confidence::Uncertain.confirm();
assert_eq!(next, Confidence::Medium);
assert!(changed);
let (next, changed) = Confidence::Certain.confirm();
assert_eq!(next, Confidence::Certain);
assert!(!changed);
}
#[test]
fn test_confidence_factor() {
assert!(Confidence::Certain.factor() > Confidence::Speculative.factor());
assert_eq!(Confidence::Certain.factor(), 1.0);
assert_eq!(Confidence::Speculative.factor(), 0.15);
}
#[test]
fn test_provenance_new() {
let p = Provenance::new("CLI");
assert_eq!(p.source, "CLI");
assert!(!p.auto_generated);
}
#[test]
fn test_audit_append() {
let mut td = TrustData::new(Confidence::Uncertain, "test");
td.append_audit(AuditEntry::new("recorded", "CLI", "2026-01-01T00:00:00Z"));
assert_eq!(td.audit_log.as_ref().unwrap().len(), 1);
}
#[test]
fn test_dispute_decay() {
let mut td = TrustData::new(Confidence::High, "test");
assert_eq!(td.dispute_decay(), 0.0);
td.disputes = Some(vec![Dispute {
id: "d1".into(),
reason: "测试质疑".into(),
disputed_at: "2026-01-01T00:00:00Z".into(),
resolved_at: None,
resolution: None,
}]);
assert_eq!(td.dispute_decay(), 0.15);
}
}