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::time::Duration;

use super::{remove_cancelled_artifacts, JobPhase, JobRecord, JobSpec};
use crate::embedding_provenance::{self, EmbeddingProvenance};
use crate::mutation::catchup::CatchUpConfig;
use crate::mutation::controller::ControllerConfig;
use crate::mutation::journal::{DirtyJournal, EpochIdentity};
use crate::storage::NativeStore;

#[test]
fn cancellation_refuses_foreign_target_provenance_without_removing_evidence() {
    let root = tempfile::tempdir().expect("root");
    let record = cancelled_record(root.path());
    drop(NativeStore::open(record.spec.identity.destination_path(), 3).expect("destination"));
    embedding_provenance::write(
        record.spec.identity.destination_path(),
        &EmbeddingProvenance::new("foreign-model", 3),
    )
    .expect("foreign provenance");

    let error = remove_cancelled_artifacts(&record).expect_err("foreign target must refuse");

    assert!(error.to_string().contains("provenance"), "{error}");
    assert!(record.spec.identity.destination_path().exists());
    assert!(record.spec.workspace.exists());
}

#[cfg(unix)]
#[test]
fn cancellation_never_follows_a_destination_symlink() {
    use std::os::unix::fs::symlink;

    let root = tempfile::tempdir().expect("root");
    let record = cancelled_record(root.path());
    let foreign = root.path().join("foreign");
    std::fs::create_dir(&foreign).expect("foreign");
    std::fs::write(foreign.join("keep"), b"evidence").expect("marker");
    symlink(&foreign, record.spec.identity.destination_path()).expect("symlink");

    remove_cancelled_artifacts(&record).expect_err("symlink must refuse");

    assert_eq!(
        std::fs::read(foreign.join("keep")).expect("marker"),
        b"evidence"
    );
    assert!(record.spec.workspace.exists());
}

fn cancelled_record(root: &std::path::Path) -> JobRecord {
    let destination = root.join("source.online-migration-target");
    let workspace = root.join("source.online-migration-target.migration-journal");
    std::fs::create_dir(&workspace).expect("workspace");
    let identity = EpochIdentity::for_test(
        root.join("source"),
        "source-model",
        "target-model",
        3,
        &format!("sha256:{}", "ab".repeat(32)),
        destination,
        "00112233445566778899aabbccddeeff",
    );
    drop(DirtyJournal::open(&workspace, &identity, 1024 * 1024).expect("journal"));
    let mut record = JobRecord::new(JobSpec {
        identity,
        target_backend: "hash".to_owned(),
        journal_max_bytes: 1024 * 1024,
        catch_up: CatchUpConfig {
            fact_batch: 8,
            replay_batch: 8,
            edge_cap: 8,
        },
        controller: ControllerConfig {
            observation_window: 2,
            pause_budget: Duration::from_secs(1),
            verification_reserve: Duration::from_millis(10),
        },
        workspace,
    })
    .expect("record");
    record.transition(JobPhase::Cancelled).expect("cancel");
    record
}