klieo-runlog 2.2.0

Tier 2 observability — RunLog aggregate + replay engine for klieo agents.
Documentation
//! `SqliteRunLogStore` CRUD smoke tests. Gated on the `sqlite` feature.

#![cfg(feature = "sqlite")]

use klieo_core::ids::RunId;
use klieo_runlog::projector::project;
use klieo_runlog::store::RunLogStore;
use klieo_runlog::store_sqlite::SqliteRunLogStore;
use klieo_runlog::types::Cost;
use tempfile::NamedTempFile;

fn tmp_db() -> NamedTempFile {
    NamedTempFile::new().unwrap()
}

#[tokio::test]
async fn put_then_get_round_trip() {
    let f = tmp_db();
    let store = SqliteRunLogStore::new(f.path()).await.unwrap();
    let log = project(RunId::new(), "writer", &[]);
    let rid = log.run_id;
    store.put(&log).await.unwrap();
    let back = store.get(rid).await.unwrap().unwrap();
    assert_eq!(back.agent, "writer");
}

#[tokio::test]
async fn list_orders_recent_first_and_respects_limit() {
    let f = tmp_db();
    let store = SqliteRunLogStore::new(f.path()).await.unwrap();
    for _ in 0..5 {
        store
            .put(&project(RunId::new(), "writer", &[]))
            .await
            .unwrap();
    }
    let rows = store.list(None, 3).await.unwrap();
    assert_eq!(rows.len(), 3);
    for w in rows.windows(2) {
        assert!(w[0].started_at >= w[1].started_at);
    }
}

#[tokio::test]
async fn list_filtered_by_agent() {
    let f = tmp_db();
    let store = SqliteRunLogStore::new(f.path()).await.unwrap();
    store
        .put(&project(RunId::new(), "writer", &[]))
        .await
        .unwrap();
    store
        .put(&project(RunId::new(), "editor", &[]))
        .await
        .unwrap();
    let rows = store.list(Some("writer"), 10).await.unwrap();
    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].agent, "writer");
}

#[tokio::test]
async fn delete_removes_log() {
    let f = tmp_db();
    let store = SqliteRunLogStore::new(f.path()).await.unwrap();
    let log = project(RunId::new(), "writer", &[]);
    let rid = log.run_id;
    store.put(&log).await.unwrap();
    store.delete(rid).await.unwrap();
    assert!(store.get(rid).await.unwrap().is_none());
}

#[tokio::test]
async fn cost_estimate_round_trips_prompt_and_completion_split() {
    let f = tmp_db();
    let store = SqliteRunLogStore::new(f.path()).await.unwrap();
    let mut log = project(RunId::new(), "writer", &[]);
    log.cost_estimate = Some(Cost {
        prompt_usd: 0.005,
        completion_usd: 0.015,
        total_usd: 0.020,
    });
    let rid = log.run_id;
    store.put(&log).await.unwrap();
    let back = store.get(rid).await.unwrap().expect("row exists");
    let cost = back.cost_estimate.expect("cost preserved");
    assert!((cost.prompt_usd - 0.005).abs() < 1e-12);
    assert!((cost.completion_usd - 0.015).abs() < 1e-12);
    assert!((cost.total_usd - 0.020).abs() < 1e-12);
}