velesdb-memory 0.14.1

VelesDB-memory: local-first MCP memory server for AI agents (remember/recall/relate/forget/why + deterministic context compiler).
Documentation
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use super::{
    execute::journal_workspace, fingerprint, query_error, CollectionProgress, MigrationLock,
    MigrationState, Phase, AGENT_COLLECTIONS, STATE_FORMAT_VERSION,
};

pub(crate) fn prepare_live_switch(
    source: &Path,
    destination: &Path,
    target_model: &str,
    target_dimension: usize,
    target_witness: &str,
) -> Result<PathBuf, crate::MemoryError> {
    verify_target_provenance(destination, target_model, target_dimension)?;
    let source = source
        .canonicalize()
        .map_err(|err| query_error(format!("cannot resolve live source: {err}")))?;
    let source_fingerprint = fingerprint(&source)?;
    let workspace = journal_workspace(destination)?;
    let lock = MigrationLock::acquire(&workspace, "migrate-live-prepare").map_err(query_error)?;
    let result = prepare_locked(
        &workspace,
        &lock,
        source,
        source_fingerprint,
        target_model,
        target_dimension,
        target_witness,
    );
    super::execute::reconcile(result, lock.release())?;
    Ok(workspace)
}

fn prepare_locked(
    workspace: &Path,
    lock: &MigrationLock,
    source_path: PathBuf,
    source_fingerprint: String,
    target_model: &str,
    target_dimension: usize,
    target_witness: &str,
) -> Result<(), crate::MemoryError> {
    if let Some(existing) = MigrationState::read(workspace).map_err(query_error)? {
        existing
            .may_resume(
                &source_path,
                &source_fingerprint,
                target_model,
                target_dimension,
            )
            .map_err(query_error)?;
        if existing.embedder_witness.as_deref() != Some(target_witness) {
            return Err(query_error(
                "live switch target embedder witness changed; start a fresh migration",
            ));
        }
        return require_validated(existing.phase);
    }
    let mut state = new_state(
        source_path,
        source_fingerprint,
        target_model,
        target_dimension,
        target_witness,
    );
    state.write(workspace, lock).map_err(query_error)?;
    state.phase = Phase::DestinationValidated;
    state.write(workspace, lock).map_err(query_error)
}

fn new_state(
    source_path: PathBuf,
    source_fingerprint: String,
    target_model: &str,
    target_dimension: usize,
    target_witness: &str,
) -> MigrationState {
    let progress = AGENT_COLLECTIONS
        .iter()
        .map(|name| ((*name).to_owned(), CollectionProgress::Complete))
        .collect::<BTreeMap<_, _>>();
    MigrationState {
        format_version: STATE_FORMAT_VERSION,
        phase: Phase::Prepared,
        source_path,
        source_fingerprint,
        target_model: target_model.to_owned(),
        target_dimension,
        progress,
        embedder_witness: Some(target_witness.to_owned()),
    }
}

fn require_validated(phase: Phase) -> Result<(), crate::MemoryError> {
    if phase == Phase::DestinationValidated {
        return Ok(());
    }
    Err(query_error(format!(
        "live switch preparation found journal phase {phase:?}; recovery must finish first"
    )))
}

fn verify_target_provenance(
    destination: &Path,
    target_model: &str,
    target_dimension: usize,
) -> Result<(), crate::MemoryError> {
    let recorded = crate::embedding_provenance::read(destination).map_err(query_error)?;
    crate::embedding_provenance::check(recorded.as_ref(), target_model, target_dimension)
        .map_err(query_error)
}