use rusqlite::Connection;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use tempfile::TempDir;
mod common;
use common::aid_cmd_in;
#[test]
fn fresh_codex_accepts_the_current_approval_flag() {
assert_fresh_codex_succeeds("t-current-codex-approval", false);
}
#[test]
fn fresh_codex_accepts_the_legacy_approval_flag() {
assert_fresh_codex_succeeds("t-legacy-codex-approval", true);
}
#[test]
fn exact_short_answer_is_a_successful_delivery() {
let aid_home = TempDir::new().unwrap();
let bin_dir = TempDir::new().unwrap();
write_fake_codex(bin_dir.path());
let output = aid_cmd_in(aid_home.path())
.env("PATH", test_path(bin_dir.path()))
.env("FAKE_CODEX_SHORT_FINAL", "1")
.args([
"run",
"codex",
"Reply with the single word: ok",
"--id",
"t-short-final",
])
.output()
.unwrap();
assert!(
output.status.success(),
"stdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let conn = Connection::open(aid_home.path().join("aid.db")).unwrap();
assert_eq!(task_facts(&conn, "t-short-final").0, "done");
}
#[test]
fn missing_explicit_result_file_fails_on_the_artifact_contract() {
let aid_home = TempDir::new().unwrap();
let bin_dir = TempDir::new().unwrap();
write_fake_codex(bin_dir.path());
let output = aid_cmd_in(aid_home.path())
.env("PATH", test_path(bin_dir.path()))
.env("FAKE_CODEX_SHORT_FINAL", "1")
.args([
"run",
"codex",
"Reply with the single word: ok",
"--result-file",
"required-result.md",
"--id",
"t-missing-required-result",
])
.output()
.unwrap();
assert!(!output.status.success());
let conn = Connection::open(aid_home.path().join("aid.db")).unwrap();
let facts = task_facts(&conn, "t-missing-required-result");
assert_eq!(facts.0, "failed");
assert_eq!(facts.1.as_deref(), Some("missing_final_delivery"));
}
fn assert_fresh_codex_succeeds(task_id: &str, legacy: bool) {
let aid_home = TempDir::new().unwrap();
let bin_dir = TempDir::new().unwrap();
write_fake_codex(bin_dir.path());
let mut command = aid_cmd_in(aid_home.path());
command
.env("PATH", test_path(bin_dir.path()))
.env("FAKE_CODEX_FINAL_ON_FRESH", "1");
if legacy {
command.env("FAKE_CODEX_LEGACY", "1");
}
let output = command
.args(["run", "codex", "Implement and report the requested change", "--id", task_id])
.output().unwrap();
assert!(
output.status.success(),
"stdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let conn = Connection::open(aid_home.path().join("aid.db")).unwrap();
assert_eq!(task_facts(&conn, task_id).0, "done");
}
#[test]
fn missing_final_delivery_fails_parent_and_resumes_once() {
let aid_home = TempDir::new().unwrap();
let bin_dir = TempDir::new().unwrap();
write_fake_codex(bin_dir.path());
let path = format!(
"{}:{}",
bin_dir.path().display(),
std::env::var("PATH").unwrap_or_default()
);
let home = home_with_seeded_rollout();
let output = aid_cmd_in(aid_home.path())
.env("PATH", path)
.env("HOME", home.path())
.args([
"run",
"codex",
"Investigate and report the result",
"--read-only",
"--id",
"t-delivery-parent",
])
.output()
.unwrap();
assert!(
output.status.success(),
"stdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let conn = Connection::open(aid_home.path().join("aid.db")).unwrap();
let parent = task_facts(&conn, "t-delivery-parent");
assert_eq!(parent.0, "failed");
assert_eq!(parent.1.as_deref(), Some("missing_final_delivery"));
let child = child_facts(&conn, "t-delivery-parent");
assert_eq!(child.0, "done");
assert_eq!(child.1, 1);
}
#[test]
fn repeated_missing_delivery_does_not_start_third_attempt() {
let aid_home = TempDir::new().unwrap();
let bin_dir = TempDir::new().unwrap();
write_fake_codex(bin_dir.path());
let home = home_with_seeded_rollout();
let output = aid_cmd_in(aid_home.path())
.env("PATH", test_path(bin_dir.path()))
.env("HOME", home.path())
.env("FAKE_CODEX_HOLLOW_RESUME", "1")
.args([
"run",
"codex",
"Investigate and report the result",
"--read-only",
"--id",
"t-delivery-repeat",
])
.output()
.unwrap();
assert!(!output.status.success(), "repeated hollow delivery must fail");
let conn = Connection::open(aid_home.path().join("aid.db")).unwrap();
let facts: (i64, i64) = conn
.query_row(
"SELECT COUNT(*), SUM(status = 'failed') FROM tasks",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(facts, (2, 2));
}
const FIXTURE_SESSION_ID: &str = "019e3e49-6b83-7563-a3d8-b51a3a716dd1";
fn home_with_seeded_rollout() -> TempDir {
let home = TempDir::new().unwrap();
let sessions = home.path().join(".codex/sessions/2026/08/09");
std::fs::create_dir_all(&sessions).unwrap();
std::fs::write(
sessions.join(format!("rollout-2026-08-09T00-00-00-{FIXTURE_SESSION_ID}.jsonl")),
"{\"type\":\"turn_context\",\"model\":\"gpt-5.6-sol\"}\n",
)
.unwrap();
home
}
fn task_facts(conn: &Connection, task_id: &str) -> (String, Option<String>) {
conn.query_row(
"SELECT status, delivery_assessment FROM tasks WHERE id = ?1",
[task_id],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap()
}
fn child_facts(conn: &Connection, parent_id: &str) -> (String, i64) {
conn.query_row(
"SELECT status, COUNT(*) OVER () FROM tasks WHERE parent_task_id = ?1",
[parent_id],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap()
}
fn write_fake_codex(dir: &Path) {
let path = dir.join("codex");
std::fs::write(
&path,
r#"#!/bin/sh
if [ "$1" = "--version" ]; then
if [ "$FAKE_CODEX_LEGACY" = "1" ]; then
echo "codex-cli 0.146.0"
else
echo "codex-cli 0.147.0"
fi
exit 0
fi
if [ "$1" = "exec" ] && [ "$2" = "--help" ]; then
if [ "$FAKE_CODEX_LEGACY" = "1" ]; then
echo " --full-auto"
else
echo " --approve-for-me"
fi
exit 0
fi
if [ "$2" = "resume" ]; then
printf '%s\n' '{"type":"thread.started","thread_id":"019e3e49-6b83-7563-a3d8-b51a3a716dd1"}'
if [ "$FAKE_CODEX_HOLLOW_RESUME" = "1" ]; then
printf '%s\n' '{"type":"item.completed","item":{"id":"work-again","type":"command_execution","command":"inspect again","aggregated_output":"","exit_code":0}}'
printf '%s\n' '{"type":"turn.completed","usage":{"input_tokens":20,"cached_input_tokens":10,"output_tokens":8000}}'
exit 0
fi
printf '%s\n' '{"type":"item.completed","item":{"id":"final","type":"agent_message","text":"Recovery complete."}}'
printf '%s\n' '{"type":"turn.completed","usage":{"input_tokens":20,"cached_input_tokens":10,"output_tokens":80}}'
exit 0
fi
if [ "$FAKE_CODEX_LEGACY" = "1" ]; then
case " $* " in
*" --full-auto "*) ;;
*) echo "error: missing '--full-auto'" >&2; exit 2 ;;
esac
else
case " $* " in
*" --approve-for-me "*) ;;
*) echo "error: missing '--approve-for-me'" >&2; exit 2 ;;
esac
fi
printf '%s\n' '{"type":"thread.started","thread_id":"019e3e49-6b83-7563-a3d8-b51a3a716dd1"}'
if [ "$FAKE_CODEX_SHORT_FINAL" = "1" ]; then
printf '%s\n' '{"type":"item.completed","item":{"id":"final","type":"agent_message","text":"ok"}}'
printf '%s\n' '{"type":"turn.completed","usage":{"input_tokens":20,"cached_input_tokens":10,"output_tokens":1}}'
exit 0
fi
if [ "$FAKE_CODEX_FINAL_ON_FRESH" = "1" ]; then
printf '%s\n' '{"type":"item.completed","item":{"id":"final","type":"agent_message","text":"Completed and verified."}}'
printf '%s\n' '{"type":"turn.completed","usage":{"input_tokens":20,"cached_input_tokens":10,"output_tokens":80}}'
exit 0
fi
printf '%s\n' '{"type":"item.completed","item":{"id":"progress","type":"agent_message","text":"I am investigating the task now. I will inspect the available evidence and provide the final report after the tool work is complete. This progress update is deliberately substantive but is not a final answer."}}'
printf '%s\n' '{"type":"item.completed","item":{"id":"work","type":"command_execution","command":"inspect evidence","aggregated_output":"","exit_code":0}}'
printf '%s\n' '{"type":"turn.completed","usage":{"input_tokens":100,"cached_input_tokens":50,"output_tokens":9000}}'
"#,
)
.unwrap();
let mut permissions = std::fs::metadata(&path).unwrap().permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(path, permissions).unwrap();
}
fn test_path(bin_dir: &Path) -> String {
format!(
"{}:{}",
bin_dir.display(),
std::env::var("PATH").unwrap_or_default()
)
}