libnoa 0.1.1

AI-native distributed version control system with per-agent workspace isolation, JSONL append-only logs, snapshot-based history, and full git protocol compatibility
Documentation
use crate::{
    error::Result,
    log::AgentLog,
    object::ObjectStore,
    snapshot::{SnapshotEngine, SnapshotId, SnapshotStore},
};

pub struct Consolidator<'a, L: AgentLog, S: SnapshotStore, O: ObjectStore> {
    engine: &'a SnapshotEngine<L, S, O>,
}

impl<'a, L: AgentLog, S: SnapshotStore, O: ObjectStore> Consolidator<'a, L, S, O> {
    pub fn new(engine: &'a SnapshotEngine<L, S, O>) -> Self {
        Consolidator { engine }
    }

    pub async fn consolidate(
        &self,
        workspace: &str,
        parent_ids: Vec<SnapshotId>,
        author: &str,
        message: &str,
    ) -> Result<crate::snapshot::Snapshot> {
        self.engine
            .compute(workspace, parent_ids, 0, author, message)
            .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::log::{FileAgentLog, LogEntry, OpType};
    use crate::object::RedbObjectStore;
    use crate::snapshot::RedbSnapshotStore;
    use std::sync::Arc;
    use tempfile::TempDir;

    async fn make_engine() -> (
        TempDir,
        SnapshotEngine<FileAgentLog, RedbSnapshotStore, RedbObjectStore>,
    ) {
        let tmp = TempDir::new().unwrap();
        let db = Arc::new(
            redb::Database::builder()
                .create(tmp.path().join("test.redb"))
                .unwrap(),
        );
        let log = FileAgentLog::create(&tmp.path().join("test.log")).unwrap();
        let snapshot_store = RedbSnapshotStore::new(Arc::clone(&db)).unwrap();
        let object_store = RedbObjectStore::new(db).unwrap();
        (tmp, SnapshotEngine::new(log, snapshot_store, object_store))
    }

    #[tokio::test]
    async fn test_consolidate_builds_snapshot() {
        let (_tmp, engine) = make_engine().await;
        let consolidator = Consolidator::new(&engine);

        let e1 = LogEntry {
            seq: 2,
            op: OpType::Write,
            path: Some("b.rs".to_string()),
            blob_id: Some("h2".to_string()),
            from_path: None,
            resolved_conflict_ours_id: None,
            resolved_conflict_theirs_id: None,
            snapshot_id: None,
            ts: 200,
            message: None,
        };
        let e2 = LogEntry {
            seq: 1,
            op: OpType::Write,
            path: Some("a.rs".to_string()),
            blob_id: Some("h1".to_string()),
            from_path: None,
            resolved_conflict_ours_id: None,
            resolved_conflict_theirs_id: None,
            snapshot_id: None,
            ts: 100,
            message: None,
        };
        engine.log.append(&e1).await.unwrap();
        engine.log.append(&e2).await.unwrap();

        let snap = consolidator
            .consolidate("ws1", vec![], "agent", "consolidated")
            .await
            .unwrap();

        let tree = engine
            .object_store
            .get_tree(&crate::object::TreeId(snap.tree_hash))
            .await
            .unwrap();
        assert_eq!(tree.0.len(), 2);
    }
}