use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use serde_json::Value;
const STALE_SOURCE_COMMIT: &str = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
const DEFAULT_FIXTURE_SOURCE_COMMIT: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
fn cortex_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_cortex"))
}
fn fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("pai-axiom")
}
fn temp_dir(label: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock after unix epoch")
.as_nanos();
let dir = std::env::temp_dir().join(format!(
"cortex-cli-axiom-freshness-{label}-{}-{nanos}",
std::process::id()
));
std::fs::create_dir_all(&dir).expect("create temp dir");
dir
}
fn run_admit_axiom_with_payload(
tmp: &Path,
axiom_execution_trust_json: &Value,
env: &[(&str, &str)],
) -> std::process::Output {
let ctx_path = fixture_dir().join("valid-cortex-context-trust.json");
let loop_path = fixture_dir().join("valid-authority-feedback-loop.json");
let exec_path = tmp.join("axiom-execution-trust.json");
let exec_serialized = serde_json::to_string_pretty(axiom_execution_trust_json)
.expect("axiom_execution_trust JSON is serializable");
std::fs::write(&exec_path, exec_serialized).expect("write axiom-execution-trust fixture");
let mut cmd = Command::new(cortex_bin());
cmd.current_dir(tmp);
cmd.env_remove("RUST_LOG");
cmd.env_remove("CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS");
cmd.env("XDG_DATA_HOME", tmp.join("xdg"));
cmd.env("HOME", tmp);
for (k, v) in env {
cmd.env(k, v);
}
cmd.args([
"memory",
"admit-axiom",
"--cortex-context-trust",
ctx_path.to_str().expect("ctx path utf8"),
"--axiom-execution-trust",
exec_path.to_str().expect("exec path utf8"),
"--authority-feedback-loop",
loop_path.to_str().expect("loop path utf8"),
"--lifecycle",
"candidate_only",
]);
cmd.output().expect("spawn cortex memory admit-axiom")
}
fn payload_with_source_commit(new_source_commit: &str) -> Value {
let raw = std::fs::read_to_string(fixture_dir().join("valid-axiom-execution-trust.json"))
.expect("read valid-axiom-execution-trust fixture");
let mut value: Value = serde_json::from_str(&raw).expect("parse fixture JSON");
value["axiom_execution_trust"]["tool_provenance"]["source_commit"] =
Value::String(new_source_commit.to_string());
value
}
#[test]
fn admit_axiom_admits_payload_with_default_fixture_source_commit() {
let tmp = temp_dir("admits-default");
let payload = payload_with_source_commit(DEFAULT_FIXTURE_SOURCE_COMMIT);
let out = run_admit_axiom_with_payload(&tmp, &payload, &[]);
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
out.status.success(),
"default-fixture source_commit must admit; exit={:?}\nstdout: {stdout}\nstderr: {stderr}",
out.status.code()
);
assert!(
stderr.contains("decision=admit_candidate"),
"default-fixture source_commit must admit as candidate; stderr: {stderr}"
);
std::fs::remove_dir_all(&tmp).ok();
}
#[test]
fn admit_axiom_refuses_unknown_source_commit_with_stable_invariant() {
let tmp = temp_dir("refuses-stale");
let payload = payload_with_source_commit(STALE_SOURCE_COMMIT);
let out = run_admit_axiom_with_payload(&tmp, &payload, &[]);
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!out.status.success(),
"unknown source_commit must refuse; got exit={:?}\nstdout: {stdout}\nstderr: {stderr}",
out.status.code()
);
assert!(
stderr.contains("axiom_execution_trust.tool_provenance.source_commit.stale"),
"stderr must surface the stable freshness invariant; got: {stderr}"
);
assert!(
stderr.contains(STALE_SOURCE_COMMIT),
"stderr must echo the offending source_commit so operators can diagnose; got: {stderr}"
);
std::fs::remove_dir_all(&tmp).ok();
}
#[test]
fn admit_axiom_env_var_override_replaces_default_accept_list() {
let tmp_admit = temp_dir("env-replaces-admits");
let stale_payload = payload_with_source_commit(STALE_SOURCE_COMMIT);
let stale_with_env_out = run_admit_axiom_with_payload(
&tmp_admit,
&stale_payload,
&[("CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS", STALE_SOURCE_COMMIT)],
);
let stale_stderr = String::from_utf8_lossy(&stale_with_env_out.stderr);
assert!(
stale_with_env_out.status.success(),
"env-var override must admit the listed SHA; exit={:?}\nstderr: {stale_stderr}",
stale_with_env_out.status.code()
);
assert!(
stale_stderr.contains("decision=admit_candidate"),
"env-var override must admit as candidate; stderr: {stale_stderr}"
);
let tmp_refuse = temp_dir("env-replaces-refuses");
let default_payload = payload_with_source_commit(DEFAULT_FIXTURE_SOURCE_COMMIT);
let default_with_env_out = run_admit_axiom_with_payload(
&tmp_refuse,
&default_payload,
&[("CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS", STALE_SOURCE_COMMIT)],
);
let default_stderr = String::from_utf8_lossy(&default_with_env_out.stderr);
assert!(
!default_with_env_out.status.success(),
"env-var override must REPLACE (not extend) the default; the default fixture SHA must now refuse; got exit={:?}\nstderr: {default_stderr}",
default_with_env_out.status.code()
);
assert!(
default_stderr.contains("axiom_execution_trust.tool_provenance.source_commit.stale"),
"default fixture SHA must refuse with the freshness invariant when env-var override excludes it; got: {default_stderr}"
);
std::fs::remove_dir_all(&tmp_admit).ok();
std::fs::remove_dir_all(&tmp_refuse).ok();
}
#[test]
fn admit_axiom_empty_env_var_refuses_everything() {
let tmp = temp_dir("env-empty");
let payload = payload_with_source_commit(DEFAULT_FIXTURE_SOURCE_COMMIT);
let out = run_admit_axiom_with_payload(
&tmp,
&payload,
&[("CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS", "")],
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!out.status.success(),
"empty env var must yield empty accept-list; got exit={:?}\nstderr: {stderr}",
out.status.code()
);
assert!(
stderr.contains("axiom_execution_trust.tool_provenance.source_commit.stale"),
"empty env var must refuse with the freshness invariant; got: {stderr}"
);
std::fs::remove_dir_all(&tmp).ok();
}