weavatrix-memory 0.3.3

Event-sourced bitemporal evidence memory and budgeted context retrieval for coding agents
Documentation
use super::{
    MemoryProjection,
    state::{Retraction, RetractionHistory},
};
use crate::{
    domain::Evidence,
    error::{MemoryError, Result},
    id::FactId,
    time::Timestamp,
};

impl MemoryProjection {
    pub(crate) fn known_retraction(
        &self,
        fact: &FactId,
        known_at: Timestamp,
    ) -> Option<&Retraction> {
        self.retractions.get(fact).and_then(|history| {
            history
                .iter()
                .filter(|change| change.recorded_at <= known_at)
                .max_by_key(|change| (change.recorded_at, change.position))
        })
    }

    pub(super) fn apply_retraction(
        &mut self,
        recorded_at: Timestamp,
        position: u64,
        fact_id: &FactId,
        valid_until: Timestamp,
        evidence: &[Evidence],
    ) -> Result<()> {
        let fact = self.fact(fact_id).ok_or_else(|| MemoryError::MissingFact {
            id: fact_id.to_string(),
        })?;
        if valid_until <= fact.valid_from {
            return Err(MemoryError::InvalidValue {
                field: "retraction.valid_until",
                reason: "must be later than the fact valid_from",
            });
        }
        if evidence.is_empty() {
            return Err(MemoryError::InvalidValue {
                field: "retraction.evidence",
                reason: "at least one evidence item is required",
            });
        }
        evidence.iter().try_for_each(Evidence::validate)?;
        self.retractions
            .entry(fact_id.clone())
            .or_default()
            .push(Retraction {
                valid_until,
                recorded_at,
                position,
                evidence: evidence.to_vec(),
            });
        Ok(())
    }
}

/// Accepts both the current history array and a legacy single retraction object.
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub(super) enum RetractionField {
    One(Retraction),
    Many(RetractionHistory),
}

impl RetractionField {
    pub(super) fn into_history(self) -> RetractionHistory {
        match self {
            Self::One(change) => vec![change],
            Self::Many(changes) => changes,
        }
    }
}