ccd-cli 1.0.0-beta.2

Bootstrap and validate Continuous Context Development repositories
use anyhow::Result;
use rusqlite::{params, Connection, OptionalExtension};

#[derive(Debug, Clone)]
pub(crate) struct MemoryEvidenceRecord {
    pub(crate) id: String,
    pub(crate) scope: String,
    pub(crate) entry_type: String,
    pub(crate) source_kind: String,
    pub(crate) summary: String,
    pub(crate) summary_digest: String,
    pub(crate) observed_at_epoch_s: u64,
    pub(crate) actor_id: Option<String>,
    pub(crate) session_id: Option<String>,
    pub(crate) source_ref: Option<String>,
    pub(crate) host: Option<String>,
    pub(crate) host_hook: Option<String>,
    pub(crate) host_session_id: Option<String>,
    pub(crate) host_run_id: Option<String>,
    pub(crate) host_task_id: Option<String>,
    pub(crate) provider_name: Option<String>,
    pub(crate) provider_ref: Option<String>,
    pub(crate) extracted: bool,
    pub(crate) extracted_candidate_id: Option<String>,
    pub(crate) extracted_at_epoch_s: Option<u64>,
}

pub(crate) fn insert(conn: &Connection, record: &MemoryEvidenceRecord) -> Result<()> {
    conn.execute(
        "INSERT INTO memory_evidence
            (id, scope, entry_type, source_kind, summary, summary_digest, observed_at_epoch_s,
             actor_id, session_id, source_ref, host, host_hook, host_session_id, host_run_id,
             host_task_id, provider_name, provider_ref, extracted, extracted_candidate_id,
             extracted_at_epoch_s)
         VALUES
            (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18,
             ?19, ?20)",
        params![
            record.id,
            record.scope,
            record.entry_type,
            record.source_kind,
            record.summary,
            record.summary_digest,
            record.observed_at_epoch_s,
            record.actor_id,
            record.session_id,
            record.source_ref,
            record.host,
            record.host_hook,
            record.host_session_id,
            record.host_run_id,
            record.host_task_id,
            record.provider_name,
            record.provider_ref,
            i64::from(record.extracted),
            record.extracted_candidate_id,
            record.extracted_at_epoch_s,
        ],
    )?;
    Ok(())
}

pub(crate) fn get_by_id(conn: &Connection, id: &str) -> Result<Option<MemoryEvidenceRecord>> {
    conn.query_row(
        "SELECT id, scope, entry_type, source_kind, summary, summary_digest, observed_at_epoch_s,
                actor_id, session_id, source_ref, host, host_hook, host_session_id, host_run_id,
                host_task_id, provider_name, provider_ref, extracted, extracted_candidate_id,
                extracted_at_epoch_s
         FROM memory_evidence
         WHERE id = ?1",
        [id],
        row_to_record,
    )
    .optional()
    .map_err(Into::into)
}

pub(crate) fn list_pending(conn: &Connection, limit: usize) -> Result<Vec<MemoryEvidenceRecord>> {
    let mut stmt = conn.prepare(
        "SELECT id, scope, entry_type, source_kind, summary, summary_digest, observed_at_epoch_s,
                actor_id, session_id, source_ref, host, host_hook, host_session_id, host_run_id,
                host_task_id, provider_name, provider_ref, extracted, extracted_candidate_id,
                extracted_at_epoch_s
         FROM memory_evidence
         WHERE extracted = 0
         ORDER BY observed_at_epoch_s ASC, id ASC
         LIMIT ?1",
    )?;
    let rows = stmt.query_map([limit as i64], row_to_record)?;
    let mut records = Vec::new();
    for row in rows {
        records.push(row?);
    }
    Ok(records)
}

pub(crate) fn mark_extracted(
    conn: &Connection,
    id: &str,
    candidate_id: &str,
    extracted_at_epoch_s: u64,
) -> Result<()> {
    conn.execute(
        "UPDATE memory_evidence
         SET extracted = 1,
             extracted_candidate_id = ?2,
             extracted_at_epoch_s = ?3
         WHERE id = ?1",
        params![id, candidate_id, extracted_at_epoch_s],
    )?;
    Ok(())
}

fn row_to_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<MemoryEvidenceRecord> {
    Ok(MemoryEvidenceRecord {
        id: row.get(0)?,
        scope: row.get(1)?,
        entry_type: row.get(2)?,
        source_kind: row.get(3)?,
        summary: row.get(4)?,
        summary_digest: row.get(5)?,
        observed_at_epoch_s: row.get(6)?,
        actor_id: row.get(7)?,
        session_id: row.get(8)?,
        source_ref: row.get(9)?,
        host: row.get(10)?,
        host_hook: row.get(11)?,
        host_session_id: row.get(12)?,
        host_run_id: row.get(13)?,
        host_task_id: row.get(14)?,
        provider_name: row.get(15)?,
        provider_ref: row.get(16)?,
        extracted: row.get::<_, i64>(17)? != 0,
        extracted_candidate_id: row.get(18)?,
        extracted_at_epoch_s: row.get(19)?,
    })
}