use serde::{Deserialize, Serialize};
use crate::trust::TrustData;
pub use crate::trust::Confidence;
#[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,
}
}
}
#[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
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mem {
pub id: String,
pub level: MemLevel,
#[serde(default)]
pub zone: MemoryZone,
pub content: String,
#[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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_matched: Option<String>,
#[serde(default)]
pub match_count: u32,
#[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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supersedes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub superseded_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub superseded_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trust: Option<TrustData>,
pub created_at: String,
pub updated_at: String,
}
fn default_weight() -> f64 { 0.5 }
impl Mem {
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>,
}
#[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 {
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))
}
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()
}
pub fn find_mem(&self, id: &str) -> Option<&Mem> {
self.memories.iter().find(|m| m.id == id || m.id.ends_with(id))
}
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");
}
}