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

use super::{open_controller, sample, ControllerConfig, ConvergenceController, EPOCH};

const STATE_FILE: &str = "online-migration-controller.json";
const STAGING_FILE: &str = "online-migration-controller.json.tmp";

#[test]
fn controller_configuration_bounds_memory_and_pause_inputs() {
    let root = tempfile::tempdir().expect("root");
    for observation_window in [0, 1, 65] {
        assert!(ConvergenceController::open(
            root.path(),
            EPOCH,
            ControllerConfig {
                observation_window,
                pause_budget: Duration::from_secs(1),
                verification_reserve: Duration::ZERO,
            },
        )
        .is_err());
    }
    assert!(ConvergenceController::open(
        root.path(),
        EPOCH,
        ControllerConfig {
            observation_window: 2,
            pause_budget: Duration::ZERO,
            verification_reserve: Duration::ZERO,
        },
    )
    .is_err());
}

#[test]
fn corrupted_or_epoch_mismatched_state_fails_closed() {
    let root = tempfile::tempdir().expect("root");
    drop(open_controller(root.path()));
    let path = root.path().join(STATE_FILE);
    let original = fs::read_to_string(&path).expect("state");

    fs::write(&path, "{truncated").expect("corrupt");
    let corrupt = ConvergenceController::open(root.path(), EPOCH, config())
        .err()
        .expect("corrupt");
    assert!(corrupt.to_string().contains("invalid controller state"));

    fs::write(&path, original).expect("restore");
    let mismatch =
        ConvergenceController::open(root.path(), "ffeeddccbbaa99887766554433221100", config())
            .err()
            .expect("epoch mismatch");
    assert!(mismatch.to_string().contains("epoch ownership"));
}

#[test]
fn activated_state_without_a_cutover_ready_window_fails_closed() {
    let root = tempfile::tempdir().expect("root");
    drop(open_controller(root.path()));
    let path = root.path().join(STATE_FILE);
    let mut state: serde_json::Value =
        serde_json::from_slice(&fs::read(&path).expect("state")).expect("json");
    state["phase"] = serde_json::Value::String("Activated".to_owned());
    fs::write(&path, serde_json::to_vec_pretty(&state).expect("encode")).expect("tamper");

    let error = ConvergenceController::open(root.path(), EPOCH, config())
        .err()
        .expect("invalid activated state");
    assert!(error.to_string().contains("activated state"), "{error}");
}

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

    let root = tempfile::tempdir().expect("root");
    let victim = root.path().join("victim");
    fs::write(&victim, b"untouched").expect("victim");
    symlink(&victim, root.path().join(STATE_FILE)).expect("symlink");

    let error = ConvergenceController::open(root.path(), EPOCH, config())
        .err()
        .expect("symlink");
    assert!(error.to_string().contains("regular file"), "{error}");
    assert_eq!(fs::read(&victim).expect("victim"), b"untouched");
}

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

    let root = tempfile::tempdir().expect("root");
    let mut controller = open_controller(root.path());
    let victim = root.path().join("victim");
    fs::write(&victim, b"untouched").expect("victim");
    symlink(&victim, root.path().join(STAGING_FILE)).expect("symlink");

    controller
        .observe(sample(0, 10, 10))
        .expect_err("save fails");
    assert_eq!(controller.retained_samples(), 0);
    assert_eq!(fs::read(&victim).expect("victim"), b"untouched");
}

#[test]
fn durable_state_contains_only_bounded_controller_facts() {
    let root = tempfile::tempdir().expect("root");
    let mut controller = open_controller(root.path());
    for second in 0..100 {
        controller
            .observe(sample(second, second, second))
            .expect("observe");
    }
    let bytes = fs::read(root.path().join(STATE_FILE)).expect("state");
    assert!(bytes.len() < 64 * 1024);
    assert_eq!(controller.retained_samples(), 3);
    let text = String::from_utf8(bytes).expect("utf8");
    assert!(!text.contains("source_path"));
    assert!(!text.contains("destination_path"));
    assert!(!text.contains("credential"));
}

fn config() -> ControllerConfig {
    ControllerConfig {
        observation_window: 3,
        pause_budget: Duration::from_millis(500),
        verification_reserve: Duration::from_millis(50),
    }
}