use super::*;
fn git(root: &Path, args: &[&str]) -> String {
let output = std::process::Command::new("git")
.current_dir(root)
.args(args)
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
fn shell(directory: &Path, command: &str) -> serde_json::Value {
serde_json::json!({"cwd":directory,"command":format!("/bin/sh -c '{}'", command.replace('\'', "'\\''")),"timeout_ms":10000})
}
pub(super) fn install_script(request: &mut PluginWorkflowStartRequest, root: &Path, trees: &Path) {
let reference = |index: usize, pointer: &str| serde_json::json!({"$fake_result":{"index":index,"pointer":pointer}});
let base = git(root, &["rev-parse", "HEAD"]);
let task = |id: &str, creation_index: usize| {
let workspace = trees.join(id);
let contents = if id == "left" {
LEFT_MODULE
} else {
RIGHT_MODULE
};
let write = serde_json::json!({"path":workspace.join("integrated.sh"),"contents":contents});
let revision_path = trees.join(format!("{id}-revision.json"));
let commit = shell(
&workspace,
&format!(
"git add integrated.sh && git -c user.name=Test -c user.email=test@example.invalid commit -m contribution && printf '\"%s\"' \"$(git rev-parse HEAD)\" > {}",
revision_path.display()
),
);
let revision = shell(&workspace, "printf '%s' \"$(git rev-parse HEAD)\"");
let result = serde_json::json!({"summary":format!("Committed {id}"),"evidence":["Commit command succeeded"],"blockers":[],"contributions":[{
"workspace":workspace,"base_revision":base,"source_directory":root,"source_had_local_changes":true,
"produced_revisions":[{"$fake_result":{"index":0,"pointer":"/output"}}],"artifacts":[workspace.join("integrated.sh")],"validation":[],
"remaining_work":["Resolve integration conflict and verify combined result"],"retention":"retained"
}]});
serde_json::json!({"task_id":id,"objective":format!("Contribute in the assigned checkout.\ntool-call filesystem.write {write}\ntool-call shell.run {commit}\ntool-call shell.run {revision}\nstructured-result {result}"),
"output":{"type_name":"test.git_contribution.v1","schema":{"type":"object","additionalProperties":false,"required":["summary","evidence","blockers","contributions"],"properties":{"summary":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}},"blockers":{"type":"array","items":{"type":"string"}},"contributions":{"type":"array","items":{"type":"object"}}}}},
"agent_profile":"build","read_only":false,"worktree_directory":reference(creation_index, "/path"),
"acceptance_criteria":["Creation-time base commit (verify before editing):",reference(creation_index, "/provenance/base_commit"),"Canonical source checkout (do not modify):",reference(creation_index, "/provenance/source_directory"),"Creation provenance does not include uncommitted inputs; inspect source dirtiness and report required missing inputs rather than copying or overwriting user work."],
"tool_allowlist":["filesystem.write","filesystem.read","shell.run"],"resources":[{"resource":format!("checkout:{id}"),"access":"write"}],
"model_selection":{"provider":"bcode.fake-provider","model":"fake-echo"}})
};
let integration = trees.join("integration");
let probe = shell(
&integration,
"git -c user.name=Test -c user.email=test@example.invalid cherry-pick swarm-left && git -c user.name=Test -c user.email=test@example.invalid cherry-pick swarm-right",
);
let inspect = shell(
&integration,
"test -n \"$(git ls-files -u)\" && git rev-parse CHERRY_PICK_HEAD && git status --short",
);
let resolution = serde_json::json!({"path":integration.join("integrated.sh"),"contents":format!("{LEFT_MODULE}{RIGHT_MODULE}")});
let verify = shell(
&integration,
&format!(
"git add integrated.sh && git -c user.name=Test -c user.email=test@example.invalid -c core.editor=true cherry-pick --continue && ({COMBINED_CHECK}) && test -z \"$(git status --porcelain)\" && printf '\"%s\"' \"$(git rev-parse HEAD)\" > revision.txt && printf verified > verification.txt"
),
);
let group = serde_json::json!({
"mutation_id":"isolated-goal-workers",
"run_id":reference(0, "/run_id"),"expected_revision":reference(0, "/graph/revision"),
"bind_source_activation":reference(0, "/activation_id"),
"tasks":[task("left", 3),task("right", 2)],
"continuation":{"objective":format!("Integrate both retained contributions. Inspect the actual conflict before resolving it; never overwrite the source checkout.\ntool-call-expect-error CONFLICT :: shell.run {probe}\ntool-call shell.run {inspect}\ntool-call filesystem.write {resolution}\ntool-call shell.run {verify}"),
"agent_profile":"build","read_only":false,"worktree_directory":reference(1, "/path"),"tool_allowlist":["shell.run","filesystem.write"],
"acceptance_criteria":["Integration creation-time base commit (verify before applying contributions):",reference(1, "/provenance/base_commit"),"Canonical source checkout (retain all user edits):",reference(1, "/provenance/source_directory"),"Retain conflicts and successful contributions; creation provenance is an observation, not authorization to overwrite the source."],
"resources":[{"resource":"checkout:integration","access":"write"}],"model_selection":{"provider":"bcode.fake-provider","model":"fake-echo"}},
"reconciliation":[]
});
let publication =
serde_json::json!({"$fake_result":{"index":0,"pointer":"/publication_arguments"}});
let source = request
.definition
.nodes
.get_mut("loop.implementation")
.unwrap();
let instructions = source.configuration["system_prompt"].as_str().unwrap();
let preparation = ["left", "right", "integration"]
.map(|id| {
let create = serde_json::json!({
"name":id,"cwd":root,"path":trees.join(id),
"new_branch":format!("swarm-{id}"),"base_ref":"head","no_setup":true
});
format!("tool-call worktree.create {create}")
})
.join("\n");
source.configuration["system_prompt"] = serde_json::json!(format!(
"{instructions}\n{preparation}\ntool-call workflow.execution_context {{\"compact\":true,\"limit\":1}}\ntool-call workflow.stage_delegation {group}\ntool-call workflow.publish_run_graph_edit {publication}"
));
install_evaluation_inspection(request, &integration);
}
fn install_evaluation_inspection(request: &mut PluginWorkflowStartRequest, integration: &Path) {
let inspect = |index, node| {
serde_json::json!({"$fake_result":{
"index":index,"pointer":"/outputs","where":{"node_id":node},
"select":"/inspection_arguments","latest_by":"created_at_ms"
}})
};
let left = inspect(0, "left");
let right = inspect(1, "right");
let revision =
serde_json::json!({"path":integration.join("revision.txt"),"offset":1,"limit":1});
let read = serde_json::json!({"path":integration.join("integrated.sh"),"offset":1,"limit":100});
let evaluation = request.definition.nodes.get_mut("loop.evaluation").unwrap();
let instructions = evaluation.configuration["system_prompt"].as_str().unwrap();
evaluation.configuration["system_prompt"] = serde_json::json!(format!(
"{instructions}\ntool-call workflow.execution_context {{\"outputs_only\":true,\"limit\":3,\"$fake_json_pages\":{{\"items\":\"/outputs\",\"next\":\"/next_page_arguments\"}}}}\ntool-call workflow.execution_context {left}\ntool-call workflow.execution_context {right}\ntool-call filesystem.read {revision}\ntool-call filesystem.read {read}"
));
}
fn prepare_source(root: &Path) -> String {
git(root, &["init", "--quiet"]);
std::fs::write(root.join("integrated.sh"), "# base\n").unwrap();
git(root, &["add", "integrated.sh"]);
git(
root,
&[
"-c",
"user.name=Test",
"-c",
"user.email=test@example.invalid",
"commit",
"-m",
"base",
],
);
let base = git(root, &["rev-parse", "HEAD"]);
std::fs::write(root.join("staged.txt"), "staged user work\n").unwrap();
git(root, &["add", "staged.txt"]);
std::fs::write(root.join("untracked.txt"), "untracked user work\n").unwrap();
std::fs::write(
root.join("integrated.sh"),
"# uncommitted user implementation\n",
)
.unwrap();
base
}
#[tokio::test]
async fn goal_workers_recover_real_git_conflict_in_isolated_integration_checkout() {
let _execution = GOAL_ENTRY_EXECUTION.lock().await;
let root = tempfile::tempdir().unwrap();
let trees = tempfile::tempdir().unwrap();
let base = prepare_source(root.path());
let status_args = [
"status",
"--porcelain",
"--",
"integrated.sh",
"staged.txt",
"untracked.txt",
];
let source_status = git(root.path(), &status_args);
let source_index = git(root.path(), &["diff", "--cached"]);
assert_eq!(std::fs::read_dir(trees.path()).unwrap().count(), 0);
let sessions = publication_fixture_sessions(root.path(), true);
let session = sessions
.create_session(None, root.path().into())
.await
.unwrap();
let store = bcode_workflow_store::WorkflowStore::open_in_state_dir(root.path()).unwrap();
let mut state = Arc::new(test_server_state_with_workflow_authorization(
sessions, store,
));
configure_goal_execution(Arc::get_mut(&mut state).unwrap(), root.path());
state.start_workflow_driver().await;
let request = goal_entry_request_with_workspace(
session.id,
root.path(),
Arc::clone(&state),
None,
Some(trees.path().into()),
)
.await;
let run_id = request.run_id.unwrap();
tokio::time::timeout(Duration::from_mins(1), async {
loop {
approve_goal_permissions(&state, session.id).await;
let outputs = state
.workflow_store
.lock()
.unwrap()
.validated_outputs(&run_id, 100)
.unwrap();
if let Some(evaluation) = outputs
.iter()
.find(|output| output.node_id == "loop.evaluation")
{
assert_eq!(evaluation.value["condition_met"], true, "{outputs:?}");
for id in ["left", "right"] {
let output = outputs.iter().find(|output| output.node_id == id).unwrap();
assert_eq!(output.value["contributions"][0]["base_revision"], base);
let worker_revision = git(&trees.path().join(id), &["rev-parse", "HEAD"]);
assert_ne!(worker_revision, base);
let produced = output.value["contributions"][0]["produced_revisions"]
.as_array()
.unwrap();
assert_eq!(produced.len(), 1);
assert_eq!(
produced[0].as_str().unwrap().trim_end_matches(['\r', '\n']),
worker_revision
);
assert_eq!(
output.value["contributions"][0]["workspace"],
trees.path().join(id).to_str().unwrap()
);
}
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
})
.await
.unwrap_or_else(|error| {
let (attempts, outputs, terminal) = {
let store = state.workflow_store.lock().unwrap();
(store.attempt_history(&run_id, None, 100).unwrap(), store.validated_outputs(&run_id, 100).unwrap(), store.canonical_terminal_output(&run_id).unwrap())
};
let output_facts = outputs.iter().map(|output| (&output.node_id, output.value.get("condition_met"))).collect::<Vec<_>>();
let failures = state.workflow_store.lock().unwrap().failure_history(&run_id, 10).unwrap();
let failure_facts = failures.iter().map(|event| {
let error = event.payload.get("error").or_else(|| event.payload.get("message")).map_or_else(|| "unavailable".into(), serde_json::Value::to_string);
(&event.event_type, event.payload.get("node_id"), error[..error.floor_char_boundary(error.len().min(1024))].to_owned())
}).collect::<Vec<_>>();
panic!("isolated goal recovery must reach evaluation: {error}; terminal_present: {}; output_facts: {output_facts:?}; failures: {failure_facts:?}; attempts: {attempts:?}", terminal.is_some());
});
drop(state);
assert_eq!(git(root.path(), &["rev-parse", "HEAD"]), base);
assert_eq!(git(root.path(), &status_args), source_status);
assert_eq!(git(root.path(), &["diff", "--cached"]), source_index);
assert_retained_checkouts(root.path(), trees.path(), &base);
}
fn assert_retained_checkouts(root: &Path, trees: &Path, base: &str) {
for (name, contents) in [
("staged.txt", "staged user work\n"),
("untracked.txt", "untracked user work\n"),
] {
assert_eq!(std::fs::read_to_string(root.join(name)).unwrap(), contents);
assert!(!trees.join("integration").join(name).exists());
}
assert_eq!(
std::fs::read_to_string(root.join("integrated.sh")).unwrap(),
"# uncommitted user implementation\n"
);
let integration = trees.join("integration");
assert_eq!(
std::fs::read_to_string(integration.join("integrated.sh")).unwrap(),
format!("{LEFT_MODULE}{RIGHT_MODULE}")
);
assert_eq!(
std::fs::read_to_string(integration.join("verification.txt")).unwrap(),
"verified"
);
assert!(git(&integration, &["ls-files", "-u"]).is_empty());
for id in ["left", "right"] {
assert_ne!(git(&trees.join(id), &["rev-parse", "HEAD"]), base);
assert!(git(&trees.join(id), &["status", "--porcelain"]).is_empty());
}
}