harn-vm 0.10.116

Async bytecode virtual machine for the Harn programming language
Documentation
use super::*;

fn durable_inventory(root: &Path) -> Vec<(PathBuf, u64, String)> {
    fn visit(root: &Path, path: &Path, out: &mut Vec<(PathBuf, u64, String)>) {
        for entry in fs::read_dir(path).unwrap() {
            let entry = entry.unwrap();
            let child = entry.path();
            if entry.file_type().unwrap().is_dir() {
                visit(root, &child, out);
            } else {
                let bytes = fs::read(&child).unwrap();
                out.push((
                    child.strip_prefix(root).unwrap().to_path_buf(),
                    bytes.len() as u64,
                    hex::encode(Sha256::digest(&bytes)),
                ));
            }
        }
    }

    let mut out = Vec::new();
    if root.is_dir() {
        visit(root, root, &mut out);
    }
    out.sort_by(|left, right| left.0.cmp(&right.0));
    out
}

fn root_options(root: &Path) -> VmValue {
    json_to_vm_value(&json!({"root": root.to_string_lossy()}))
}

#[tokio::test(flavor = "current_thread")]
async fn missing_and_malformed_read_surfaces_do_not_materialize_state() {
    let dir = tempfile::tempdir().unwrap();
    let options = root_options(dir.path());
    let ctx = || crate::vm::AsyncBuiltinCtx::for_test(crate::vm::Vm::new());

    assert!(matches!(
        session_store_list_impl(ctx(), vec![options.clone()]).await,
        Ok(VmValue::List(items)) if items.is_empty()
    ));
    assert!(matches!(
        session_store_events_impl(
            ctx(),
            vec![VmValue::String(arcstr::ArcStr::from("missing")), options.clone()],
        )
        .await,
        Ok(VmValue::List(items)) if items.is_empty()
    ));
    session_store_verify_impl(
        ctx(),
        vec![
            VmValue::String(arcstr::ArcStr::from("missing")),
            options.clone(),
        ],
    )
    .await
    .unwrap();
    let search_options = json_to_vm_value(&json!({
        "root": dir.path().to_string_lossy(),
        "project_scope": "missing-project",
    }));
    session_store_search_impl(
        ctx(),
        vec![
            VmValue::String(arcstr::ArcStr::from("anything")),
            search_options,
        ],
    )
    .await
    .unwrap();
    assert!(durable_inventory(dir.path()).is_empty());

    let malformed = json_to_vm_value(&json!({
        "root": dir.path().to_string_lossy(),
        "status": "not-a-status",
    }));
    assert!(session_store_list_impl(ctx(), vec![malformed])
        .await
        .is_err());
    assert!(durable_inventory(dir.path()).is_empty());
}

#[tokio::test(flavor = "current_thread")]
async fn canonical_read_surfaces_preserve_the_full_durable_inventory() {
    let dir = tempfile::tempdir().unwrap();
    let state_dir = SessionStoreDir::under_root(dir.path());
    let store = open_store(&state_dir).unwrap();
    ensure_session(&store, &state_dir, "inspect", true, None)
        .await
        .unwrap();
    store
        .append(
            "inspect",
            AppendEvent::new(
                SessionEventKind::Custom {
                    custom_type: "inspection".to_string(),
                },
                json!({
                    "message": "inspection evidence",
                    "token": "sk-proj-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                }),
            ),
        )
        .await
        .unwrap();
    drop(store);

    let mut lock_path = store_path(&state_dir).as_os_str().to_os_string();
    lock_path.push(".harn-init.lock");
    let lock_path = PathBuf::from(lock_path);
    fs::remove_file(&lock_path).unwrap();
    let before = durable_inventory(dir.path());
    let raw_before = fs::read(store_path(&state_dir)).unwrap();
    assert!(!String::from_utf8_lossy(&raw_before).contains("sk-proj-"));

    let options = root_options(dir.path());
    let ctx = || crate::vm::AsyncBuiltinCtx::for_test(crate::vm::Vm::new());
    session_store_list_impl(ctx(), vec![options.clone()])
        .await
        .unwrap();
    session_store_events_impl(
        ctx(),
        vec![
            VmValue::String(arcstr::ArcStr::from("inspect")),
            options.clone(),
        ],
    )
    .await
    .unwrap();
    session_store_verify_impl(
        ctx(),
        vec![
            VmValue::String(arcstr::ArcStr::from("inspect")),
            options.clone(),
        ],
    )
    .await
    .unwrap();
    let search_options = json_to_vm_value(&json!({
        "root": dir.path().to_string_lossy(),
        "project_scope": "inspect-project",
    }));
    session_store_search_impl(
        ctx(),
        vec![
            VmValue::String(arcstr::ArcStr::from("inspection")),
            search_options,
        ],
    )
    .await
    .unwrap();

    assert_eq!(durable_inventory(dir.path()), before);
    assert!(!lock_path.exists());
}