crtx-memory 0.1.1

Memory lifecycle, salience, decay policies, and contradiction objects.
Documentation
//! Epistemic and usefulness separation scaffolds.
//!
//! Usefulness can affect retrieval pressure. It must not upgrade truth,
//! authority, proof closure, validation, or promotion state.

/// Result type for epistemic transition helpers.
pub type EpistemicResult<T> = Result<T, EpistemicError>;

/// Error raised when a transition would conflate utility with truth/proof.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EpistemicError {
    /// Outcome success was used to upgrade truth status.
    UtilityCannotUpgradeTruth,
    /// Outcome success was used to upgrade proof status.
    UtilityCannotUpgradeProof,
    /// Usefulness score was outside the accepted range.
    UsefulnessOutOfRange,
}

/// Truth status describes claim truth, not usefulness.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruthStatus {
    /// Truth is not known.
    Unknown,
    /// Claim is proposed but not verified.
    Hypothesis,
    /// Claim is supported within a declared scope.
    Supported,
    /// Claim is verified by the appropriate authority path.
    Verified,
    /// Claim has been contradicted or rejected.
    Rejected,
}

/// Proof status describes lineage/proof closure, not task success.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProofStatus {
    /// Proof state is unknown.
    Unknown,
    /// Proof chain is broken.
    Broken,
    /// Proof chain is partial.
    Partial,
    /// Proof chain is fully verified.
    FullChainVerified,
}

/// Utility signal observed from use or outcomes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UtilitySignal {
    /// The memory was retrieved or used; exposure only.
    Used,
    /// The memory helped under a declared scope.
    OutcomeSucceeded,
    /// The memory did not help under a declared scope.
    OutcomeFailed,
    /// Operator explicitly marked the memory useful.
    OperatorMarkedUseful,
}

/// Evidence that may justify an explicit truth transition.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruthTransitionEvidence {
    /// Source anchors and claim scope were verified outside utility scoring.
    SourceAnchored,
    /// Operator confirmed the claim under the normal authority path.
    OperatorConfirmed,
    /// Contradiction or rejection evidence changed the truth posture downward.
    ContradictionOrRejection,
    /// Utility evidence is intentionally rejected for truth transitions.
    Utility(UtilitySignal),
}

/// Evidence that may justify an explicit proof transition.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProofTransitionEvidence {
    /// Proof closure verifier supplied this transition.
    ProofClosureVerifier,
    /// Lineage repair supplied this transition.
    LineageRepair,
    /// Utility evidence is intentionally rejected for proof transitions.
    Utility(UtilitySignal),
}

/// Bounded usefulness score. This is retrieval utility, not proof or truth.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Usefulness {
    score: f32,
}

impl Usefulness {
    /// Minimum usefulness score.
    pub const MIN: f32 = 0.0;
    /// Maximum usefulness score.
    pub const MAX: f32 = 1.0;

    /// Construct usefulness when the score is in `[0, 1]`.
    pub fn new(score: f32) -> EpistemicResult<Self> {
        if score.is_nan() || !(Self::MIN..=Self::MAX).contains(&score) {
            return Err(EpistemicError::UsefulnessOutOfRange);
        }

        Ok(Self { score })
    }

    /// Read the bounded score.
    #[must_use]
    pub const fn score(self) -> f32 {
        self.score
    }

    /// Apply a utility signal to usefulness only.
    #[must_use]
    pub fn apply_signal(self, signal: UtilitySignal) -> Self {
        let delta = match signal {
            UtilitySignal::Used => 0.0,
            UtilitySignal::OutcomeSucceeded => 0.10,
            UtilitySignal::OutcomeFailed => -0.15,
            UtilitySignal::OperatorMarkedUseful => 0.20,
        };

        Self {
            score: (self.score + delta).clamp(Self::MIN, Self::MAX),
        }
    }
}

impl Default for Usefulness {
    fn default() -> Self {
        Self { score: 0.0 }
    }
}

/// Combined memory epistemic state plus utility.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EpistemicState {
    /// Claim truth status.
    pub truth_status: TruthStatus,
    /// Proof closure status.
    pub proof_status: ProofStatus,
    /// Retrieval/usefulness signal.
    pub usefulness: Usefulness,
}

impl EpistemicState {
    /// Construct a separated epistemic state.
    #[must_use]
    pub const fn new(
        truth_status: TruthStatus,
        proof_status: ProofStatus,
        usefulness: Usefulness,
    ) -> Self {
        Self {
            truth_status,
            proof_status,
            usefulness,
        }
    }

    /// Apply utility without changing truth or proof.
    #[must_use]
    pub fn apply_utility_signal(self, signal: UtilitySignal) -> Self {
        Self {
            usefulness: self.usefulness.apply_signal(signal),
            ..self
        }
    }

