@@ -979,23 +979,7 @@ fn delegate_agent_task_lifecycle_to_pinned_runtime(
979979 let Some ( run_id) = run_id else {
980980 return Ok ( None ) ;
981981 } ;
982- let Some ( pinned) = crate :: agents:: agent_tasks:: lifecycle:: pinned_runtime_for_mutation ( run_id) ?
983- else {
984- return Ok ( None ) ;
985- } ;
986- let status = ProcessCommand :: new ( & pinned)
987- . args ( & normalized_args[ 1 ..] )
988- . status ( )
989- . map_err ( |error| {
990- homeboy:: core:: Error :: internal_io (
991- error. to_string ( ) ,
992- Some ( format ! (
993- "execute pinned controller runtime {}" ,
994- pinned. display( )
995- ) ) ,
996- )
997- } ) ?;
998- Ok ( Some ( status. code ( ) . unwrap_or ( 1 ) ) )
982+ delegate_agent_task_lifecycle_to_resolved_runtime ( run_id, normalized_args)
999983}
1000984
1001985fn delegate_cook_continue_to_pinned_runtime (
@@ -1007,31 +991,59 @@ fn delegate_cook_continue_to_pinned_runtime(
1007991 if current_runtime_owns_terminal_cook_continuation ( & run_id) ? {
1008992 return Ok ( None ) ;
1009993 }
994+ delegate_agent_task_lifecycle_to_resolved_runtime ( & run_id, normalized_args)
995+ }
996+
997+ enum AgentTaskLifecyclePinnedRuntime {
998+ Runner ( crate :: agents:: agent_tasks:: lifecycle:: RunnerPinnedRuntime ) ,
999+ Controller ( std:: path:: PathBuf ) ,
1000+ }
1001+
1002+ /// Select runner authority before validating a controller-local pin. Historical
1003+ /// runner records carry paths that are intentionally unavailable on controller.
1004+ fn agent_task_lifecycle_pinned_runtime_for_mutation (
1005+ run_id : & str ,
1006+ ) -> homeboy:: core:: Result < Option < AgentTaskLifecyclePinnedRuntime > > {
10101007 if let Some ( pinned) =
1011- crate :: agents:: agent_tasks:: lifecycle:: runner_pinned_runtime_for_mutation ( & run_id) ?
1008+ crate :: agents:: agent_tasks:: lifecycle:: runner_pinned_runtime_for_mutation ( run_id) ?
10121009 {
1013- return delegate_cook_continue_to_runner_pinned_runtime ( & pinned, normalized_args) . map ( Some ) ;
1010+ return Ok ( Some ( AgentTaskLifecyclePinnedRuntime :: Runner ( pinned) ) ) ;
1011+ }
1012+ Ok (
1013+ crate :: agents:: agent_tasks:: lifecycle:: pinned_runtime_for_mutation ( run_id) ?
1014+ . map ( AgentTaskLifecyclePinnedRuntime :: Controller ) ,
1015+ )
1016+ }
1017+
1018+ fn delegate_agent_task_lifecycle_to_resolved_runtime (
1019+ run_id : & str ,
1020+ normalized_args : & [ String ] ,
1021+ ) -> homeboy:: core:: Result < Option < i32 > > {
1022+ match agent_task_lifecycle_pinned_runtime_for_mutation ( run_id) ? {
1023+ Some ( AgentTaskLifecyclePinnedRuntime :: Runner ( pinned) ) => {
1024+ delegate_agent_task_lifecycle_to_runner_pinned_runtime ( & pinned, normalized_args)
1025+ . map ( Some )
1026+ }
1027+ Some ( AgentTaskLifecyclePinnedRuntime :: Controller ( pinned) ) => {
1028+ let status = ProcessCommand :: new ( & pinned)
1029+ . args ( & normalized_args[ 1 ..] )
1030+ . status ( )
1031+ . map_err ( |error| {
1032+ homeboy:: core:: Error :: internal_io (
1033+ error. to_string ( ) ,
1034+ Some ( format ! (
1035+ "execute pinned controller runtime {}" ,
1036+ pinned. display( )
1037+ ) ) ,
1038+ )
1039+ } ) ?;
1040+ Ok ( Some ( status. code ( ) . unwrap_or ( 1 ) ) )
1041+ }
1042+ None => Ok ( None ) ,
10141043 }
1015- let Some ( pinned) = crate :: agents:: agent_tasks:: lifecycle:: pinned_runtime_for_mutation ( & run_id) ?
1016- else {
1017- return Ok ( None ) ;
1018- } ;
1019- let status = ProcessCommand :: new ( & pinned)
1020- . args ( & normalized_args[ 1 ..] )
1021- . status ( )
1022- . map_err ( |error| {
1023- homeboy:: core:: Error :: internal_io (
1024- error. to_string ( ) ,
1025- Some ( format ! (
1026- "execute pinned controller runtime {}" ,
1027- pinned. display( )
1028- ) ) ,
1029- )
1030- } ) ?;
1031- Ok ( Some ( status. code ( ) . unwrap_or ( 1 ) ) )
10321044}
10331045
1034- fn delegate_cook_continue_to_runner_pinned_runtime (
1046+ fn delegate_agent_task_lifecycle_to_runner_pinned_runtime (
10351047 pinned : & crate :: agents:: agent_tasks:: lifecycle:: RunnerPinnedRuntime ,
10361048 normalized_args : & [ String ] ,
10371049) -> homeboy:: core:: Result < i32 > {
@@ -1044,14 +1056,36 @@ fn delegate_cook_continue_to_runner_pinned_runtime(
10441056 raw_exec : true ,
10451057 ..Default :: default ( )
10461058 } ,
1047- ) ?;
1059+ )
1060+ . map_err ( |error| annotate_runner_pinned_runtime_failure ( error, & pinned. runner_id ) ) ?;
10481061 if !output. stderr . is_empty ( ) {
10491062 eprint ! ( "{}" , output. stderr) ;
10501063 }
10511064 print ! ( "{}" , output. stdout) ;
10521065 Ok ( exit_code)
10531066}
10541067
1068+ fn annotate_runner_pinned_runtime_failure (
1069+ mut error : homeboy:: core:: Error ,
1070+ runner_id : & str ,
1071+ ) -> homeboy:: core:: Error {
1072+ error. message = format ! (
1073+ "runner `{runner_id}` could not execute its pinned controller runtime: {}" ,
1074+ error. message
1075+ ) ;
1076+ if !error. details . is_object ( ) {
1077+ error. details = serde_json:: json!( { } ) ;
1078+ }
1079+ error. details [ "runner_id" ] = serde_json:: json!( runner_id) ;
1080+ error. details [ "next_actions" ] = serde_json:: json!( [
1081+ format!( "homeboy runner status {runner_id}" ) ,
1082+ format!( "homeboy runner connect {runner_id}" )
1083+ ] ) ;
1084+ error. with_hint ( format ! (
1085+ "Verify runner `{runner_id}` is reachable, then retry the exact durable command."
1086+ ) )
1087+ }
1088+
10551089/// A terminal recipe-bound continuation is controller-owned. Provider execution
10561090/// remains pinned, but harvest, artifact hydration, gates, and finalization must
10571091/// run where the immutable Cook recipe is stored rather than on Lab.
@@ -3203,6 +3237,62 @@ mod tests {
32033237 ) ;
32043238 }
32053239
3240+ #[ test]
3241+ fn run_delegates_a_runner_owned_linux_v2_pin_before_controller_validation ( ) {
3242+ crate :: test_support:: with_isolated_home ( |_| {
3243+ let run_id = "runner-owned-linux-v2-run" ;
3244+ let plan: crate :: agents:: agent_tasks:: scheduler:: AgentTaskPlan = serde_json:: from_str (
3245+ include_str ! ( "../../../tests/fixtures/agent_task_smoke_plan.json" ) ,
3246+ )
3247+ . expect ( "deserialize durable test plan" ) ;
3248+ crate :: agents:: agent_tasks:: lifecycle:: submit_plan ( & plan, Some ( run_id) )
3249+ . expect ( "persist durable run" ) ;
3250+ crate :: agents:: agent_tasks:: lifecycle:: record_detached_lab_run (
3251+ crate :: agents:: agent_tasks:: lifecycle:: DetachedLabRunRecord {
3252+ run_id,
3253+ runner_id : "homeboy-lab" ,
3254+ runner_job_id : "linux-v2-pin-job" ,
3255+ remote_workspace : "/home/chubes/reaped-lab-workspace" ,
3256+ remote_command : & [
3257+ "homeboy" . to_string ( ) ,
3258+ "agent-task" . to_string ( ) ,
3259+ "run" . to_string ( ) ,
3260+ ] ,
3261+ } ,
3262+ )
3263+ . expect ( "persist runner authority" ) ;
3264+ crate :: agents:: agent_tasks:: lifecycle:: rewrite_record_for_test ( run_id, |record| {
3265+ record. metadata [ homeboy:: core:: controller_runtime:: CONTROLLER_RUNTIME_METADATA_KEY ] =
3266+ serde_json:: json!( {
3267+ "schema" : "homeboy/controller-runtime-pin/v2" ,
3268+ "originating" : {
3269+ "build_identity" : "homeboy linux-runner" ,
3270+ "pinned_executable" : "/home/chubes/.local/share/homeboy/controller-runtimes/linux/homeboy" ,
3271+ "sha256" : "linux-runner-sha256"
3272+ }
3273+ } ) ;
3274+ } )
3275+ . expect ( "persist runner-owned v2 pin" ) ;
3276+
3277+ let runtime = agent_task_lifecycle_pinned_runtime_for_mutation ( run_id)
3278+ . expect ( "runner authority is selected before controller-local validation" )
3279+ . expect ( "runner-owned v2 pin selects a runtime" ) ;
3280+ assert ! ( matches!(
3281+ runtime,
3282+ AgentTaskLifecyclePinnedRuntime :: Runner ( ref pinned)
3283+ if pinned. runner_id == "homeboy-lab"
3284+ && pinned. executable
3285+ == std:: path:: Path :: new( "/home/chubes/.local/share/homeboy/controller-runtimes/linux/homeboy" )
3286+ ) ) ;
3287+ assert_eq ! (
3288+ crate :: agents:: agent_tasks:: lifecycle:: status( run_id)
3289+ . expect( "runtime resolution leaves the durable record intact" )
3290+ . run_id,
3291+ run_id
3292+ ) ;
3293+ } ) ;
3294+ }
3295+
32063296 #[ cfg( unix) ]
32073297 #[ test]
32083298 fn terminal_cook_with_a_controller_recipe_does_not_route_to_lab_or_origin_runtime ( ) {
0 commit comments