cordance-cli 0.1.0

Cordance CLI binary.
use assert_cmd::prelude::*;
use std::process::Command;

/// Verify cordance can run against the cellos sibling repo in dry-run mode.
/// No files are written to cellos.
#[test]
#[ignore = "requires ../cellos sibling on disk"]
fn cellos_dry_run_succeeds() {
    let cellos_path = std::path::Path::new("../cellos");
    if !cellos_path.exists() {
        return;
    }

    let mut cmd = Command::cargo_bin("cordance").expect("bin");
    cmd.arg("--target")
        .arg("../cellos")
        .arg("pack")
        .arg("--output-mode")
        .arg("dry-run");
    cmd.assert().success();
}

/// Verify the dry-run output lists the harness-target among planned writes.
#[test]
#[ignore = "requires ../cellos sibling on disk"]
fn cellos_dry_run_plans_harness_target() {
    let cellos_path = std::path::Path::new("../cellos");
    if !cellos_path.exists() {
        return;
    }

    let mut cmd = Command::cargo_bin("cordance").expect("bin");
    let output = cmd
        .arg("--target")
        .arg("../cellos")
        .arg("pack")
        .arg("--output-mode")
        .arg("dry-run")
        .output()
        .expect("run cordance");

    assert!(
        output.status.success(),
        "cordance failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("pai-axiom-project-harness-target.json"),
        "expected harness-target in dry-run output, got: {stdout}"
    );
    assert!(
        stdout.contains("would write"),
        "expected 'would write' in dry-run output, got: {stdout}"
    );
}

/// Verify the harness-target golden fixture is structurally correct.
/// Reads the pre-built fixture from fixtures/cellos-expected/.
#[test]
fn cellos_golden_fixture_has_correct_structure() {
    let manifest = env!("CARGO_MANIFEST_DIR");
    let fixture_path = format!(
        "{manifest}/../../fixtures/cellos-expected/pai-axiom-project-harness-target.json"
    );
    let json = std::fs::read_to_string(&fixture_path)
        .unwrap_or_else(|e| panic!("failed to read cellos golden fixture: {e}"));

    let value: serde_json::Value =
        serde_json::from_str(&json).expect("golden fixture must be valid JSON");

    assert_eq!(
        value["schema"],
        "pai-axiom-project-harness-target.v1",
        "schema must be pai-axiom-project-harness-target.v1"
    );
    assert_eq!(value["version"], 1, "version must be 1");
    assert_eq!(
        value["project"]["name"], "cellos",
        "project.name must be cellos"
    );
    assert_eq!(
        value["project"]["access_mode"], "read-only-advisory",
        "project.access_mode must be read-only-advisory"
    );
    assert_eq!(
        value["harness"]["claim_ceiling"], "candidate",
        "harness.claim_ceiling must be candidate"
    );
    assert_eq!(
        value["harness"]["classification"], "read-only-advisory",
        "harness.classification must be read-only-advisory"
    );

    let allowed: Vec<&str> = value["harness"]["allowed_operations"]
        .as_array()
        .expect("allowed_operations must be array")
        .iter()
        .map(|v| v.as_str().expect("operation must be string"))
        .collect();
    assert!(
        allowed.contains(&"inspect"),
        "allowed_operations must include inspect"
    );
    assert!(
        allowed.contains(&"validate-target"),
        "allowed_operations must include validate-target"
    );
    assert!(
        allowed.contains(&"emit-candidate-report"),
        "allowed_operations must include emit-candidate-report"
    );

    let denied: Vec<&str> = value["harness"]["denied_operations"]
        .as_array()
        .expect("denied_operations must be array")
        .iter()
        .map(|v| v.as_str().expect("operation must be string"))
        .collect();
    assert!(
        denied.contains(&"write-project-files"),
        "denied_operations must include write-project-files"
    );
    assert!(
        denied.contains(&"promote-project-doctrine"),
        "denied_operations must include promote-project-doctrine"
    );
    assert!(
        denied.contains(&"mutate-runtime-roots"),
        "denied_operations must include mutate-runtime-roots"
    );
    assert!(
        denied.contains(&"modify-release-gates"),
        "denied_operations must include modify-release-gates"
    );
    assert!(
        denied.contains(&"rewrite-adrs"),
        "denied_operations must include rewrite-adrs"
    );

    let product_spec = value["authority_surfaces"]["product_spec"]
        .as_array()
        .expect("product_spec must be array");
    assert!(
        !product_spec.is_empty(),
        "product_spec must not be empty for cellos"
    );

    let adrs = value["authority_surfaces"]["adrs"]
        .as_array()
        .expect("adrs must be array");
    assert!(!adrs.is_empty(), "adrs must not be empty for cellos");

    let tests_or_evals = value["authority_surfaces"]["tests_or_evals"]
        .as_array()
        .expect("tests_or_evals must be array");
    assert!(
        !tests_or_evals.is_empty(),
        "tests_or_evals must not be empty for cellos"
    );
}

/// Confirm that cellos AGENTS.md pins a stale axiom version relative to LATEST,
/// demonstrating the value cordance provides by tracking LATEST automatically.
/// This test is observational; it does not fail if cellos updates its pin.
#[test]
#[ignore = "requires ../cellos and ../pai-axiom siblings on disk"]
fn cellos_axiom_pin_is_stale_relative_to_latest() {
    let latest_path = std::path::Path::new("../pai-axiom/PAI/Algorithm/LATEST");
    if !latest_path.exists() {
        return;
    }

    let current_latest = std::fs::read_to_string(latest_path)
        .expect("read LATEST")
        .trim()
        .to_string();

    let cellos_agents_path = std::path::Path::new("../cellos/AGENTS.md");
    if !cellos_agents_path.exists() {
        return;
    }
    let cellos_agents = std::fs::read_to_string(cellos_agents_path)
        .expect("read cellos AGENTS.md");

    if cellos_agents.contains("v3.1.0-axiom") && !cellos_agents.contains(&current_latest) {
        eprintln!(
            "CONFIRMED: cellos AGENTS.md pins v3.1.0-axiom, but LATEST is {current_latest}"
        );
        eprintln!("Cordance would update it to {current_latest} on next pack.");
    }
    // Observational only — always passes.
}