artifactize 0.5.0

Declare Artifacts and the evals that review them (tests, LLM reviews, human sign-offs), and reuse every verdict while its fingerprints are unchanged.
Documentation
use std::{
    fs,
    os::unix::fs::{MetadataExt, PermissionsExt, symlink},
    path::Path,
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
    time::Duration,
};

use serde::{Deserialize, Serialize};

use super::storage::Storage;

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct Secret {
    token: String,
}

fn secret(token: &str) -> Secret {
    Secret {
        token: token.into(),
    }
}

fn test_storage(root: &Path) -> Storage {
    use std::os::unix::fs::DirBuilderExt;
    let directory = root.join("auth");
    fs::DirBuilder::new()
        .mode(0o700)
        .create(&directory)
        .unwrap();
    Storage { directory }
}

#[test]
fn storage_is_atomic_private_and_refuses_links() {
    let temp = tempfile::tempdir().unwrap();
    let storage = test_storage(temp.path());
    storage.save("secret.json", &secret("old-secret")).unwrap();
    let path = storage.directory.join("secret.json");
    let old = fs::File::open(&path).unwrap();
    let old_inode = old.metadata().unwrap().ino();
    storage.save("secret.json", &secret("new-secret")).unwrap();
    assert_ne!(old_inode, path.metadata().unwrap().ino());
    assert_eq!(path.metadata().unwrap().permissions().mode() & 0o777, 0o600);
    assert_eq!(
        storage.directory.metadata().unwrap().permissions().mode() & 0o777,
        0o700
    );
    assert_eq!(
        storage.read::<Secret>("secret.json").unwrap(),
        Some(secret("new-secret"))
    );
    assert_eq!(
        serde_json::from_reader::<_, Secret>(old).unwrap(),
        secret("old-secret")
    );
    assert_eq!(fs::read_dir(&storage.directory).unwrap().count(), 1);
    storage.remove("secret.json").unwrap();
    assert!(storage.read::<Secret>("secret.json").unwrap().is_none());
    storage.remove("secret.json").unwrap();

    let target = temp.path().join("public.json");
    fs::write(&target, r#"{"token":"public"}"#).unwrap();
    symlink(&target, &path).unwrap();
    assert!(storage.read::<Secret>("secret.json").is_err());
    fs::remove_file(&path).unwrap();
    fs::write(&path, r#"{"token":"readable"}"#).unwrap();
    fs::set_permissions(&path, fs::Permissions::from_mode(0o644)).unwrap();
    assert!(storage.read::<Secret>("secret.json").is_err());
}

#[test]
fn credential_storage_rejects_repositories_and_symlink_escapes() {
    let temp = tempfile::tempdir().unwrap();
    let repo = temp.path().join("repo");
    fs::create_dir(&repo).unwrap();
    fs::write(repo.join(".git"), "worktree marker").unwrap();
    assert!(Storage::new(Some(&repo.join("state")), None).is_err());
    let alias = temp.path().join("alias");
    symlink(&repo, &alias).unwrap();
    assert!(Storage::new(Some(&alias.join("state")), None).is_err());
    fs::remove_file(repo.join(".git")).unwrap();
    assert!(Storage::new(Some(&repo.join("state")), Some(&repo)).is_err());
    assert!(!repo.join("state").exists());
    let state = temp.path().join("state");
    let storage = Storage::new(Some(&state), None).unwrap();
    assert_eq!(
        storage.directory.metadata().unwrap().permissions().mode() & 0o777,
        0o700
    );
    fs::set_permissions(&storage.directory, fs::Permissions::from_mode(0o755)).unwrap();
    assert!(Storage::new(Some(&state), None).is_err());
}

#[tokio::test]
async fn named_locks_serialize_holders_and_stay_private() {
    let temp = tempfile::tempdir().unwrap();
    let storage = Arc::new(test_storage(temp.path()));
    let first = storage.lock("codex").await.unwrap();
    let lock = storage.directory.join("codex.lock");
    assert_eq!(lock.metadata().unwrap().permissions().mode() & 0o777, 0o600);
    // Another name is independent of a held lock.
    drop(storage.lock("other").await.unwrap());
    let acquired = Arc::new(AtomicBool::new(false));
    let waiter = tokio::spawn({
        let storage = storage.clone();
        let acquired = acquired.clone();
        async move {
            let _second = storage.lock("codex").await.unwrap();
            acquired.store(true, Ordering::SeqCst);
        }
    });
    tokio::time::sleep(Duration::from_millis(100)).await;
    assert!(!acquired.load(Ordering::SeqCst));
    drop(first);
    tokio::time::timeout(Duration::from_secs(5), waiter)
        .await
        .unwrap()
        .unwrap();
    assert!(acquired.load(Ordering::SeqCst));
    fs::remove_file(&lock).unwrap();
    symlink(temp.path().join("elsewhere"), &lock).unwrap();
    assert!(storage.lock("codex").await.is_err());
}