kcode-kweb-db 1.0.1

A disk-first convergent signed-DAG store for Kweb nodes and objects
Documentation
use chrono::Utc;
use kcode_kweb_db::{Config, Error, KwebDb, NodeData, NoopGossip, Owner, Provenance, WriterId};
use std::{fs, sync::Arc};

fn provenance(label: &str) -> Provenance {
    Provenance {
        author: "Recovery".into(),
        source: label.into(),
        source_created_at: Utc::now(),
        data: String::new(),
    }
}

fn data(name: &str) -> NodeData {
    NodeData {
        short_name: name.into(),
        short_description: String::new(),
        long_description: String::new(),
        owner: Owner::SelfNode,
        fixed_connections: Vec::new(),
        recent_connections: Vec::new(),
        objects: Vec::new(),
    }
}

fn config(key: [u8; 32]) -> Config {
    Config {
        signing_key: key,
        writers_by_priority: vec![WriterId::from_signing_key(&key)],
        gossip: Arc::new(NoopGossip),
    }
}

#[test]
fn normal_open_does_not_scan_log_or_rebuild_nodes() {
    let root = tempfile::tempdir().unwrap();
    let key = [31; 32];
    let database = KwebDb::open(root.path(), config(key)).unwrap();
    let mut transaction = database.start_transaction(provenance("create")).unwrap();
    let node = transaction.create_node(data("Durable node")).unwrap();
    transaction.finalize().unwrap();
    drop(database);

    fs::write(
        root.path().join("transactions.kwl"),
        b"not scanned at ordinary open",
    )
    .unwrap();
    let reopened = KwebDb::open(root.path(), config(key)).unwrap();
    assert_eq!(
        reopened.get_node(node).unwrap().data.short_name,
        "Durable node"
    );
}

#[test]
fn corrupt_or_missing_authoritative_node_fails_on_access() {
    let root = tempfile::tempdir().unwrap();
    let key = [32; 32];
    let database = KwebDb::open(root.path(), config(key)).unwrap();
    let mut transaction = database.start_transaction(provenance("create")).unwrap();
    let node = transaction.create_node(data("Corrupt node")).unwrap();
    transaction.finalize().unwrap();
    drop(database);

    let text = node.to_string();
    let path = root
        .path()
        .join("nodes")
        .join(&text[..2])
        .join(format!("{}.kwn", &text[2..]));
    fs::remove_file(path).unwrap();
    let reopened = KwebDb::open(root.path(), config(key)).unwrap();
    assert!(matches!(reopened.get_node(node), Err(Error::Corrupt(_))));
}

#[test]
fn legacy_root_requires_offline_upgrade_and_lock_is_exclusive() {
    let legacy = tempfile::tempdir().unwrap();
    fs::write(legacy.path().join("ledger.kwl"), b"v1").unwrap();
    assert!(matches!(
        KwebDb::open(legacy.path(), config([33; 32])),
        Err(Error::OfflineUpgradeRequired(_))
    ));

    let root = tempfile::tempdir().unwrap();
    let first = KwebDb::open(root.path(), config([34; 32])).unwrap();
    assert!(matches!(
        KwebDb::open(root.path(), config([34; 32])),
        Err(Error::Busy(_))
    ));
    drop(first);
    assert!(KwebDb::open(root.path(), config([34; 32])).is_ok());
}

#[test]
fn writer_priority_is_part_of_the_persisted_root_contract() {
    let root = tempfile::tempdir().unwrap();
    let local_key = [35; 32];
    let local_writer = WriterId::from_signing_key(&local_key);
    let other_writer = WriterId::from_signing_key(&[36; 32]);
    let original = Config {
        signing_key: local_key,
        writers_by_priority: vec![local_writer, other_writer],
        gossip: Arc::new(NoopGossip),
    };
    drop(KwebDb::open(root.path(), original).unwrap());

    let reordered = Config {
        signing_key: local_key,
        writers_by_priority: vec![other_writer, local_writer],
        gossip: Arc::new(NoopGossip),
    };
    assert!(matches!(
        KwebDb::open(root.path(), reordered),
        Err(Error::InvalidConfig(_))
    ));
}