dhive-core 0.1.0

D-HIVE Trust Protocol — Rust core: BM25 search, LCS similarity, canonicalHash, pack format, curation scoring
Documentation
//! Memory 数据结构 — D-HIVE 记忆内核
//!
//! Mem 结构体、Zone 语义分区、supersedes 超越链、KernelState 持久化格式。

use serde::{Deserialize, Serialize};
use crate::trust::TrustData;

/// 置信度阶梯 (重新导出以保持模块独立)
pub use crate::trust::Confidence;

/// 记忆级别:activity(临时活动) < fact(持久事实) < cornerstone(基石原则)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum MemLevel {
    Activity,
    Fact,
    Cornerstone,
}

impl MemLevel {
    pub fn as_str(&self) -> &'static str {
        match self {
            MemLevel::Activity => "activity",
            MemLevel::Fact => "fact",
            MemLevel::Cornerstone => "cornerstone",
        }
    }

    pub fn from_str(s: &str) -> Option<Self> {
        match s {
            "activity" => Some(MemLevel::Activity),
            "fact" => Some(MemLevel::Fact),
            "cornerstone" => Some(MemLevel::Cornerstone),
            _ => None,
        }
    }
}

/// Zone 语义分区
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum MemoryZone {
    /// 用户身份、偏好、核心原则(几乎不变)
    Core,
    /// 当前焦点、近期决策(中频更新)
    Work,
    /// 项目级约定和上下文(按项目隔离)
    Project,
    /// 会话摘要(高频写入)
    Episode,
    /// 未分类(兜底)
    General,
}

impl MemoryZone {
    pub fn as_str(&self) -> &'static str {
        match self {
            MemoryZone::Core => "core",
            MemoryZone::Work => "work",
            MemoryZone::Project => "project",
            MemoryZone::Episode => "episode",
            MemoryZone::General => "general",
        }
    }

    pub fn from_str(s: &str) -> Option<Self> {
        match s {
            "core" => Some(MemoryZone::Core),
            "work" => Some(MemoryZone::Work),
            "project" => Some(MemoryZone::Project),
            "episode" => Some(MemoryZone::Episode),
            "general" => Some(MemoryZone::General),
            _ => None,
        }
    }
}

impl Default for MemoryZone {
    fn default() -> Self {
        MemoryZone::General
    }
}

/// 记忆条目 — D-HIVE 知识的基本单元
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mem {
    /// 唯一标识符 (timestamp-nanoid 格式)
    pub id: String,
    /// 记忆级别
    pub level: MemLevel,
    /// Zone 语义分区 (默认 general)
    #[serde(default)]
    pub zone: MemoryZone,
    /// 记忆内容 (最长 5000 字符)
    pub content: String,
    /// 记忆权重 0.0–1.0 (策展评分算法自动维护)
    #[serde(default = "default_weight")]
    pub weight: f64,
    /// 标签列表
    #[serde(default)]
    pub tags: Vec<String>,
    /// 创建来源
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,
    /// 扩展元数据
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
    /// 上次搜索命中时间 (ISO 8601)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_matched: Option<String>,
    /// 搜索命中次数
    #[serde(default)]
    pub match_count: u32,
    /// 被加载进 context 的次数
    #[serde(default)]
    pub loaded_count: u32,
    /// 被引用的次数
    #[serde(default)]
    pub referenced_count: u32,
    /// 上次加载时间
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_loaded: Option<String>,
    /// 上次引用时间
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_referenced: Option<String>,
    /// 被此记忆替代的旧记忆 ID 列表
    #[serde(skip_serializing_if = "Option::is_none")]
    pub supersedes: Option<Vec<String>>,
    /// 发生替代的时间
    #[serde(skip_serializing_if = "Option::is_none")]
    pub superseded_at: Option<String>,
    /// 替代此记忆的新记忆 ID (单链)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub superseded_by: Option<String>,
    /// 信任数据
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trust: Option<TrustData>,
    /// 创建时间 (ISO 8601)
    pub created_at: String,
    /// 最后更新时间 (ISO 8601)
    pub updated_at: String,
}

fn default_weight() -> f64 { 0.5 }

impl Mem {
    /// 返回短 ID (后 8 位)
    pub fn short_id(&self) -> &str {
        if self.id.len() > 8 {
            &self.id[self.id.len() - 8..]
        } else {
            &self.id
        }
    }

    /// 此记忆是否被超越
    pub fn is_superseded(&self) -> bool {
        self.superseded_by.is_some()
    }
}

/// 告警
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Warning {
    #[serde(rename = "memoryId")]
    pub memory_id: String,
    #[serde(rename = "cornerstoneContent")]
    pub cornerstone_content: String,
    pub violation: String,
    pub severity: String,
    pub timestamp: String,
}

/// 会话信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInfo {
    pub id: String,
    #[serde(rename = "startedAt")]
    pub started_at: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "endedAt")]
    pub ended_at: Option<String>,
    #[serde(rename = "activityCount", default)]
    pub activity_count: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,
}

