lean-ctx 3.9.19

Context Runtime for AI Agents with CCP. 79 MCP tools, 10 read modes, 95+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24+ AI tools. Reduces LLM token consumption by up to 99%.
//! One-way migration helpers for legacy Savings Ledger observations.
//!
//! Migration only reads the source ledger.  The source file is never opened for
//! writing and its signed/hash-chained bytes are not rewritten or re-chained.

use std::path::Path;

use super::Result;
use super::event::ExecutionEvent;
use super::store::ExecutionLedgerStore;

const LEGACY_TASK_ID: &str = "legacy-savings-ledger";
const LEGACY_TRACE_ID: &str = "legacy-savings-ledger";

/// Migrates the configured Savings Ledger into the configured execution ledger.
///
/// Legacy savings entries did not always have task identity.  Such entries are
/// attached to the stable compatibility task/trace IDs above; an existing
/// `SavingsEvent::trace_id` is retained when present.  The original entry hash is
/// carried in the generated plan reference as a payload-free evidence reference.
pub fn migrate_from_savings_ledger() -> Result<usize> {
    let Some(source_path) = crate::core::savings_ledger::store::default_path() else {
        return Ok(0);
    };
    let destination = ExecutionLedgerStore::from_default()?;
    migrate_from_savings_ledger_at(&source_path, &destination, LEGACY_TASK_ID, LEGACY_TRACE_ID)
}

/// Migrates one source file into an explicitly selected destination store.
///
/// The return value is the number of Savings Ledger entries converted, not the
/// number of execution events appended (each source entry produces a plan
/// reference and a model-observation event).
pub fn migrate_from_savings_ledger_at(
    source_path: &Path,
    destination: &ExecutionLedgerStore,
    task_id: &str,
    trace_id: &str,
) -> Result<usize> {
    let source_events = crate::core::savings_ledger::store::load(source_path);
    let mut migrated = 0;

    for (index, source) in source_events.iter().enumerate() {
        let evidence_ref = format!("savings-ledger:sha256:{}", source.entry_hash);
        let plan_id = format!("legacy-savings-plan:{index}");
        let event_trace_id = source
            .trace_id
            .as_deref()
            .filter(|value| !value.is_empty())
            .unwrap_or(trace_id)
            .to_owned();

        destination.append(ExecutionEvent::PlanCreated {
            task_id: task_id.to_owned(),
            trace_id: event_trace_id.clone(),
            plan_id: plan_id.clone(),
            plan_ref: evidence_ref,
            timestamp: source.ts.clone(),
            sequence_number: 0,
            prev_hash: String::new(),
        })?;
        destination.append(ExecutionEvent::ModelInvoked {
            task_id: task_id.to_owned(),
            trace_id: event_trace_id,
            plan_id,
            model: source.model_id.clone(),
            provider: "savings-ledger".to_owned(),
            tokens_in: source.actual_tokens,
            tokens_out: source.response_delivered_tokens.unwrap_or(0),
            latency_ms: 0,
            timestamp: source.ts.clone(),
            sequence_number: 0,
            prev_hash: String::new(),
        })?;
        migrated += 1;
    }

    Ok(migrated)
}