khive-vcs 0.4.0

KG versioning — git-native core types, canonical hash, and NDJSON-to-SQLite sync (ADR-010/ADR-020)
Documentation

khive-vcs

KG versioning — content-addressed snapshot hashing and NDJSON-to-SQLite sync.

Git-native v1: KG state lives as sorted NDJSON files (entities.ndjson, edges.ndjson) in a git repository. This crate provides the canonical hash that identifies a snapshot's content, the sync routine that rebuilds a queryable SQLite database from those NDJSON files, and remote-archive fetch with hash-pin verification. Branching, history, and push/pull are git's job — there is no custom khive remote protocol.

Features

  • Canonical SHA-256 snapshot hashing — deterministic, order-independent content hash over entities and edges (SnapshotId, prefixed sha256:<hex64>)
  • NDJSON-to-SQLite sync — atomic rebuild of a working database from NDJSON sources
  • Remote archive fetch — sparse git clone of a remote KG archive with SHA-256 pin verification, fail-closed on mismatch
  • Validated wire typesSnapshotId and its Deserialize impl reject anything that isn't exactly sha256: + 64 lower-case hex characters

Usage

use khive_runtime::portability::KgArchive;
use khive_vcs::hash::snapshot_id_for_archive;

fn print_snapshot_id(archive: &KgArchive) {
    // Deterministic regardless of entity/edge insertion order or property key order.
    let id = snapshot_id_for_archive(archive).expect("archive hashes cleanly");
    println!("{id}"); // "sha256:<64 hex chars>"
}

Rebuilding a working database from NDJSON sources (used by kkernel sync):

use std::path::Path;

async fn rebuild(repo_root: &Path, db_path: &Path) -> anyhow::Result<()> {
    let report = khive_vcs::sync::run_sync(repo_root, db_path, "local").await?;
    println!("{} entities, {} edges -> {}", report.entities, report.edges, report.db_path);
    Ok(())
}

run_sync reads .khive/kg/{entities,edges}.ndjson under repo_root, validates every edge relation before touching disk, builds the new database in a .tmp sibling file, and renames it over db_path only on success — a crash or parse error leaves the previous database intact. run_sync_remote(repo_root, &RemoteConfig, repin) fetches a remote KG archive (sparse, depth-1 clone), verifies its content hash against RemoteConfig::pin when set, and populates .khive/kg/remotes/<name>/ with a meta.json recording the resolved commit and hash.

Semantics

  • SnapshotId::from_hash / from_prefixed are the only ways to construct one outside snapshot_id_for_archive; both reject anything but 64 lower-case hex characters.
  • canonical_json sorts entities by UUID, edges by (source, target, relation), tags lexicographically, and object keys recursively — two archives differing only in insertion order hash identically; two differing in entity_type or edge weight do not.
  • VcsState tracks namespace, current_branch, and last_committed_id — there is no dirty flag; uncommitted changes are computed fresh via a DB-vs-NDJSON diff, not cached.
  • KG_V1_COVERAGE records that v1 snapshots cover entities and edges only; notes are excluded pending a note pack that defines versioned export/import/redaction/merge.

Where this sits

khive-vcs depends on khive-runtime, khive-storage, and khive-types, and is a regular workspace member and published crate — not forward-deployed. kkernel consumes it directly for the sync, kg status, and kg fetch subcommands.

khive-merge is the forward-deployed v2 semantic merge layer that will consume khive-vcs's snapshot ancestry once ADR-039's LCA-walk integration lands; today's merge path is git's own line-merge over sorted NDJSON.

Governed by ADR-010 (versioning strategy) and ADR-020 (git-native implementation).

License

Apache-2.0.