@@ -15,6 +15,7 @@ import { FIXTURE_MATRIX_RUN_FIELDS, MAX_EXTRA_SURFACE_COUNT, createFixtureMatrix
1515
1616export const RIG_ID = 'static-site-importer-fixture-matrix' ;
1717export const SOLVED_ONLY_LANE_ID = 'fixtures-solved-only/v1' ;
18+ export const FIXTURE_MATRIX_PHASE_PLAN_SCHEMA = 'static-site-importer/fixture-matrix-phase-plan/v1' ;
1819
1920const packageRoot = path . dirname ( path . dirname ( fileURLToPath ( import . meta. url ) ) ) ;
2021
@@ -122,6 +123,7 @@ export function buildFixtureMatrixRunPlan(input) {
122123 ...buildFreshnessWarnings ( codeFreshness , options ) ,
123124 ] ;
124125
126+ const steps = buildSteps ( options , settings ) ;
125127 return {
126128 schema : 'static-site-importer/fixture-matrix-operator-run/v1' ,
127129 mode : options . mode ,
@@ -195,7 +197,17 @@ export function buildFixtureMatrixRunPlan(input) {
195197 } ,
196198 }
197199 : { } ,
198- steps : buildSteps ( options , settings ) ,
200+ phase_plan : {
201+ schema : FIXTURE_MATRIX_PHASE_PLAN_SCHEMA ,
202+ phases : steps . map ( ( { phase, placement, resolved_placement, reason, retry_command } ) => ( {
203+ phase,
204+ placement,
205+ resolved_placement,
206+ reason,
207+ retry_command,
208+ } ) ) ,
209+ } ,
210+ steps,
199211 } ;
200212}
201213
@@ -680,18 +692,26 @@ function gitText(cwd, args, gitRunner) {
680692function buildSteps ( options , settings ) {
681693 const steps = [ ] ;
682694 if ( ! options . skipInstall ) {
683- steps . push ( {
695+ steps . push ( createPhaseStep ( {
696+ phase : 'controller-setup' ,
697+ placement : 'auto' ,
698+ resolvedPlacement : 'controller-local' ,
699+ reason : 'Rig source installation remains on the controller and does not inherit fixture workload routing.' ,
684700 label : `Refresh installed SSI fixture matrix rig (${ options . executionTarget } )` ,
685701 command : options . homeboyBin ,
686702 args : [ 'rig' , 'install' , packageRoot , '--id' , RIG_ID , '--reinstall' ] ,
687- } ) ;
703+ } ) ) ;
688704 }
689705 if ( ! options . skipSync ) {
690- steps . push ( {
706+ steps . push ( createPhaseStep ( {
707+ phase : 'controller-setup' ,
708+ placement : 'auto' ,
709+ resolvedPlacement : 'controller-local' ,
710+ reason : 'Rig component synchronization remains on the controller and does not inherit fixture workload routing.' ,
691711 label : `Sync/materialize rig components (${ options . executionTarget } )` ,
692712 command : options . homeboyBin ,
693713 args : [ 'rig' , 'sync' , RIG_ID ] ,
694- } ) ;
714+ } ) ) ;
695715 }
696716
697717 const benchArgs = [
@@ -716,15 +736,39 @@ function buildSteps(options, settings) {
716736 if ( options . passthrough . length > 0 ) {
717737 routedBenchArgs . push ( '--' , ...options . passthrough ) ;
718738 }
719- steps . push ( {
739+ steps . push ( createPhaseStep ( {
740+ phase : 'fixture-workload' ,
741+ placement : options . placement ,
742+ resolvedPlacement : workloadResolvedPlacement ( options ) ,
743+ reason : 'Fixture execution owns the requested Homeboy placement and runner.' ,
720744 label : `Run SSI fixture matrix bench through Homeboy/Lab/WP Codebox (${ options . executionTarget } )` ,
721745 command : options . homeboyBin ,
722746 args : routedBenchArgs ,
723- } ) ;
747+ } ) ) ;
724748
725749 return steps ;
726750}
727751
752+ function createPhaseStep ( { phase, placement, resolvedPlacement, reason, label, command, args } ) {
753+ const step = {
754+ phase,
755+ placement,
756+ resolved_placement : resolvedPlacement ,
757+ reason,
758+ label,
759+ command,
760+ args,
761+ } ;
762+ return { ...step , retry_command : shellCommand ( step ) } ;
763+ }
764+
765+ function workloadResolvedPlacement ( options ) {
766+ if ( options . runner ) {
767+ return `lab:${ options . runner } ` ;
768+ }
769+ return options . placement ;
770+ }
771+
728772function withBenchRouting ( args , options ) {
729773 const routed = [ ...args ] ;
730774 if ( ! ( options . runner && options . placement === 'lab' ) ) {
@@ -745,10 +789,16 @@ function withBenchRouting(args, options) {
745789function runCommand ( step ) {
746790 const status = runStep ( step ) ;
747791 if ( status !== 0 ) {
748- throw new Error ( ` ${ step . label } failed with exit ${ status } ` ) ;
792+ throw new Error ( phaseFailureDiagnostic ( step , status ) ) ;
749793 }
750794}
751795
796+ export function phaseFailureDiagnostic ( step , status ) {
797+ const phase = step . phase || 'unknown' ;
798+ const retry = step . retry_command || shellCommand ( step ) ;
799+ return `${ step . label } failed with exit ${ status } during ${ phase } . Retry: ${ retry } ` ;
800+ }
801+
752802// Run a step and return its exit status without throwing, so callers can decide
753803// whether a non-zero exit is fatal (setup) or an expected outcome (bench gate).
754804function runStep ( step ) {
@@ -763,7 +813,10 @@ function runStep(step) {
763813// On crash, preserve the historical throw/error behavior.
764814export function summarizeBenchRun ( { plan, benchStatus, benchLabel = 'bench step' } ) {
765815 if ( benchStatus !== 0 && ! benchProducedResult ( plan . output_file ) ) {
766- throw new Error ( `${ benchLabel } failed with exit ${ benchStatus } ` ) ;
816+ const benchStep = plan . steps ?. at ( - 1 ) ;
817+ throw new Error ( benchStep
818+ ? phaseFailureDiagnostic ( benchStep , benchStatus )
819+ : `${ benchLabel } failed with exit ${ benchStatus } ` ) ;
767820 }
768821 const gateFailed = benchStatus !== 0 ;
769822 return {
0 commit comments