decapod 0.52.1

Decapod is a Rust-built governance runtime for AI agents: repo-native state, enforced workflow, proof gates, safe coordination.
Documentation
use serde_json::Value;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;

fn run_decapod(dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> std::process::Output {
    let mut cmd = Command::new(env!("CARGO_BIN_EXE_decapod"));
    cmd.current_dir(dir).args(args);
    for (k, v) in envs {
        cmd.env(k, v);
    }
    cmd.output().expect("run decapod")
}

fn setup_repo() -> (TempDir, PathBuf, String) {
    let tmp = TempDir::new().expect("tmpdir");
    let dir = tmp.path().to_path_buf();

    let git_init = Command::new("git")
        .current_dir(&dir)
        .args(["init", "-b", "master"])
        .output()
        .expect("git init");
    assert!(git_init.status.success(), "git init failed");

    let init = run_decapod(&dir, &["init", "--force"], &[]);
    assert!(
        init.status.success(),
        "decapod init failed: {}",
        String::from_utf8_lossy(&init.stderr)
    );

    let acquire = run_decapod(
        &dir,
        &["session", "acquire"],
        &[("DECAPOD_AGENT_ID", "unknown")],
    );
    assert!(
        acquire.status.success(),
        "session acquire failed: {}",
        String::from_utf8_lossy(&acquire.stderr)
    );
    let password = String::from_utf8_lossy(&acquire.stdout)
        .lines()
        .find_map(|line| {
            line.strip_prefix("Password: ")
                .map(|s| s.trim().to_string())
        })
        .expect("password in session acquire output");

    (tmp, dir, password)
}

#[test]
fn phase4_kernel_surfaces_work_together() {
    let (_tmp, dir, password) = setup_repo();
    let auth = [
        ("DECAPOD_AGENT_ID", "unknown"),
        ("DECAPOD_SESSION_PASSWORD", password.as_str()),
        ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
    ];

    let init_workunit = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "R_PHASE4",
            "--intent-ref",
            "intent://phase4",
        ],
        &auth,
    );
    assert!(
        init_workunit.status.success(),
        "workunit init failed: {}",
        String::from_utf8_lossy(&init_workunit.stderr)
    );

    let proof_plan = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "set-proof-plan",
            "--task-id",
            "R_PHASE4",
            "--gate",
            "validate_passes",
        ],
        &auth,
    );
    assert!(
        proof_plan.status.success(),
        "set-proof-plan failed: {}",
        String::from_utf8_lossy(&proof_plan.stderr)
    );

    for (gate, status) in [("validate_passes", "pass"), ("validate_passes", "pass")] {
        let out = run_decapod(
            &dir,
            &[
                "govern",
                "workunit",
                "record-proof",
                "--task-id",
                "R_PHASE4",
                "--gate",
                gate,
                "--status",
                status,
            ],
            &auth,
        );
        assert!(
            out.status.success(),
            "record-proof failed: {}",
            String::from_utf8_lossy(&out.stderr)
        );
    }

    for status in ["executing", "claimed", "verified"] {
        let out = run_decapod(
            &dir,
            &[
                "govern",
                "workunit",
                "transition",
                "--task-id",
                "R_PHASE4",
                "--to",
                status,
            ],
            &auth,
        );
        assert!(
            out.status.success(),
            "workunit transition {} failed: {}",
            status,
            String::from_utf8_lossy(&out.stderr)
        );
    }

    let capsule = run_decapod(
        &dir,
        &[
            "govern",
            "capsule",
            "query",
            "--topic",
            "proof gates",
            "--scope",
            "interfaces",
            "--task-id",
            "R_PHASE4",
            "--write",
        ],
        &auth,
    );
    assert!(
        capsule.status.success(),
        "capsule query failed: {}",
        String::from_utf8_lossy(&capsule.stderr)
    );
    let capsule_payload: Value = serde_json::from_slice(&capsule.stdout).expect("capsule payload");
    let capsule_path = capsule_payload["path"].as_str().expect("capsule path");
    assert!(Path::new(capsule_path).exists(), "capsule artifact missing");

    let promote = run_decapod(
        &dir,
        &[
            "data",
            "knowledge",
            "promote",
            "--source-entry-id",
            "K_PHASE4",
            "--evidence-ref",
            "commit:abc123",
            "--approved-by",
            "human/reviewer",
            "--reason",
            "phase4 regression",
        ],
        &auth,
    );
    assert!(
        promote.status.success(),
        "knowledge promote failed: {}",
        String::from_utf8_lossy(&promote.stderr)
    );
    let promote_payload: Value = serde_json::from_slice(&promote.stdout).expect("promote payload");
    let event_id = promote_payload["event_id"].as_str().expect("event id");

    let add_proc = run_decapod(
        &dir,
        &[
            "data",
            "knowledge",
            "add",
            "--id",
            "procedural/commit_norms/phase4",
            "--title",
            "Phase 4 Norm",
            "--text",
            "always prove before publish",
            "--provenance",
            &format!("event:{}", event_id),
        ],
        &auth,
    );
    assert!(
        add_proc.status.success(),
        "procedural knowledge add failed: {}",
        String::from_utf8_lossy(&add_proc.stderr)
    );

    let validate = run_decapod(&dir, &["validate"], &auth);
    assert!(
        validate.status.success(),
        "phase4 regression validate failed: {}",
        String::from_utf8_lossy(&validate.stderr)
    );
}