use std::path::Path;
use std::sync::{Arc, Mutex};
use super::heal_intake::{Checkout, HealTarget};
use super::heal_live::{CoderRunner, LiveTickIo};
use super::heal_runner::{delivery_branch, LiveCoderRunner, Reviewer};
use super::heal_select::{Candidate, CandidateKind};
use super::heal_tick::{Intent, TickIo};
use super::merge::{
CiCheck, CiState, CiSummary, GhError, GitHubApi, PrDeliveryOutcome, PrRecord, PrState,
};
use super::provenance::LocalSignatures;
use super::router::EngineChoice;
use crate::session::ServerState;
const LIVE_REPO: &str = "Parslee-ai/car";
fn state() -> Arc<ServerState> {
let journal = tempfile::tempdir().unwrap();
let path = journal.keep();
Arc::new(ServerState::standalone(path))
}
fn git(dir: &Path, args: &[&str]) -> String {
let out = std::process::Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.output()
.expect("git runs");
assert!(
out.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
#[tokio::test]
#[ignore = "live: needs `gh` auth and network"]
async fn a_real_maintainer_issue_resolves_to_a_tier_that_can_seed() {
let io = LiveTickIo {
issues: Arc::new(super::fix_issues::GhIssues),
prs: Arc::new(super::heal_intake::GhPullRequests),
oracle: Arc::new(super::provenance::GhPermissions),
coder: Arc::new(Unreached),
local_signatures: LocalSignatures::from_proposals(&[]),
redactor: car_selfheal::redact::Redactor::from_env(std::env::vars()),
panel: Vec::new(),
};
let target = HealTarget {
repo: LIVE_REPO.into(),
fix_repo: None,
checkout: None,
label: "self-heal".into(),
base: "main".into(),
};
let candidates = io.candidates(&target).await.expect("live issue list");
assert!(!candidates.is_empty(), "the live queue is empty");
println!("scanned {} open issues", candidates.len());
let newest = candidates
.iter()
.max_by_key(|c| c.number)
.expect("a candidate");
let intent = io.intent_for(newest).await.expect("live tier resolution");
match intent {
Intent::Seed(seed) => {
println!(
"#{} cleared to seed a session ({} chars of intent)",
newest.number,
seed.as_str().len()
);
assert!(!seed.as_str().is_empty());
}
Intent::Refused { tier, stale } => panic!(
"#{} was refused (tier {tier:?}, stale {stale}) — this repository's own \
maintainer must clear the gate, or the loop can never act on anything",
newest.number
),
Intent::Gone => println!("#{} closed between the scan and now", newest.number),
}
}
struct Unreached;
#[async_trait::async_trait]
impl CoderRunner for Unreached {
async fn run(
&self,
_t: &HealTarget,
_i: &Candidate,
_s: &super::provenance::SessionSeed,
) -> Result<super::heal_tick::Attempt, super::heal_tick::RunFailure> {
panic!("the tier trial must not start a coder session")
}
async fn deliver(
&self,
_t: &HealTarget,
_i: &Candidate,
_s: &str,
_b: &str,
) -> Result<PrDeliveryOutcome, super::heal_tick::DeliverRefusal> {
panic!("the tier trial must not deliver")
}
async fn abandon(&self, _s: &str) {}
}
struct RecordingGh {
created: Mutex<Vec<(String, String)>>,
}
impl GitHubApi for RecordingGh {
fn auth_status(&self) -> Result<(), GhError> {
Ok(())
}
fn list_prs_for_head(&self, _d: &Path, _h: &str) -> Result<Vec<PrRecord>, GhError> {
Ok(Vec::new())
}
fn create_pr(
&self,
_d: &Path,
head: &str,
base: &str,
_t: &str,
_b: &str,
_draft: bool,
) -> Result<PrRecord, GhError> {
self.created
.lock()
.unwrap()
.push((head.to_string(), base.to_string()));
Ok(PrRecord {
number: 1,
state: PrState::Open,
url: "https://example.invalid/pull/1".into(),
is_draft: false,
base: base.to_string(),
})
}
fn set_pr_body(&self, _d: &Path, _n: u64, _b: &str) -> Result<(), GhError> {
Ok(())
}
fn ci_for_sha(&self, _d: &Path, _n: u64, head_sha: &str) -> Result<CiSummary, GhError> {
Ok(CiSummary {
observation_error: None,
head_sha: head_sha.to_string(),
state: CiState::Green,
checks: vec![CiCheck {
name: "trial".into(),
state: CiState::Green,
}],
})
}
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "live: needs provider credentials, network, and real spend"]
async fn a_real_model_heals_a_real_repository_under_a_real_panel() {
let origin = tempfile::tempdir().unwrap();
let repo = tempfile::tempdir().unwrap();
git(origin.path(), &["init", "-q", "--bare", "-b", "main"]);
git(repo.path(), &["init", "-q", "-b", "main"]);
git(repo.path(), &["config", "user.name", "heal-trial"]);
git(repo.path(), &["config", "user.email", "heal@car"]);
std::fs::write(
repo.path().join("greet.py"),
"def greet(name):\n return 'Hello, ' + name\n",
)
.unwrap();
std::fs::write(
repo.path().join("test_greet.py"),
"from greet import greet\n\n\n def test_greets_a_string():\n assert greet('Ada') == 'Hello, Ada'\n\n\n def test_greets_a_number():\n assert greet(7) == 'Hello, 7'\n",
)
.unwrap();
std::fs::write(
repo.path().join("README.md"),
"A tiny library used by the CAR self-heal live trial. Run the tests with `python3 -m pytest`.\n",
)
.unwrap();
let before = std::process::Command::new("python3")
.args(["-m", "pytest", "-q"])
.current_dir(repo.path())
.output()
.expect("pytest runs");
assert!(
!before.status.success(),
"the fixture is already green; there is nothing to heal"
);
println!("fixture is red, as intended");
git(repo.path(), &["add", "-A"]);
git(repo.path(), &["commit", "-qm", "seed"]);
git(
repo.path(),
&["remote", "add", "origin", origin.path().to_str().unwrap()],
);
git(repo.path(), &["push", "-q", "origin", "main"]);
let state = state();
let state_dir = tempfile::tempdir().unwrap();
let gh = Arc::new(RecordingGh {
created: Mutex::new(Vec::new()),
});
let reviewers: Vec<Arc<dyn Reviewer>> = ["gpt-5.6", "gpt-5.6-sol", "gpt-5.4"]
.iter()
.map(|m| {
Arc::new(super::heal_review::ModelReviewer::new(state.clone(), *m)) as Arc<dyn Reviewer>
})
.collect();
let runner = LiveCoderRunner {
state: state.clone(),
generator: crate::handler::get_inference_engine(&state).clone(),
state_dir: state_dir.path().to_path_buf(),
reviewers,
max_wall_secs: 20 * 60,
max_iterations: Some(12),
engine: EngineChoice::Native,
model: Some("gpt-5.5".into()),
routing_exclusions: Vec::new(),
canonical_model: {
let engine_handle = crate::handler::get_inference_engine(&state).clone();
Arc::new(move |m: &str| {
engine_handle
.model_schema(m)
.map(|s| s.id.clone())
.unwrap_or_else(|| m.to_string())
})
},
github: gh.clone(),
};
let target = HealTarget {
repo: "acme/greet".into(),
fix_repo: None,
checkout: Some(Checkout::Local(repo.path().to_path_buf())),
label: "self-heal".into(),
base: "main".into(),
};
let item = Candidate {
repo: "acme/greet".into(),
number: 1,
tier: Some(super::provenance::ProvenanceTier::Maintainer),
labelled: true,
created_ms: 1,
kind: CandidateKind::Issue,
};
let seed = super::provenance::SessionSeed::from_trusted(
"greet() crashes when called with a non-string, e.g. greet(7) raises \
TypeError. Make it accept any value by converting it to a string.",
);
println!("running a real coder session…");
let attempt = runner
.run(&target, &item, &seed)
.await
.expect("the real coder session");
println!(
"contract_passed={} panel={} verdicts={:?} unreachable={:?}",
attempt.contract_passed,
attempt.panel_size,
attempt
.verdicts
.iter()
.map(|v| (v.model.as_str(), v.pass))
.collect::<Vec<_>>(),
attempt.unreachable
);
{
let sessions = state.coder_sessions.lock().await;
if let Some(entry) = sessions.get(&attempt.session_id) {
let s = entry.session.lock().await;
if let Some(c) = &s.contract {
println!("--- derived contract ---");
println!("description: {}", c.description);
for check in &c.checks {
println!(" check {:?}: {}", check.name, check.command);
}
}
println!("iterations: {}", s.iterations);
}
}
assert!(
attempt.contract_passed,
"the runtime's own contract re-run went red: {}",
attempt.contract_detail
);
assert!(
!attempt.verdicts.is_empty(),
"no reviewer produced a readable verdict — every seat was unreachable: {:?}",
attempt.unreachable
);
let gate = super::heal_gate::decide(
attempt.contract_passed,
&attempt.contract_detail,
attempt.panel_size,
&attempt.verdicts,
&attempt.unreachable,
);
println!("gate: {}", gate.summary());
assert!(
gate.approved(),
"the real panel refused the change: {}",
gate.summary()
);
let delivered = runner
.deliver(&target, &item, &attempt.session_id, "live trial")
.await
.expect("real delivery");
println!(
"delivered: {} ({})",
delivered.pr_url,
delivered.delivery_report()
);
let branch = delivery_branch(&item);
let on_origin = git(
origin.path(),
&["rev-parse", &format!("refs/heads/{branch}")],
);
println!("{branch} is on the remote at {}", on_origin.trim());
let content = git(
origin.path(),
&["show", &format!("{}:greet.py", on_origin.trim())],
);
println!("--- delivered greet.py ---\n{content}");
let checkout = tempfile::tempdir().unwrap();
git(
origin.path(),
&[
"worktree",
"add",
"-q",
"--detach",
checkout.path().to_str().unwrap(),
on_origin.trim(),
],
);
let after = std::process::Command::new("python3")
.args(["-m", "pytest", "-q"])
.current_dir(checkout.path())
.output()
.expect("pytest runs");
println!(
"delivered suite: {}",
String::from_utf8_lossy(&after.stdout).trim()
);
assert!(
after.status.success(),
"the delivered commit does not pass the suite:\n{}",
String::from_utf8_lossy(&after.stdout)
);
for (which, files) in [
("local", git(repo.path(), &["show", "main:greet.py"])),
("origin", git(origin.path(), &["show", "main:greet.py"])),
] {
assert_eq!(
files, "def greet(name):\n return 'Hello, ' + name\n",
"the loop wrote to {which} main"
);
}
let created = gh.created.lock().unwrap().clone();
assert_eq!(created, vec![(branch.clone(), "main".to_string())]);
let sessions = state.coder_sessions.lock().await;
let entry = sessions.get(&attempt.session_id).expect("the session");
let s = entry.session.lock().await;
assert_eq!(s.state, super::session::CoderState::Merged);
if let Some(w) = &s.workspace_path {
assert!(!w.exists(), "the worktree outlived the session: {w:?}");
}
println!("session {} is {}", attempt.session_id, s.state.as_str());
}