weavatrix-memory 0.3.3

Event-sourced bitemporal evidence memory and budgeted context retrieval for coding agents
Documentation
use super::domain::{read_evidence, write_evidence};
use super::io::{Reader, Writer, codec};
use crate::{
    error::Result,
    id::FactId,
    projection::memory::state::{Retraction, RetractionHistory},
    time::Timestamp,
};
use std::collections::BTreeMap;

#[cfg(test)]
pub(super) fn write_v1(
    writer: &mut Writer,
    retractions: &BTreeMap<FactId, RetractionHistory>,
) -> Result<()> {
    writer.usize(retractions.len())?;
    for (fact, history) in retractions {
        let change = history
            .last()
            .ok_or_else(|| codec("retraction history must not be empty"))?;
        writer.string(fact.as_str())?;
        writer.signed(change.valid_until.as_unix_micros());
        writer.signed(change.recorded_at.as_unix_micros());
    }
    Ok(())
}

pub(super) fn write(
    writer: &mut Writer,
    retractions: &BTreeMap<FactId, RetractionHistory>,
) -> Result<()> {
    writer.usize(retractions.len())?;
    for (fact, history) in retractions {
        writer.string(fact.as_str())?;
        writer.usize(history.len())?;
        for change in history {
            write_change(writer, change)?;
        }
    }
    Ok(())
}

pub(super) fn read_v2(reader: &mut Reader<'_>) -> Result<BTreeMap<FactId, RetractionHistory>> {
    let count = reader.count()?;
    let mut changes = BTreeMap::new();
    for _ in 0..count {
        let fact = FactId::new(reader.string()?)?;
        let versions = reader.count()?;
        if versions == 0 {
            return Err(codec("retraction history must not be empty"));
        }
        let mut history = Vec::with_capacity(versions);
        for _ in 0..versions {
            history.push(read_change(reader)?);
        }
        if changes.insert(fact, history).is_some() {
            return Err(codec("duplicate retraction"));
        }
    }
    Ok(changes)
}

pub(super) fn read_v1(reader: &mut Reader<'_>) -> Result<BTreeMap<FactId, RetractionHistory>> {
    let count = reader.count()?;
    let mut changes = BTreeMap::new();
    for _ in 0..count {
        let fact = FactId::new(reader.string()?)?;
        let change = Retraction {
            valid_until: Timestamp::from_unix_micros(reader.signed()?),
            recorded_at: Timestamp::from_unix_micros(reader.signed()?),
            position: 0,
            evidence: Vec::new(),
        };
        if changes.insert(fact, vec![change]).is_some() {
            return Err(codec("duplicate retraction"));
        }
    }
    Ok(changes)
}

fn write_change(writer: &mut Writer, change: &Retraction) -> Result<()> {
    writer.signed(change.valid_until.as_unix_micros());
    writer.signed(change.recorded_at.as_unix_micros());
    writer.varint(change.position);
    writer.usize(change.evidence.len())?;
    for evidence in &change.evidence {
        write_evidence(writer, evidence)?;
    }
    Ok(())
}

fn read_change(reader: &mut Reader<'_>) -> Result<Retraction> {
    let valid_until = Timestamp::from_unix_micros(reader.signed()?);
    let recorded_at = Timestamp::from_unix_micros(reader.signed()?);
    let position = reader.varint()?;
    let count = reader.count()?;
    let mut evidence = Vec::with_capacity(count);
    for _ in 0..count {
        evidence.push(read_evidence(reader)?);
    }
    Ok(Retraction {
        valid_until,
        recorded_at,
        position,
        evidence,
    })
}