Skip to content

Commit 476361e

Browse files
committed
fix(pm): dispatch-gates still derives the full-battery jobs' leads
ci.yml's moved jobs now spell `&& github.event_name != 'pull_request' && github.event_name != 'merge_group'` after their filter output, and the strict `if:` whitelist refused the whole expression: a `.objectui-sha` card derived no console gates and a dogfood card no dogfood suite, exactly when a PR no longer runs those jobs and this derivation is the only place the dev hears of them before a main-red. The clause is stripped like `!cancelled()` — exactly as spelled, only as the last conjunct; reordered, partial, positive or leading spellings still refuse (five new cases). The marker census follows check-expected-skips' header growing two lines. Claude-Session: https://claude.ai/code/session_01LqNgdPZB2Jnr5HpGbaqLeB Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5354b52 commit 476361e

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

‎scripts/pm/dispatch-gates.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,8 +1178,20 @@ export function extractTriggerPaths(workflowText) {
11781178
* is refused whole. `!cancelled()` is the one term stripped, because it is a
11791179
* status function that discriminates no path — THE FILTER CONTRACT on ci.yml's
11801180
* `filter` job is why every one of these `if:`s carries it.
1181+
*
1182+
* ⭐ The FULL-BATTERY clause is stripped too, exactly as spelled and only as the
1183+
* expression's LAST conjunct (#22998, ci.yml's THE TWO SPEEDS): `&& github.
1184+
* event_name != 'pull_request' && github.event_name != 'merge_group'`. It
1185+
* discriminates EVENTS, never paths — the job still judges exactly the paths its
1186+
* filter output names, on `main`'s push and hourly runs — and since a PR no
1187+
* longer runs such a job, this derivation is the only place a card's dev hears
1188+
* of it before the red lands on `main`. Refusing it would drop the console and
1189+
* dogfood leads this function exists to give. Any other event term, a reordered
1190+
* clause or one event alone is still refused whole.
11811191
*/
11821192
const JOB_IF_STATUS_TERM = /(?:^|\s)!\s*cancelled\(\)\s*&&\s*/g;
1193+
const JOB_IF_FULL_BATTERY_TERM =
1194+
/\s*&&\s*github\.event_name\s*!=\s*'pull_request'\s*&&\s*github\.event_name\s*!=\s*'merge_group'\s*$/;
11831195
const JOB_FILTER_OUTPUT_TERM =
11841196
/^needs\.([A-Za-z_][\w-]*)\.outputs\.([A-Za-z_][\w-]*)\s*(?:!=\s*(['"])false\3|==\s*(['"])true\4)$/;
11851197

@@ -1195,6 +1207,7 @@ export function jobFilterOutputRefs(ifExpression) {
11951207
const wrapped = /^\$\{\{([\s\S]*)\}\}$/.exec(expr);
11961208
if (wrapped) expr = wrapped[1].trim();
11971209
expr = expr.replace(JOB_IF_STATUS_TERM, ' ').trim();
1210+
expr = expr.replace(JOB_IF_FULL_BATTERY_TERM, '').trim();
11981211
// One balanced outer paren pair at a time — `(A || B)` is the live spelling.
11991212
for (;;) {
12001213
if (!expr.startsWith('(') || !expr.endsWith(')')) break;

‎scripts/pm/dispatch-gates.self-test.mjs‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5484,6 +5484,30 @@ export function selfTest({ tier = 'full' } = {}) {
54845484
t('a term this cannot read refuses the WHOLE expression', jobFilterOutputRefs("${{ github.event_name == 'push' || needs.f.outputs.a != 'false' }}") === null);
54855485
t('an if: naming no filter output at all yields no population', jobFilterOutputRefs("${{ github.ref == 'refs/heads/main' }}") === null);
54865486
t('an absent if: is not an expression', jobFilterOutputRefs(null) === null);
5487+
// #22998: the full-battery clause ci.yml's moved jobs carry discriminates
5488+
// EVENTS, not paths — stripped exactly as spelled, and only as the last
5489+
// conjunct; every near-miss still refuses the whole expression.
5490+
const FULL_BATTERY = "github.event_name != 'pull_request' && github.event_name != 'merge_group'";
5491+
t(
5492+
'the full-battery event clause is stripped like !cancelled(): the job still judges its filter output',
5493+
jobFilterOutputRefs(`\${{ !cancelled() && needs.filter.outputs.console != 'false' && ${FULL_BATTERY} }}`)?.map((r) => `${r.job}.${r.output}`).join() === 'filter.console',
5494+
);
5495+
t(
5496+
'...but only as spelled: the two events reordered refuse',
5497+
jobFilterOutputRefs("${{ needs.f.outputs.a != 'false' && github.event_name != 'merge_group' && github.event_name != 'pull_request' }}") === null,
5498+
);
5499+
t(
5500+
'...one excluded event alone refuses — it is not the full-battery clause',
5501+
jobFilterOutputRefs("${{ needs.f.outputs.a != 'false' && github.event_name != 'pull_request' }}") === null,
5502+
);
5503+
t(
5504+
'...a positive event term refuses — it says when the job runs, not that a PR never does',
5505+
jobFilterOutputRefs("${{ needs.f.outputs.a != 'false' && github.event_name == 'push' }}") === null,
5506+
);
5507+
t(
5508+
'...and the clause in front of the filter output is not the stripped spelling either',
5509+
jobFilterOutputRefs(`\${{ ${FULL_BATTERY} && needs.f.outputs.a != 'false' }}`) === null,
5510+
);
54875511

54885512
const fixturePops = jobPathPopulations(jobFilterWf, 'fixture.yml');
54895513
t(
@@ -7289,7 +7313,8 @@ export function selfTest({ tier = 'full' } = {}) {
72897313
// bare diff-scoped run declares the two step-`env:` values it does not need.
72907314
'scripts/check-issue-citations.mjs:204 local-env',
72917315
'scripts/cli-build-prerequisite.mjs:111 inherited-population',
7292-
'scripts/pm/check-expected-skips.mjs:131 self-test-reads',
7316+
// 131 -> 133 (#22998): the header gained two lines naming the full-battery-only jobs.
7317+
'scripts/pm/check-expected-skips.mjs:133 self-test-reads',
72937318
'scripts/pm/dispatch-gates.mjs:736 inherited-population',
72947319
].join(' · '),
72957320
censusRows.join(' · '),

0 commit comments

Comments
 (0)