    /// Attempt a truth transition. Utility signals are rejected.
    pub fn with_truth_status(
        self,
        truth_status: TruthStatus,
        evidence: TruthTransitionEvidence,
    ) -> EpistemicResult<Self> {
        if matches!(evidence, TruthTransitionEvidence::Utility(_)) {
            return Err(EpistemicError::UtilityCannotUpgradeTruth);
        }

        Ok(Self {
            truth_status,
            ..self
        })
    }

    /// Attempt a proof transition. Utility signals are rejected.
    pub fn with_proof_status(
        self,
        proof_status: ProofStatus,
        evidence: ProofTransitionEvidence,
    ) -> EpistemicResult<Self> {
        if matches!(evidence, ProofTransitionEvidence::Utility(_)) {
            return Err(EpistemicError::UtilityCannotUpgradeProof);
        }

        Ok(Self {
            proof_status,
            ..self
        })
    }
}

/// Apply outcome success to usefulness only.
#[must_use]
pub fn apply_outcome_success(state: EpistemicState) -> EpistemicState {
    state.apply_utility_signal(UtilitySignal::OutcomeSucceeded)
}

/// Fail closed for any attempted utility-to-truth transition.
pub fn reject_utility_to_truth_transition(
    _signal: UtilitySignal,
    _target: TruthStatus,
) -> EpistemicResult<()> {
    Err(EpistemicError::UtilityCannotUpgradeTruth)
}

/// Fail closed for any attempted utility-to-proof transition.
pub fn reject_utility_to_proof_transition(
    _signal: UtilitySignal,
    _target: ProofStatus,
) -> EpistemicResult<()> {
    Err(EpistemicError::UtilityCannotUpgradeProof)
}

/// Validate that a utility application preserved truth and proof status.
pub fn assert_utility_preserved_epistemics(
    before: EpistemicState,
    after: EpistemicState,
) -> EpistemicResult<()> {
    if before.truth_status != after.truth_status {
        return Err(EpistemicError::UtilityCannotUpgradeTruth);
    }

    if before.proof_status != after.proof_status {
        return Err(EpistemicError::UtilityCannotUpgradeProof);
    }

    Ok(())
}

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

    fn partial_hypothesis() -> EpistemicState {
        EpistemicState::new(
            TruthStatus::Hypothesis,
            ProofStatus::Partial,
            Usefulness::new(0.2).unwrap(),
        )
    }

    #[test]
    fn outcome_success_does_not_upgrade_truth_status() {
        let before = partial_hypothesis();
        let after = apply_outcome_success(before);

        assert_eq!(after.truth_status, TruthStatus::Hypothesis);
        assert_eq!(after.proof_status, ProofStatus::Partial);
        assert!(after.usefulness.score() > before.usefulness.score());
        assert!(assert_utility_preserved_epistemics(before, after).is_ok());
    }

    #[test]
    fn repeated_successful_reuse_can_increase_usefulness_while_proof_remains_partial() {
        let before = partial_hypothesis();
        let after = (0..5).fold(before, |state, _| apply_outcome_success(state));

        assert!(after.usefulness.score() > before.usefulness.score());
        assert_eq!(after.proof_status, ProofStatus::Partial);
        assert_eq!(after.truth_status, TruthStatus::Hypothesis);
    }

    #[test]
    fn utility_to_truth_transition_fails_closed() {
        assert_eq!(
            reject_utility_to_truth_transition(
                UtilitySignal::OutcomeSucceeded,
                TruthStatus::Verified,
            ),
            Err(EpistemicError::UtilityCannotUpgradeTruth)
        );

        assert_eq!(
            partial_hypothesis().with_truth_status(
                TruthStatus::Verified,
                TruthTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
            ),
            Err(EpistemicError::UtilityCannotUpgradeTruth)
        );
    }

    #[test]
    fn utility_to_proof_transition_fails_closed() {
        assert_eq!(
            reject_utility_to_proof_transition(
                UtilitySignal::OutcomeSucceeded,
                ProofStatus::FullChainVerified,
            ),
            Err(EpistemicError::UtilityCannotUpgradeProof)
        );

        assert_eq!(
            partial_hypothesis().with_proof_status(
                ProofStatus::FullChainVerified,
                ProofTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
            ),
            Err(EpistemicError::UtilityCannotUpgradeProof)
        );
    }

    #[test]
    fn usefulness_rejects_nan_and_out_of_range_scores() {
        assert_eq!(
            Usefulness::new(f32::NAN),
            Err(EpistemicError::UsefulnessOutOfRange)
        );
        assert_eq!(
            Usefulness::new(1.01),
            Err(EpistemicError::UsefulnessOutOfRange)
        );
    }
}