use super::{Error, base, committed_role, dispatch, fork_point, governing, role};
use crate::template::GitRunner;
use crate::workspace;
use std::path::Path;
const REASON: &str = "a retarget re-forks a running agent off another config commit (ARCH §2.2)";
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Marks {
pub commit: Option<String>,
pub role: Option<String>,
}
impl Marks {
pub fn is_noop(&self) -> bool {
self.commit.is_none() && self.role.is_none()
}
}
pub fn preflight(
workspace_dir: &Path,
agent_id: &str,
config_name: &str,
role: Option<&str>,
git: &dyn GitRunner,
) -> Result<Marks, Error> {
workspace::require(workspace_dir)?;
workspace::require_agent(workspace_dir, agent_id, REASON, git)?;
workspace::require_lineage(workspace_dir, config_name, git)
.map_err(|e| Error::from(fork_point::Error::from(e)))?;
let repo = workspace::repo_git(workspace_dir);
let branch = workspace::agent_ref(agent_id);
let spec = format!("{}^{{commit}}", workspace::config_ref(config_name));
let target = git
.run_capture(&repo, &["rev-parse", &spec])
.map_err(|source| Error::Git {
op: "retarget resolve target",
source,
})?
.trim()
.to_string();
let held = committed_role(&repo, agent_id, &branch, git)?;
let asked = role.unwrap_or(&held);
let marks = Marks {
commit: (governing(workspace_dir, &branch, git)? != target).then(|| target.clone()),
role: (asked != held).then(|| asked.to_string()),
};
if marks.is_noop() {
return Ok(marks);
}
if marks.role.is_some() {
role::validate::against(
workspace_dir,
&target,
&format!("agent {agent_id:?}"),
asked,
git,
)?;
}
let tools = base::granted(workspace_dir, &target, asked, git)?;
dispatch::require_described(
&repo,
&dispatch::Grant {
role: asked,
tools: &tools,
config_commit: &target,
},
git,
)?;
Ok(marks)
}