remem-ai 0.6.81

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use super::*;
use rusqlite::params;

fn confidence_test_weights() -> SearchWeights {
    SearchWeights {
        graph: 0.0,
        fact: 0.0,
        max_vector_distance: -1.0,
        ..SearchWeights::default()
    }
}

fn fact_confidence_test_weights() -> SearchWeights {
    SearchWeights {
        graph: 0.0,
        max_vector_distance: -1.0,
        ..SearchWeights::default()
    }
}

fn insert_scope_fixture_memory(
    conn: &Connection,
    project: &str,
    title: &str,
    text: &str,
    scope: &str,
) -> Result<i64> {
    crate::memory::insert_memory_full(
        conn, None, project, None, title, text, "decision", None, None, scope, None,
    )
}

#[test]
fn ordered_load_keeps_global_overlay_and_drops_foreign_project_scope() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    crate::memory::tests_helper::setup_memory_schema(&conn);
    let local = insert_scope_fixture_memory(&conn, "/repo", "local", "local", "project")?;
    let global = insert_scope_fixture_memory(&conn, "/other", "global", "global", "global")?;
    let foreign = insert_scope_fixture_memory(&conn, "/other", "foreign", "foreign", "project")?;

    let loaded = load_ordered_memories(&conn, &[local, global, foreign], Some("/repo"), false)?;
    assert_eq!(
        loaded.iter().map(|memory| memory.id).collect::<Vec<_>>(),
        vec![local, global]
    );
    Ok(())
}

#[test]
fn project_and_destination_entities_cannot_bind_a_different_subject() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    crate::memory::tests_helper::setup_memory_schema(&conn);
    insert_scope_fixture_memory(
        &conn,
        "synthetic/kestrelnook",
        "KestrelNook NebulaLatch Owner",
        "NebulaLatch is owned by Team Mica.",
        "project",
    )?;
    insert_scope_fixture_memory(
        &conn,
        "synthetic/kestrelnook",
        "KestrelNook Oracle Cloud Migration",
        "Project KestrelNook migrated ArchiveFox to Oracle Cloud.",
        "project",
    )?;

    let result = search_with_query_weights(
        &conn,
        "Has Project KestrelNook migrated NebulaLatch to Oracle Cloud?",
        Some("synthetic/kestrelnook"),
        None,
        5,
        0,
        false,
        None,
        false,
        confidence_test_weights(),
    )?;

    assert!(result.is_empty(), "{result:#?}");
    Ok(())
}

#[test]
fn title_case_predicate_remains_required_claim_evidence() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    crate::memory::tests_helper::setup_memory_schema(&conn);
    insert_scope_fixture_memory(
        &conn,
        "/repo",
        "NebulaLatch Ownership",
        "NebulaLatch is owned by Team Mica.",
        "project",
    )?;

    let result = search_with_query_weights(
        &conn,
        "Who Maintains NebulaLatch?",
        Some("/repo"),
        None,
        5,
        0,
        false,
        None,
        false,
        confidence_test_weights(),
    )?;

    assert!(result.is_empty(), "{result:#?}");
    Ok(())
}

#[test]
fn handles_predicate_rejects_unrelated_entity_content() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    crate::memory::tests_helper::setup_memory_schema(&conn);
    insert_scope_fixture_memory(
        &conn,
        "/repo",
        "NebulaLatch Storage",
        "NebulaLatch uses SQLite WAL mode.",
        "project",
    )?;

    let result = search_with_query_weights(
        &conn,
        "Who handles NebulaLatch?",
        Some("/repo"),
        None,
        5,
        0,
        false,
        None,
        false,
        confidence_test_weights(),
    )?;

    assert!(result.is_empty(), "{result:#?}");
    Ok(())
}

#[test]
fn indexed_title_case_relation_does_not_override_query_subject() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    crate::memory::tests_helper::setup_memory_schema(&conn);
    let relation_memory = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Handles escalation",
        "Handles release escalation for another service.",
        "project",
    )?;
    let expected = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Rust ownership",
        "Rust is handled by Team Ferris.",
        "project",
    )?;
    let other_subject = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Rust tooling",
        "Rust uses Cargo for builds.",
        "project",
    )?;
    crate::retrieval::entity::link_entities(&conn, relation_memory, &["Handles".to_string()])?;
    crate::retrieval::entity::link_entities(&conn, expected, &["Rust".to_string()])?;
    crate::retrieval::entity::link_entities(&conn, other_subject, &["Rust".to_string()])?;

    let result = search_with_query_weights(
        &conn,
        "Who Handles Rust?",
        Some("/repo"),
        None,
        5,
        0,
        false,
        None,
        false,
        confidence_test_weights(),
    )?;

    assert_eq!(
        result.iter().map(|memory| memory.id).collect::<Vec<_>>(),
        vec![expected],
        "{result:#?}"
    );
    Ok(())
}

#[test]
fn common_title_entities_cannot_bridge_an_unrelated_candidate() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    crate::memory::tests_helper::setup_memory_schema(&conn);
    let anchor = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Current NebulaLatch Quorum",
        "Current NebulaLatch quorum is 7.",
        "project",
    )?;
    let unrelated = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Current OtherService Quorum",
        "Current OtherService quorum is 3.",
        "project",
    )?;

    let result = search_with_query_weights(
        &conn,
        "Current NebulaLatch quorum?",
        Some("/repo"),
        None,
        5,
        0,
        false,
        None,
        false,
        confidence_test_weights(),
    )?;
    let ids = result.iter().map(|memory| memory.id).collect::<Vec<_>>();

    assert!(ids.contains(&anchor), "{result:#?}");
    assert!(!ids.contains(&unrelated), "{result:#?}");
    Ok(())
}

#[test]
fn fact_query_requires_primary_and_secondary_entities_in_the_same_fact() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    crate::migrate::run_migrations(&conn)?;
    let expected = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Opaque source alpha",
        "Details live only in structured storage.",
        "project",
    )?;
    let wrong_subject = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Opaque source beta",
        "Different details live only in structured storage.",
        "project",
    )?;
    let wrong_object = insert_scope_fixture_memory(
        &conn,
        "/repo",
        "Opaque source gamma",
        "More details live only in structured storage.",
        "project",
    )?;
    let now = chrono::Utc::now().timestamp();
    conn.execute(
        "INSERT INTO memory_facts
         (project, subject, predicate, object, learned_at_epoch, source_memory_id,
          source_event_ids, confidence, status, created_at_epoch, updated_at_epoch)
         VALUES ('/repo', 'HarborMint', 'verified_by', 'Toma Reed', ?1, ?2, '[]', 0.9,
                 'active', ?1, ?1),
                ('/repo', 'OtherService', 'verified_by', 'Toma Reed', ?1, ?3, '[]', 0.9,
                 'active', ?1, ?1),
                ('/repo', 'HarborMint', 'verified_by', 'Mira Lane', ?1, ?4, '[]', 0.9,
                 'active', ?1, ?1)",
        params![now - 10, expected, wrong_subject, wrong_object],
    )?;

    for query in [
        "Who verified HarborMint with Toma Reed?",
        "who verified harbormint with toma reed?",
    ] {
        let result = search_with_query_weights(
            &conn,
            query,
            Some("/repo"),
            None,
            5,
            0,
            false,
            None,
            false,
            fact_confidence_test_weights(),
        )?;
        let ids = result.iter().map(|memory| memory.id).collect::<Vec<_>>();

        assert_eq!(ids, vec![expected], "query={query}: {result:#?}");
    }
    Ok(())
}