/// D-HIVE 内核状态 — kernel-state.json 的完整模型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KernelState {
    #[serde(default)]
    pub memories: Vec<Mem>,
    #[serde(default, rename = "reflectedIds")]
    pub reflected_ids: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "lastReflectionAt")]
    pub last_reflection_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "lastCuratedAt")]
    pub last_curated_at: Option<String>,
    #[serde(default)]
    pub warnings: Vec<Warning>,
    #[serde(default)]
    pub sessions: Vec<SessionInfo>,
    #[serde(default, rename = "pendingFacts")]
    pub pending_facts: Vec<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub skills: Option<Vec<serde_json::Value>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub goals: Option<Vec<serde_json::Value>>,
}

impl KernelState {
    /// 加载 kernel-state.json 文件
    pub fn load(path: &str) -> Result<Self, String> {
        let content = std::fs::read_to_string(path)
            .map_err(|e| format!("Failed to read {}: {}", path, e))?;
        serde_json::from_str(&content)
            .map_err(|e| format!("Failed to parse {}: {}", path, e))
    }

    /// 保存 kernel-state.json 文件
    pub fn save(&self, path: &str) -> Result<(), String> {
        let json = serde_json::to_string_pretty(self)
            .map_err(|e| format!("Failed to serialize: {}", e))?;
        std::fs::write(path, json)
            .map_err(|e| format!("Failed to write {}: {}", path, e))
    }

    /// 返回活跃记忆列表 (未被超越的记忆)
    pub fn list_active(&self) -> Vec<&Mem> {
        self.memories.iter().filter(|m| !m.is_superseded()).collect()
    }

    /// 通过 ID (精确或后 8 位) 查找记忆
    pub fn find_mem(&self, id: &str) -> Option<&Mem> {
        self.memories.iter().find(|m| m.id == id || m.id.ends_with(id))
    }

    /// 通过 ID (精确或后 8 位) 查找可变引用
    pub fn find_mem_mut(&mut self, id: &str) -> Option<&mut Mem> {
        self.memories.iter_mut().find(|m| m.id == id || m.id.ends_with(id))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mem_level_roundtrip() {
        assert_eq!(MemLevel::from_str("activity"), Some(MemLevel::Activity));
        assert_eq!(MemLevel::from_str("fact"), Some(MemLevel::Fact));
        assert_eq!(MemLevel::from_str("cornerstone"), Some(MemLevel::Cornerstone));
        assert_eq!(MemLevel::from_str("invalid"), None);
        assert_eq!(MemLevel::Activity.as_str(), "activity");
    }

    #[test]
    fn test_memory_zone_roundtrip() {
        assert_eq!(MemoryZone::from_str("core"), Some(MemoryZone::Core));
        assert_eq!(MemoryZone::from_str("general"), Some(MemoryZone::General));
        assert_eq!(MemoryZone::from_str("unknown"), None);
    }

    #[test]
    fn test_short_id() {
        let mem = Mem {
            id: "1779000000001-abcdefgh".into(),
            level: MemLevel::Fact,
            zone: MemoryZone::General,
            content: "test".into(),
            weight: 0.5,
            tags: vec![],
            source: None,
            metadata: None,
            last_matched: None,
            match_count: 0,
            loaded_count: 0,
            referenced_count: 0,
            last_loaded: None,
            last_referenced: None,
            supersedes: None,
            superseded_at: None,
            superseded_by: None,
            trust: None,
            created_at: "2026-01-01T00:00:00Z".into(),
            updated_at: "2026-01-01T00:00:00Z".into(),
        };
        assert_eq!(mem.short_id(), "abcdefgh");
    }

    #[test]
    fn test_list_active_filters_superseded() {
        let mut state = KernelState {
            memories: vec![],
            reflected_ids: vec![],
            last_reflection_at: None,
            last_curated_at: None,
            warnings: vec![],
            sessions: vec![],
            pending_facts: vec![],
            skills: None,
            goals: None,
        };

        let m1 = Mem {
            id: "m1".into(),
            level: MemLevel::Fact,
            zone: MemoryZone::General,
            content: "active".into(),
            weight: 0.5,
            tags: vec![],
            source: None, metadata: None,
            last_matched: None, match_count: 0, loaded_count: 0, referenced_count: 0,
            last_loaded: None, last_referenced: None,
            supersedes: None, superseded_at: None, superseded_by: None,
            trust: None,
            created_at: "2026-01-01T00:00:00Z".into(),
            updated_at: "2026-01-01T00:00:00Z".into(),
        };
        let m2 = Mem {
            id: "m2".into(),
            level: MemLevel::Fact,
            zone: MemoryZone::General,
            content: "superseded".into(),
            weight: 0.5,
            tags: vec![],
            source: None, metadata: None,
            last_matched: None, match_count: 0, loaded_count: 0, referenced_count: 0,
            last_loaded: None, last_referenced: None,
            supersedes: None, superseded_at: None,
            superseded_by: Some("m3".into()),
            trust: None,
            created_at: "2026-01-01T00:00:00Z".into(),
            updated_at: "2026-01-01T00:00:00Z".into(),
        };
        state.memories = vec![m1, m2];
        let active = state.list_active();
        assert_eq!(active.len(), 1);
        assert_eq!(active[0].id, "m1");
    }
}