Skip to main content

cortex_memory/
epistemic.rs

1//! Epistemic and usefulness separation scaffolds.
2//!
3//! Usefulness can affect retrieval pressure. It must not upgrade truth,
4//! authority, proof closure, validation, or promotion state.
5
6/// Result type for epistemic transition helpers.
7pub type EpistemicResult<T> = Result<T, EpistemicError>;
8
9/// Error raised when a transition would conflate utility with truth/proof.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EpistemicError {
12    /// Outcome success was used to upgrade truth status.
13    UtilityCannotUpgradeTruth,
14    /// Outcome success was used to upgrade proof status.
15    UtilityCannotUpgradeProof,
16    /// Usefulness score was outside the accepted range.
17    UsefulnessOutOfRange,
18}
19
20/// Truth status describes claim truth, not usefulness.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TruthStatus {
23    /// Truth is not known.
24    Unknown,
25    /// Claim is proposed but not verified.
26    Hypothesis,
27    /// Claim is supported within a declared scope.
28    Supported,
29    /// Claim is verified by the appropriate authority path.
30    Verified,
31    /// Claim has been contradicted or rejected.
32    Rejected,
33}
34
35/// Proof status describes lineage/proof closure, not task success.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ProofStatus {
38    /// Proof state is unknown.
39    Unknown,
40    /// Proof chain is broken.
41    Broken,
42    /// Proof chain is partial.
43    Partial,
44    /// Proof chain is fully verified.
45    FullChainVerified,
46}
47
48/// Utility signal observed from use or outcomes.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum UtilitySignal {
51    /// The memory was retrieved or used; exposure only.
52    Used,
53    /// The memory helped under a declared scope.
54    OutcomeSucceeded,
55    /// The memory did not help under a declared scope.
56    OutcomeFailed,
57    /// Operator explicitly marked the memory useful.
58    OperatorMarkedUseful,
59}
60
61/// Evidence that may justify an explicit truth transition.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum TruthTransitionEvidence {
64    /// Source anchors and claim scope were verified outside utility scoring.
65    SourceAnchored,
66    /// Operator confirmed the claim under the normal authority path.
67    OperatorConfirmed,
68    /// Contradiction or rejection evidence changed the truth posture downward.
69    ContradictionOrRejection,
70    /// Utility evidence is intentionally rejected for truth transitions.
71    Utility(UtilitySignal),
72}
73
74/// Evidence that may justify an explicit proof transition.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum ProofTransitionEvidence {
77    /// Proof closure verifier supplied this transition.
78    ProofClosureVerifier,
79    /// Lineage repair supplied this transition.
80    LineageRepair,
81    /// Utility evidence is intentionally rejected for proof transitions.
82    Utility(UtilitySignal),
83}
84
85/// Bounded usefulness score. This is retrieval utility, not proof or truth.
86#[derive(Debug, Clone, Copy, PartialEq)]
87pub struct Usefulness {
88    score: f32,
89}
90
91impl Usefulness {
92    /// Minimum usefulness score.
93    pub const MIN: f32 = 0.0;
94    /// Maximum usefulness score.
95    pub const MAX: f32 = 1.0;
96
97    /// Construct usefulness when the score is in `[0, 1]`.
98    pub fn new(score: f32) -> EpistemicResult<Self> {
99        if score.is_nan() || !(Self::MIN..=Self::MAX).contains(&score) {
100            return Err(EpistemicError::UsefulnessOutOfRange);
101        }
102
103        Ok(Self { score })
104    }
105
106    /// Read the bounded score.
107    #[must_use]
108    pub const fn score(self) -> f32 {
109        self.score
110    }
111
112    /// Apply a utility signal to usefulness only.
113    #[must_use]
114    pub fn apply_signal(self, signal: UtilitySignal) -> Self {
115        let delta = match signal {
116            UtilitySignal::Used => 0.0,
117            UtilitySignal::OutcomeSucceeded => 0.10,
118            UtilitySignal::OutcomeFailed => -0.15,
119            UtilitySignal::OperatorMarkedUseful => 0.20,
120        };
121
122        Self {
123            score: (self.score + delta).clamp(Self::MIN, Self::MAX),
124        }
125    }
126}
127
128impl Default for Usefulness {
129    fn default() -> Self {
130        Self { score: 0.0 }
131    }
132}
133
134/// Combined memory epistemic state plus utility.
135#[derive(Debug, Clone, Copy, PartialEq)]
136pub struct EpistemicState {
137    /// Claim truth status.
138    pub truth_status: TruthStatus,
139    /// Proof closure status.
140    pub proof_status: ProofStatus,
141    /// Retrieval/usefulness signal.
142    pub usefulness: Usefulness,
143}
144
145impl EpistemicState {
146    /// Construct a separated epistemic state.
147    #[must_use]
148    pub const fn new(
149        truth_status: TruthStatus,
150        proof_status: ProofStatus,
151        usefulness: Usefulness,
152    ) -> Self {
153        Self {
154            truth_status,
155            proof_status,
156            usefulness,
157        }
158    }
159
160    /// Apply utility without changing truth or proof.
161    #[must_use]
162    pub fn apply_utility_signal(self, signal: UtilitySignal) -> Self {
163        Self {
164            usefulness: self.usefulness.apply_signal(signal),
165            ..self
166        }
167    }
168
169    /// Attempt a truth transition. Utility signals are rejected.
170    pub fn with_truth_status(
171        self,
172        truth_status: TruthStatus,
173        evidence: TruthTransitionEvidence,
174    ) -> EpistemicResult<Self> {
175        if matches!(evidence, TruthTransitionEvidence::Utility(_)) {
176            return Err(EpistemicError::UtilityCannotUpgradeTruth);
177        }
178
179        Ok(Self {
180            truth_status,
181            ..self
182        })
183    }
184
185    /// Attempt a proof transition. Utility signals are rejected.
186    pub fn with_proof_status(
187        self,
188        proof_status: ProofStatus,
189        evidence: ProofTransitionEvidence,
190    ) -> EpistemicResult<Self> {
191        if matches!(evidence, ProofTransitionEvidence::Utility(_)) {
192            return Err(EpistemicError::UtilityCannotUpgradeProof);
193        }
194
195        Ok(Self {
196            proof_status,
197            ..self
198        })
199    }
200}
201
202/// Apply outcome success to usefulness only.
203#[must_use]
204pub fn apply_outcome_success(state: EpistemicState) -> EpistemicState {
205    state.apply_utility_signal(UtilitySignal::OutcomeSucceeded)
206}
207
208/// Fail closed for any attempted utility-to-truth transition.
209pub fn reject_utility_to_truth_transition(
210    _signal: UtilitySignal,
211    _target: TruthStatus,
212) -> EpistemicResult<()> {
213    Err(EpistemicError::UtilityCannotUpgradeTruth)
214}
215
216/// Fail closed for any attempted utility-to-proof transition.
217pub fn reject_utility_to_proof_transition(
218    _signal: UtilitySignal,
219    _target: ProofStatus,
220) -> EpistemicResult<()> {
221    Err(EpistemicError::UtilityCannotUpgradeProof)
222}
223
224/// Validate that a utility application preserved truth and proof status.
225pub fn assert_utility_preserved_epistemics(
226    before: EpistemicState,
227    after: EpistemicState,
228) -> EpistemicResult<()> {
229    if before.truth_status != after.truth_status {
230        return Err(EpistemicError::UtilityCannotUpgradeTruth);
231    }
232
233    if before.proof_status != after.proof_status {
234        return Err(EpistemicError::UtilityCannotUpgradeProof);
235    }
236
237    Ok(())
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    fn partial_hypothesis() -> EpistemicState {
245        EpistemicState::new(
246            TruthStatus::Hypothesis,
247            ProofStatus::Partial,
248            Usefulness::new(0.2).unwrap(),
249        )
250    }
251
252    #[test]
253    fn outcome_success_does_not_upgrade_truth_status() {
254        let before = partial_hypothesis();
255        let after = apply_outcome_success(before);
256
257        assert_eq!(after.truth_status, TruthStatus::Hypothesis);
258        assert_eq!(after.proof_status, ProofStatus::Partial);
259        assert!(after.usefulness.score() > before.usefulness.score());
260        assert!(assert_utility_preserved_epistemics(before, after).is_ok());
261    }
262
263    #[test]
264    fn repeated_successful_reuse_can_increase_usefulness_while_proof_remains_partial() {
265        let before = partial_hypothesis();
266        let after = (0..5).fold(before, |state, _| apply_outcome_success(state));
267
268        assert!(after.usefulness.score() > before.usefulness.score());
269        assert_eq!(after.proof_status, ProofStatus::Partial);
270        assert_eq!(after.truth_status, TruthStatus::Hypothesis);
271    }
272
273    #[test]
274    fn utility_to_truth_transition_fails_closed() {
275        assert_eq!(
276            reject_utility_to_truth_transition(
277                UtilitySignal::OutcomeSucceeded,
278                TruthStatus::Verified,
279            ),
280            Err(EpistemicError::UtilityCannotUpgradeTruth)
281        );
282
283        assert_eq!(
284            partial_hypothesis().with_truth_status(
285                TruthStatus::Verified,
286                TruthTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
287            ),
288            Err(EpistemicError::UtilityCannotUpgradeTruth)
289        );
290    }
291
292    #[test]
293    fn utility_to_proof_transition_fails_closed() {
294        assert_eq!(
295            reject_utility_to_proof_transition(
296                UtilitySignal::OutcomeSucceeded,
297                ProofStatus::FullChainVerified,
298            ),
299            Err(EpistemicError::UtilityCannotUpgradeProof)
300        );
301
302        assert_eq!(
303            partial_hypothesis().with_proof_status(
304                ProofStatus::FullChainVerified,
305                ProofTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
306            ),
307            Err(EpistemicError::UtilityCannotUpgradeProof)
308        );
309    }
310
311    #[test]
312    fn usefulness_rejects_nan_and_out_of_range_scores() {
313        assert_eq!(
314            Usefulness::new(f32::NAN),
315            Err(EpistemicError::UsefulnessOutOfRange)
316        );
317        assert_eq!(
318            Usefulness::new(1.01),
319            Err(EpistemicError::UsefulnessOutOfRange)
320        );
321    }
322}