Skip to content

Commit 4bb260f

Browse files
authored
Fix Lab cook harvest path provenance (#8197)
1 parent 175e1a8 commit 4bb260f

3 files changed

Lines changed: 107 additions & 3 deletions

File tree

src/core/agent_task_scheduler/mod.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,6 +1484,66 @@ mod committed_harvest_tests {
14841484
}));
14851485
}
14861486

1487+
#[test]
1488+
fn committed_harvest_preflight_uses_runner_workspace_not_controller_provenance() {
1489+
let _guard = LAB_ENV_LOCK
1490+
.get_or_init(|| Mutex::new(()))
1491+
.lock()
1492+
.expect("Lab environment lock");
1493+
std::env::remove_var(crate::core::observation::SOURCE_SNAPSHOT_METADATA_ENV);
1494+
std::env::remove_var(crate::core::observation::LAB_OFFLOAD_METADATA_ENV);
1495+
let temp = tempfile::tempdir().expect("tempdir");
1496+
let runner_workspace = temp.path().join("runner-workspace");
1497+
std::fs::create_dir(&runner_workspace).expect("runner workspace");
1498+
git(&runner_workspace, &["init", "-b", "main"]);
1499+
git(
1500+
&runner_workspace,
1501+
&["config", "user.email", "test@example.com"],
1502+
);
1503+
git(&runner_workspace, &["config", "user.name", "Homeboy Test"]);
1504+
std::fs::write(runner_workspace.join("file.txt"), "baseline\n").expect("baseline file");
1505+
git(&runner_workspace, &["add", "file.txt"]);
1506+
git(&runner_workspace, &["commit", "-m", "baseline"]);
1507+
let controller_workspace = temp.path().join("missing-controller-workspace");
1508+
let request = AgentTaskRequest {
1509+
schema: AGENT_TASK_REQUEST_SCHEMA.to_string(),
1510+
task_id: "runner-task".to_string(),
1511+
group_key: None,
1512+
parent_plan_id: None,
1513+
executor: AgentTaskExecutor {
1514+
backend: "test".to_string(),
1515+
selector: None,
1516+
runtime_selection: None,
1517+
required_capabilities: Vec::new(),
1518+
secret_env: Vec::new(),
1519+
model: None,
1520+
config: serde_json::Value::Null,
1521+
},
1522+
instructions: String::new(),
1523+
inputs: serde_json::Value::Null,
1524+
source_refs: Vec::new(),
1525+
workspace: AgentTaskWorkspace {
1526+
root: Some(runner_workspace.display().to_string()),
1527+
..Default::default()
1528+
},
1529+
component_contracts: Vec::new(),
1530+
policy: AgentTaskPolicy::default(),
1531+
limits: AgentTaskLimits::default(),
1532+
expected_artifacts: Vec::new(),
1533+
artifact_declarations: Vec::new(),
1534+
metadata: serde_json::json!({
1535+
"workspace_source_provenance": {
1536+
"controller_root": controller_workspace.display().to_string()
1537+
}
1538+
}),
1539+
};
1540+
1541+
let preflight = prepare_committed_harvest(&request).expect("runner workspace preflight");
1542+
1543+
assert!(preflight.base_sha.is_some());
1544+
assert!(!controller_workspace.exists());
1545+
}
1546+
14871547
#[test]
14881548
fn lab_snapshot_preflight_materializes_a_provider_ready_attempt_workspace() {
14891549
let _guard = LAB_ENV_LOCK

src/core/runner/lab_args/agent_task_specs.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ fn remap_agent_task_plan_spec(
190190
None,
191191
)
192192
})?;
193+
let controller_plan = value.clone();
193194
remap_paths_in_value(&mut value, mappings);
195+
record_controller_workspace_provenance(&mut value, &controller_plan);
194196
serde_json::to_string(&value).map_err(|err| {
195197
Error::internal_json(
196198
err.to_string(),
@@ -199,6 +201,41 @@ fn remap_agent_task_plan_spec(
199201
})
200202
}
201203

204+
fn record_controller_workspace_provenance(remapped: &mut Value, controller: &Value) {
205+
let Some(remapped_tasks) = remapped.get_mut("tasks").and_then(Value::as_array_mut) else {
206+
return;
207+
};
208+
let Some(controller_tasks) = controller.get("tasks").and_then(Value::as_array) else {
209+
return;
210+
};
211+
212+
for (remapped_task, controller_task) in remapped_tasks.iter_mut().zip(controller_tasks) {
213+
let Some(controller_root) = controller_task
214+
.get("workspace")
215+
.and_then(|workspace| workspace.get("root"))
216+
.and_then(Value::as_str)
217+
else {
218+
continue;
219+
};
220+
let Some(task) = remapped_task.as_object_mut() else {
221+
continue;
222+
};
223+
let metadata = task
224+
.entry("metadata".to_string())
225+
.or_insert(Value::Object(serde_json::Map::new()));
226+
if metadata.is_null() {
227+
*metadata = Value::Object(serde_json::Map::new());
228+
}
229+
let Some(metadata) = metadata.as_object_mut() else {
230+
continue;
231+
};
232+
metadata.insert(
233+
"workspace_source_provenance".to_string(),
234+
serde_json::json!({ "controller_root": controller_root }),
235+
);
236+
}
237+
}
238+
202239
fn remap_agent_task_fanout_input_spec(
203240
spec: &str,
204241
mappings: &[&LabPathRemap],

src/core/runner/lab_args/tests.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,10 @@ mod materialize_specs_tests {
18571857
staged["tasks"][0]["workspace"]["root"],
18581858
runner.display().to_string()
18591859
);
1860+
assert_eq!(
1861+
staged["tasks"][0]["metadata"]["workspace_source_provenance"]["controller_root"],
1862+
controller.display().to_string()
1863+
);
18601864
assert_eq!(
18611865
staged["tasks"][0]["executor"]["config"]["workspace"],
18621866
runner.display().to_string()
@@ -1885,10 +1889,13 @@ mod materialize_specs_tests {
18851889
staged["component_contracts"][0]["path"],
18861890
runner_component.display().to_string()
18871891
);
1888-
assert!(
1889-
!std::fs::read_to_string(runner.join("agent-task-attempt-plan.json"))
1892+
assert_eq!(
1893+
std::fs::read_to_string(runner.join("agent-task-attempt-plan.json"))
18901894
.expect("read staged plan")
1891-
.contains(&controller.display().to_string())
1895+
.matches(&controller.display().to_string())
1896+
.count(),
1897+
1,
1898+
"the controller path is retained only as provenance"
18921899
);
18931900
assert!(std::process::Command::new("git")
18941901
.args(["rev-parse", "--is-inside-work-tree"])

0 commit comments

Comments
 (0)