use std::path::PathBuf;
use std::process::Command;
use serde_json::Value;
use tempfile::TempDir;
fn cortex_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_cortex"))
}
fn run_in_data_dir(data_dir: &std::path::Path, args: &[&str]) -> std::process::Output {
Command::new(cortex_bin())
.env_remove("RUST_LOG")
.env("CORTEX_DATA_DIR", data_dir)
.args(args)
.output()
.expect("spawn cortex")
}
fn assert_precondition_unmet(out: &std::process::Output) {
let code = out.status.code().expect("exited via signal");
assert_eq!(
code,
7,
"expected exit 7 (PreconditionUnmet)\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
}
fn parse_stderr_json(out: &std::process::Output) -> Value {
let stderr = String::from_utf8_lossy(&out.stderr);
let start = stderr
.find('{')
.unwrap_or_else(|| panic!("no JSON found in stderr:\n{stderr}"));
serde_json::from_str(&stderr[start..])
.unwrap_or_else(|err| panic!("stderr is not valid JSON ({err}):\n{stderr}"))
}
#[test]
fn release_from_store_with_no_local_state_emits_advisory_with_all_axes_missing() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["release", "readiness", "--from-store"]);
assert_precondition_unmet(&out);
let report = parse_stderr_json(&out);
assert_eq!(report["command"], "release.readiness.from_store");
assert_eq!(report["mode"], "from_store");
assert_eq!(report["trusted_artifact_emitted"], false);
assert_eq!(report["all_witness_axes_present"], false);
let missing = report["missing_witness_axes"]
.as_array()
.expect("missing_witness_axes is array")
.iter()
.filter_map(Value::as_str)
.collect::<Vec<_>>();
assert!(
missing.contains(&"signed_chain"),
"signed_chain missing: {missing:?}"
);
assert!(
missing.contains(&"ado_build"),
"ado_build missing: {missing:?}"
);
assert!(missing.contains(&"rekor"), "rekor missing: {missing:?}");
assert!(missing.contains(&"ots"), "ots missing: {missing:?}");
}
#[test]
fn release_from_store_emits_stable_invariants_for_every_missing_axis() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["release", "readiness", "--from-store"]);
let report = parse_stderr_json(&out);
let signed_chain = &report["evidence_input"]["signed_chain"];
assert_eq!(signed_chain["state"], "missing");
assert_eq!(
signed_chain["invariant"],
"release.readiness.from_store.witness_axis_missing.signed_chain"
);
let ado_build = &report["evidence_input"]["ado_build"];
assert_eq!(ado_build["state"], "missing");
assert_eq!(
ado_build["invariant"],
"release.readiness.from_store.witness_axis_missing.ado_build"
);
let rekor = &report["evidence_input"]["rekor"];
assert_eq!(rekor["state"], "missing");
assert_eq!(
rekor["invariant"],
"release.readiness.from_store.witness_axis_missing.rekor"
);
let ots = &report["evidence_input"]["ots"];
assert_eq!(ots["state"], "missing");
assert_eq!(
ots["invariant"],
"release.readiness.from_store.witness_axis_missing.ots"
);
}
#[test]
fn release_default_mode_without_from_store_still_fails_closed_on_missing_input() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["release", "readiness"]);
assert_precondition_unmet(&out);
let report = parse_stderr_json(&out);
assert_eq!(report["command"], "release.readiness");
assert_eq!(report["trusted_artifact_emitted"], false);
assert_eq!(
report["evidence_input"]["failed_rule"],
"release.evidence_input.missing"
);
}
#[test]
fn release_from_store_missing_signed_chain_when_no_event_log_present() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["release", "readiness", "--from-store"]);
let report = parse_stderr_json(&out);
let signed_chain = &report["evidence_input"]["signed_chain"];
let detail = signed_chain["detail"].as_str().unwrap_or("");
assert!(
detail.contains("event log") || detail.contains("events.jsonl"),
"expected detail to name missing event log, got: {detail}"
);
}
#[test]
fn compliance_from_store_with_no_local_state_emits_advisory_with_all_axes_missing() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["compliance", "evidence", "--from-store"]);
assert_precondition_unmet(&out);
let report = parse_stderr_json(&out);
assert_eq!(report["command"], "compliance.evidence.from_store");
assert_eq!(report["mode"], "from_store");
assert_eq!(report["trusted_artifact_emitted"], false);
assert_eq!(report["all_witness_axes_present"], false);
let missing = report["missing_witness_axes"]
.as_array()
.expect("missing_witness_axes is array")
.iter()
.filter_map(Value::as_str)
.collect::<Vec<_>>();
assert!(missing.contains(&"signed_chain"));
assert!(missing.contains(&"ado_build"));
assert!(missing.contains(&"rekor"));
assert!(missing.contains(&"ots"));
}
#[test]
fn compliance_from_store_emits_stable_invariants_for_every_missing_axis() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["compliance", "evidence", "--from-store"]);
let report = parse_stderr_json(&out);
assert_eq!(
report["evidence_input"]["signed_chain"]["invariant"],
"compliance.evidence.from_store.witness_axis_missing.signed_chain"
);
assert_eq!(
report["evidence_input"]["ado_build"]["invariant"],
"compliance.evidence.from_store.witness_axis_missing.ado_build"
);
assert_eq!(
report["evidence_input"]["rekor"]["invariant"],
"compliance.evidence.from_store.witness_axis_missing.rekor"
);
assert_eq!(
report["evidence_input"]["ots"]["invariant"],
"compliance.evidence.from_store.witness_axis_missing.ots"
);
}
#[test]
fn compliance_default_mode_without_from_store_still_fails_closed_on_missing_input() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["compliance", "evidence"]);
assert_precondition_unmet(&out);
let report = parse_stderr_json(&out);
assert_eq!(report["command"], "compliance.evidence");
assert_eq!(report["trusted_artifact_emitted"], false);
assert_eq!(
report["evidence_input"]["failed_rule"],
"compliance.evidence_input.missing"
);
}
#[test]
fn compliance_from_store_missing_signed_chain_when_no_event_log_present() {
let tmp = TempDir::new().expect("tempdir");
let out = run_in_data_dir(tmp.path(), &["compliance", "evidence", "--from-store"]);
let report = parse_stderr_json(&out);
let signed_chain = &report["evidence_input"]["signed_chain"];
let detail = signed_chain["detail"].as_str().unwrap_or("");
assert!(
detail.contains("event log") || detail.contains("events.jsonl"),
"expected detail to name missing event log, got: {detail}"
);
}
#[test]
fn release_and_compliance_from_store_share_the_same_evidence_input_axes_keys() {
let tmp = TempDir::new().expect("tempdir");
let r = parse_stderr_json(&run_in_data_dir(
tmp.path(),
&["release", "readiness", "--from-store"],
));
let c = parse_stderr_json(&run_in_data_dir(
tmp.path(),
&["compliance", "evidence", "--from-store"],
));
let r_keys: Vec<&str> = r["evidence_input"]
.as_object()
.expect("release evidence_input is object")
.keys()
.map(String::as_str)
.collect();
let c_keys: Vec<&str> = c["evidence_input"]
.as_object()
.expect("compliance evidence_input is object")
.keys()
.map(String::as_str)
.collect();
for key in [
"signed_chain",
"ado_build",
"rekor",
"ots",
"all_witness_axes_present",
"data_dir",
] {
assert!(
r_keys.contains(&key),
"release missing key {key}: {r_keys:?}"
);
assert!(
c_keys.contains(&key),
"compliance missing key {key}: {c_keys:?}"
);
}
}