Skip to main content

ijima_core/
memory.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Memory palace domain types — the curated long-term memory surface.
5//!
6//! These types are import-compatible with the pi-mempalace schema (see
7//! `docs/HANDOFF.md` §3 for the live SQLite schema). The store
8//! implementation lives in `ijima-server`; this module defines only the
9//! pure domain model so it can be shared by the server, miner, and
10//! client crates.
11
12use crate::harness::Harness;
13use crate::provenance::{AuthorityScope, InstanceId};
14
15/// A newtype for the stable, opaque identifier of a stored memory.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(feature = "serde", serde(transparent))]
19pub struct MemoryId(pub String);
20
21/// A curated memory palace entry: the refined-metal output of either an
22/// explicit save or a mining pass.
23///
24/// Provenance (`harness`, `session_id`, `source`) is mandatory so that
25/// any entry can be traced back to the conversation that produced it.
26#[derive(Debug, Clone, PartialEq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Memory {
29    /// Stable, opaque identifier (e.g. `mem_<ulid>`).
30    pub id: MemoryId,
31    /// The curated content text.
32    pub content: String,
33    /// Project namespace (defaults to `"general"`).
34    pub project: String,
35    /// Topic within the project.
36    pub topic: String,
37    /// Provenance: how this entry was created — explicit save, auto-capture,
38    /// or a mined extraction.
39    pub source: MemorySource,
40    /// Provenance: which harness wrote this entry.
41    pub harness: Harness,
42    /// Provenance: the originating session, when known.
43    pub session_id: Option<String>,
44    /// Provenance: the instance that authored this entry (ADR
45    /// provenance-tier). Defaults to the local instance for 0.1.0;
46    /// federation (Phase 5) stamps the origin instance.
47    #[cfg_attr(feature = "serde", serde(default))]
48    pub origin: InstanceId,
49    /// Provenance: the authority scope (source-of-truth) for this entry's
50    /// domain (ADR provenance-tier). Defaults to local; drives Phase 5
51    /// conflict resolution.
52    #[cfg_attr(feature = "serde", serde(default))]
53    pub authority: AuthorityScope,
54    /// Importance score (0.0–1.0). Used for wake-up ranking
55    /// (top-N by importance × recency). Defaults to 0.5, matching
56    /// pi-mempalace.
57    #[cfg_attr(feature = "serde", serde(default = "default_importance"))]
58    pub importance: f32,
59    /// Creation timestamp. v0: Unix epoch seconds as a string (monotonic
60    /// for DESC ordering). Future: ISO-8601 when a time crate lands.
61    #[cfg_attr(feature = "serde", serde(default))]
62    pub created_at: String,
63}
64
65#[cfg(feature = "serde")]
66fn default_importance() -> f32 {
67    0.5
68}
69
70/// How a [`Memory`] entered the palace.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73pub enum MemorySource {
74    /// An operator or harness explicitly saved it.
75    Explicit,
76    /// An auto-capture hook wrote it.
77    AutoCapture,
78    /// The miner extracted it from a session transcript.
79    Mined,
80    /// Doctrine: curated, Git-versioned, PR-reviewed memory mirrored from
81    /// the repository seed pack. Never written directly by agents — the
82    /// highest-trust, lowest-write-rate tier.
83    Doctrine,
84}
85
86impl MemorySource {
87    /// The Schubert trust grade (codimension) of this tier — the
88    /// quantitative trust axis. **Higher = more trusted.** Phase 5 egress
89    /// filters turn this into intersection arithmetic ("does this
90    /// content's grade fit the link's trust budget?"); Phase 4 keys
91    /// doctrine-health on it.
92    ///
93    /// Grades fit comfortably inside Gr(4,8)'s 4×4 Schubert box:
94    ///
95    /// | Tier | `trust_grade` |
96    /// |---|---|
97    /// | [`AutoCapture`](MemorySource::AutoCapture) | 1 (ambient, unverified) |
98    /// | [`Mined`](MemorySource::Mined) | 2 (model-extracted, review-eligible) |
99    /// | [`Explicit`](MemorySource::Explicit) | 3 (a human deliberately saved it) |
100    /// | [`Doctrine`](MemorySource::Doctrine) | 4 (Git-versioned, PR-reviewed, curated) |
101    pub fn trust_grade(self) -> u64 {
102        match self {
103            MemorySource::AutoCapture => 1,
104            MemorySource::Mined => 2,
105            MemorySource::Explicit => 3,
106            MemorySource::Doctrine => 4,
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn memory_round_trips_its_fields() {
117        let m = Memory {
118            id: MemoryId("mem_01".into()),
119            content: "Decided to use DeepSeek for extraction.".into(),
120            project: "ijima".into(),
121            topic: "mining".into(),
122            source: MemorySource::Mined,
123            harness: Harness::Pi,
124            session_id: Some("sess_7".into()),
125            origin: InstanceId::local(),
126            authority: AuthorityScope::local(),
127            importance: 0.8,
128            created_at: "123".into(),
129        };
130        assert_eq!(m.id.0, "mem_01");
131        assert_eq!(m.source, MemorySource::Mined);
132        assert_eq!(m.harness, Harness::Pi);
133        assert_eq!(m.importance, 0.8);
134        assert_eq!(m.created_at, "123");
135        assert_eq!(m.session_id.as_deref(), Some("sess_7"));
136    }
137
138    #[test]
139    fn doctrine_is_distinct_from_explicit() {
140        // Doctrine is the curated, Git-versioned tier — it must not be
141        // confused with an operator's explicit save.
142        assert!(matches!(MemorySource::Doctrine, MemorySource::Doctrine));
143        assert_ne!(MemorySource::Doctrine, MemorySource::Explicit);
144        assert_ne!(MemorySource::Doctrine, MemorySource::Mined);
145    }
146
147    #[test]
148    fn trust_grade_monotone_in_trust() {
149        // Higher tier ⇒ higher grade (more trusted). Drives Phase 5 egress
150        // intersection arithmetic and Phase 4 doctrine-health.
151        assert_eq!(MemorySource::AutoCapture.trust_grade(), 1);
152        assert_eq!(MemorySource::Mined.trust_grade(), 2);
153        assert_eq!(MemorySource::Explicit.trust_grade(), 3);
154        assert_eq!(MemorySource::Doctrine.trust_grade(), 4);
155        assert!(
156            MemorySource::AutoCapture.trust_grade() < MemorySource::Mined.trust_grade()
157                && MemorySource::Mined.trust_grade() < MemorySource::Explicit.trust_grade()
158                && MemorySource::Explicit.trust_grade() < MemorySource::Doctrine.trust_grade()
159        );
160    }
161}