dhive-core 0.1.0

D-HIVE Trust Protocol — Rust core: BM25 search, LCS similarity, canonicalHash, pack format, curation scoring
Documentation
//! 信任标注 — 置信度阶梯、溯源链、审计日志
//!
//! D-HIVE 信任模型:置信度(5级) × 溯源链深度 × 审计日志完整性 × 争议解决率

use serde::{Deserialize, Serialize};

/// 置信度阶梯 (5 级有序)
///
/// speculative → uncertain → medium → high → certain
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
pub enum Confidence {
    /// 推测,可能错误 — 低置信度 AI 输出
    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,
        }
    }

    /// confirm 升级
    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), // already max
        }
    }
}

/// 溯源信息 — 记忆从哪里来
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Provenance {
    /// 信息来源 (CLI/MCP/hook/auto/user/session-id)
    pub source: String,
    /// 推导来源记忆 ID 列表
    #[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>,
    /// 确认时间 (ISO 8601)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub confirmed_at: Option<String>,
    /// 是否 AI 自动生成
    #[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);
    }
}