remem-ai 0.6.88

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use anyhow::{Context, Result};
use rusqlite::Connection;

use super::SchemaInvariant;

mod shape;
use shape::{require_create_sql, require_foreign_key, require_unique_columns};

const V084_MIGRATION_SQL: &str = include_str!("../../../migrations/v084_session_observatory.sql");

pub(in crate::migrate) const V084_SCHEMA_INVARIANTS: &[SchemaInvariant] = &[
    SchemaInvariant::table(84, "session_observatory", "session_turns"),
    SchemaInvariant::table(84, "session_observatory", "session_turn_actions"),
    SchemaInvariant::index(84, "session_observatory", "idx_session_turns_recent"),
    SchemaInvariant::index(
        84,
        "session_observatory",
        "idx_session_turns_project_recent",
    ),
    SchemaInvariant::index(84, "session_observatory", "idx_session_turns_session"),
    SchemaInvariant::index(84, "session_observatory", "idx_session_turns_identity"),
    SchemaInvariant::index(84, "session_observatory", "idx_session_turn_actions_turn"),
    SchemaInvariant::index(84, "session_observatory", "idx_session_turn_actions_event"),
    SchemaInvariant::index(
        84,
        "session_observatory",
        "idx_raw_messages_activity_tuple_recent",
    ),
    SchemaInvariant::index(
        84,
        "session_observatory",
        "idx_raw_messages_activity_recent",
    ),
    SchemaInvariant::index(
        84,
        "session_observatory",
        "idx_raw_messages_project_activity_recent",
    ),
];

pub(in crate::migrate) fn v084_critical_shape_findings(conn: &Connection) -> Result<Vec<String>> {
    let mut findings = Vec::new();
    for table in ["session_turns", "session_turn_actions"] {
        require_create_sql(
            conn,
            &mut findings,
            "table",
            table,
            canonical_statement("CREATE TABLE", table)?,
        )?;
    }
    for index in [
        "idx_session_turns_recent",
        "idx_session_turns_project_recent",
        "idx_session_turns_session",
        "idx_session_turns_identity",
        "idx_session_turn_actions_turn",
        "idx_session_turn_actions_event",
        "idx_raw_messages_activity_tuple_recent",
        "idx_raw_messages_activity_recent",
        "idx_raw_messages_project_activity_recent",
    ] {
        require_create_sql(
            conn,
            &mut findings,
            "index",
            index,
            canonical_statement("CREATE INDEX", index)?,
        )?;
    }
    require_unique_columns(
        conn,
        &mut findings,
        "session_turns",
        &["source_root", "project", "session_id", "turn_index"],
    )?;
    require_unique_columns(
        conn,
        &mut findings,
        "session_turn_actions",
        &["session_turn_id", "action_index"],
    )?;

    for (table, from, target, to, on_delete) in [
        (
            "session_turns",
            "transcript_identity_id",
            "raw_session_identities",
            "id",
            "RESTRICT",
        ),
        (
            "session_turns",
            "session_row_id",
            "sessions",
            "id",
            "SET NULL",
        ),
        (
            "session_turns",
            "user_message_id",
            "raw_messages",
            "id",
            "CASCADE",
        ),
        (
            "session_turns",
            "understanding_message_id",
            "raw_messages",
            "id",
            "SET NULL",
        ),
        (
            "session_turns",
            "result_message_id",
            "raw_messages",
            "id",
            "SET NULL",
        ),
        (
            "session_turn_actions",
            "session_turn_id",
            "session_turns",
            "id",
            "CASCADE",
        ),
        (
            "session_turn_actions",
            "event_row_id",
            "captured_events",
            "id",
            "SET NULL",
        ),
    ] {
        require_foreign_key(conn, &mut findings, table, from, target, to, on_delete)?;
    }

    Ok(findings)
}

fn canonical_statement(object_type: &str, name: &str) -> Result<&'static str> {
    let prefix = format!("{object_type} {name}");
    V084_MIGRATION_SQL
        .split(';')
        .map(str::trim)
        .find_map(|statement| statement.find(&prefix).map(|offset| &statement[offset..]))
        .with_context(|| format!("v084 migration is missing canonical {object_type} {name}"))
}