Skip to content

Commit 108781a

Browse files
authored
fix(matrix): route phase placement by command (Automattic#844)
Route planning, execution, and finalization through the Homeboy placement contracts intended for each command so fixture-matrix orchestration remains deterministic. Closes Automattic#843. AI assistance: OpenAI openai/gpt-5.6-sol and openai/gpt-5.6-terra via OpenCode diagnosed the wrapper/control-plane contract, implemented the command-specific routing and regression coverage, and verified the published parser repair across PHP 8.1-8.4. Chris Huber remains responsible for every line.
1 parent e923036 commit 108781a

2 files changed

Lines changed: 104 additions & 12 deletions

File tree

tools/fixture-matrix.test.mjs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import runFixtureMatrixBench, {
3333
import {
3434
buildCodeFreshness,
3535
buildFixtureMatrixRunPlan,
36+
FIXTURE_MATRIX_PHASE_PLAN_SCHEMA,
37+
phaseFailureDiagnostic,
3638
SOLVED_ONLY_LANE_ID,
3739
resolvePathFreshness,
3840
summarizeBenchRun,
@@ -3892,7 +3894,7 @@ test('--solved-only selects exactly the valid solved fixture corpus', () => {
38923894
assert.ok(matrix.fixtures.every((fixture) => fixture.fixture_corpus === 'solved'));
38933895
});
38943896

3895-
test('fixture matrix operator composes typed placement only for the bench step', () => {
3897+
test('fixture matrix deterministic dry-run phase plans route setup locally and workload by mode', () => {
38963898
const root = mkdtempSync(path.join(tmpdir(), 'ssi-routing-plan-'));
38973899
const staticSiteImporter = path.join(root, 'static-site-importer');
38983900
const fixtureRoot = path.join(root, 'fixtures');
@@ -3908,6 +3910,19 @@ test('fixture matrix operator composes typed placement only for the bench step',
39083910
['rig', 'install', path.dirname(path.dirname(fileURLToPath(import.meta.url))), '--id', 'static-site-importer-fixture-matrix', '--reinstall'],
39093911
['rig', 'sync', 'static-site-importer-fixture-matrix'],
39103912
]);
3913+
assert.deepEqual(setupPlan.phase_plan, {
3914+
schema: FIXTURE_MATRIX_PHASE_PLAN_SCHEMA,
3915+
phases: setupPlan.steps.map(({ phase, placement, resolved_placement, reason, retry_command }) => ({
3916+
phase,
3917+
placement,
3918+
resolved_placement,
3919+
reason,
3920+
retry_command,
3921+
})),
3922+
});
3923+
assert.ok(setupPlan.steps.slice(0, -1).every((step) => step.phase === 'controller-setup' && step.placement === 'auto' && step.resolved_placement === 'controller-local'));
3924+
assert.equal(setupPlan.steps.at(-1).phase, 'fixture-workload');
3925+
assert.equal(setupPlan.steps.at(-1).resolved_placement, 'lab:homeboy-lab');
39113926

39123927
const labPlan = buildFixtureMatrixRunPlan({
39133928
runner: 'homeboy-lab',
@@ -3998,6 +4013,20 @@ test('fixture matrix operator composes typed placement only for the bench step',
39984013
});
39994014
assert.equal(autoPlan.execution_target, 'auto');
40004015
assert.deepEqual(autoPlan.steps.at(-1).args.slice(-2), ['--placement', 'auto']);
4016+
assert.equal(autoPlan.steps.at(-1).resolved_placement, 'auto');
4017+
});
4018+
4019+
test('fixture matrix phase retries identify setup ownership and the exact command', () => {
4020+
const setup = {
4021+
phase: 'controller-setup',
4022+
label: 'Sync/materialize rig components (lab:homeboy-lab)',
4023+
command: 'homeboy',
4024+
args: ['rig', 'sync', 'static-site-importer-fixture-matrix'],
4025+
};
4026+
assert.equal(
4027+
phaseFailureDiagnostic(setup, 17),
4028+
'Sync/materialize rig components (lab:homeboy-lab) failed with exit 17 during controller-setup. Retry: homeboy rig sync static-site-importer-fixture-matrix',
4029+
);
40014030
});
40024031

40034032
test('fixture matrix operator projects exact Homeboy arguments for every routing mode', () => {
@@ -4802,11 +4831,21 @@ test('summarizeBenchRun still throws when a non-zero bench produced no parseable
48024831
// No output file at all -> genuine crash, keep throwing.
48034832
assert.throws(
48044833
() => summarizeBenchRun({
4805-
plan: { mode: 'development-override', run_id: 'planned-run', output_file: missingOutput },
4834+
plan: {
4835+
mode: 'development-override',
4836+
run_id: 'planned-run',
4837+
output_file: missingOutput,
4838+
steps: [{
4839+
phase: 'fixture-workload',
4840+
label: 'Run SSI fixture matrix bench',
4841+
command: 'homeboy',
4842+
args: ['bench', '--rig', 'static-site-importer-fixture-matrix'],
4843+
}],
4844+
},
48064845
benchStatus: 1,
48074846
benchLabel: 'Run SSI fixture matrix bench',
48084847
}),
4809-
/Run SSI fixture matrix bench failed with exit 1/,
4848+
/Run SSI fixture matrix bench failed with exit 1 during fixture-workload\. Retry: homeboy bench --rig static-site-importer-fixture-matrix/,
48104849
);
48114850

48124851
// Output exists but is unparseable / carries no result payload -> still a crash.

tools/run-fixture-matrix.mjs

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { FIXTURE_MATRIX_RUN_FIELDS, MAX_EXTRA_SURFACE_COUNT, createFixtureMatrix
1515

1616
export const RIG_ID = 'static-site-importer-fixture-matrix';
1717
export 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

1920
const 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) {
680692
function 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+
728772
function withBenchRouting(args, options) {
729773
const routed = [...args];
730774
if (!(options.runner && options.placement === 'lab')) {
@@ -745,10 +789,16 @@ function withBenchRouting(args, options) {
745789
function 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).
754804
function runStep(step) {
@@ -763,7 +813,10 @@ function runStep(step) {
763813
// On crash, preserve the historical throw/error behavior.
764814
export 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

Comments
 (0)