harn-vm 0.7.44

Async bytecode virtual machine for the Harn programming language
Documentation
use std::process::Command;

use ed25519_dalek::SigningKey;
use harn_vm::flow::{Atom, Provenance, ShadowGitBackend, TextOp, VcsBackend};
use tempfile::TempDir;
use time::OffsetDateTime;

fn git_available() -> bool {
    Command::new("git").arg("--version").output().is_ok()
}

fn run_git(root: &TempDir, args: &[&str]) -> String {
    let output = Command::new("git")
        .current_dir(root.path())
        .args(args)
        .env_remove("GIT_DIR")
        .env_remove("GIT_WORK_TREE")
        .env_remove("GIT_COMMON_DIR")
        .env_remove("GIT_INDEX_FILE")
        .env_remove("GIT_PREFIX")
        .output()
        .expect("run git");
    assert!(
        output.status.success(),
        "git {:?} failed: {}",
        args,
        String::from_utf8_lossy(&output.stderr)
    );
    String::from_utf8_lossy(&output.stdout).trim().to_string()
}

fn init_repo() -> TempDir {
    let repo = tempfile::tempdir().expect("tempdir");
    run_git(&repo, &["init"]);
    repo
}

fn signing_key(seed: u8) -> SigningKey {
    SigningKey::from_bytes(&[seed; 32])
}

fn atom(index: usize, parent: Option<harn_vm::flow::AtomId>) -> Atom {
    let principal = signing_key(1);
    let persona = signing_key(2);
    Atom::sign(
        vec![TextOp::Insert {
            offset: index as u64,
            content: format!("atom-{index}\n"),
        }],
        parent.into_iter().collect(),
        Provenance {
            principal: "user:alice".to_string(),
            persona: "ship-captain".to_string(),
            agent_run_id: "run-flow-backend".to_string(),
            tool_call_id: Some(format!("tool-{index}")),
            trace_id: format!("trace-{index}"),
            transcript_ref: "transcript:test".to_string(),
            timestamp: OffsetDateTime::from_unix_timestamp(1_777_000_000 + index as i64).unwrap(),
        },
        None,
        &principal,
        &persona,
    )
    .expect("sign atom")
}

#[test]
fn shadow_git_emits_atoms_as_sidecar_commits() {
    if !git_available() {
        return;
    }

    let repo = init_repo();
    let backend = ShadowGitBackend::new(repo.path()).expect("backend");
    let mut atoms = Vec::new();
    let mut parent = None;

    for index in 0..3 {
        let next = atom(index, parent);
        let atom_ref = backend.emit_atom(&next).expect("emit atom");
        assert_eq!(atom_ref.atom_id, next.id);
        assert_eq!(atom_ref.ref_name, format!("refs/flow/atoms/{}", next.id));
        let commit = run_git(
            &repo,
            &[
                "rev-parse",
                &format!("refs/flow/atoms/{}^{{commit}}", next.id),
            ],
        );
        assert_eq!(commit, atom_ref.commit);
        let payload = run_git(&repo, &["show", &format!("{commit}:atom.json")]);
        let decoded = Atom::from_json_slice(payload.as_bytes()).expect("atom payload");
        assert_eq!(decoded.id, next.id);
        parent = Some(next.id);
        atoms.push(next);
    }

    let listed = backend.list_atoms().expect("list atoms");
    assert_eq!(listed.len(), 3);

    let atom_ids = atoms.iter().map(|atom| atom.id).collect::<Vec<_>>();
    let slice = backend
        .derive_slice(&[*atom_ids.last().expect("ten atom ids")])
        .expect("derive slice");
    assert_eq!(slice.atoms, atom_ids);
    let replayed = backend.replay_slice(&slice).expect("replay slice");
    assert_eq!(
        replayed.iter().map(|atom| atom.id).collect::<Vec<_>>(),
        atom_ids
    );

    let receipt = backend.ship_slice(&slice).expect("ship slice");
    assert_eq!(receipt.ref_name, format!("refs/flow/slices/{}", slice.id));
    let exported = backend
        .export_git(&slice, "refs/heads/flow-export")
        .expect("export git");
    assert_eq!(exported.commit, receipt.commit);

    let imported = backend
        .import_git("refs/heads/flow-export")
        .expect("import git");
    assert_eq!(imported.atoms, atom_ids);
}