@@ -162,6 +162,7 @@ import {
162162 firstPartyImportBindings,
163163 firstPartyImportTargets,
164164 followCompositeActions,
165+ fullBatteryOnlyClause,
165166 gateFamilyFiles,
166167 generatedModuleSources,
167168 globCarriesLiteralSuffix,
@@ -184,8 +185,10 @@ import {
184185 isTypeCheckInvocation,
185186 jobFilterOutputRefs,
186187 jobFilterPopulation,
188+ jobFilteredNotMeasuredLine,
187189 jobFilteredStepLines,
188190 jobFilteredSteps,
191+ jobIfReading,
189192 jobPathPopulations,
190193 joinLineContinuations,
191194 judgedAsPattern,
@@ -5670,6 +5673,17 @@ export function selfTest({ tier = 'full' } = {}) {
56705673 '...and the clause in front of the filter output is not the stripped spelling either',
56715674 jobFilterOutputRefs(`\${{ ${FULL_BATTERY} && needs.f.outputs.a != 'false' }}`) === null,
56725675 );
5676+ // #24073: stripped is not FORGOTTEN. The reading returns the event fact beside
5677+ // the refs it shares with `jobFilterOutputRefs`, and only when it stripped it.
5678+ const fullBatteryReading = jobIfReading(`\${{ !cancelled() && needs.filter.outputs.console != 'false' && ${FULL_BATTERY} }}`);
5679+ t(
5680+ 'jobIfReading: the stripped full-battery clause is RETURNED as fullBatteryOnly, beside the same refs',
5681+ fullBatteryReading?.fullBatteryOnly === true && fullBatteryReading?.refs.map((r) => `${r.job}.${r.output}`).join() === 'filter.console',
5682+ );
5683+ t(
5684+ '...and an if: with no event clause reads fullBatteryOnly FALSE — the fact is read off the text, never defaulted on',
5685+ jobIfReading("${{ !cancelled() && needs.filter.outputs.console != 'false' }}")?.fullBatteryOnly === false,
5686+ );
56735687
56745688 const fixturePops = jobPathPopulations(jobFilterWf, 'fixture.yml');
56755689 t(
@@ -11928,6 +11942,63 @@ export function selfTest({ tier = 'full' } = {}) {
1192811942 outsideBlockCounts(familyReconciliation({ jobFilteredRows: [{}, {}] })).jobFilteredJobs === 2,
1192911943 );
1193011944
11945+ // ── WHEN a path-scheduled job runs, on the row the reader reads (#24073) ──
11946+ //
11947+ // The measured case: a `packages/runtime/src/route-ledger.ts` change derived
11948+ // the Dogfood Regression Gate as "scheduled BY YOUR PATHS", and nothing said
11949+ // its `if:` skips pull_request and merge_group — so the PR never ran the two
11950+ // dogfood pins that read that file, and their first run was `main`'s push
11951+ // battery, red. Pinned LIVE on ci.yml for both dogfood jobs (they carry the
11952+ // same `if:`), against a NEGATIVE control on a job that does run on the PR,
11953+ // and with an in-place ABLATION of the carried flag on every row, so the
11954+ // statement cannot pass as a string typed per job. If ci.yml moves either job
11955+ // back onto the PR, these go red: re-point them to whichever path-scheduled
11956+ // job still carries the clause — ⛔ do not delete them.
11957+ const ledgerCard = 'packages/runtime/src/route-ledger.ts';
11958+ const ledgerJobs = jobFilteredSteps(liveWorkflows, [ledgerCard]);
11959+ const ledgerRow = (prefix) => ledgerJobs.rows.find((r) => r.job.startsWith(prefix)) ?? null;
11960+ const NOT_ON_PR = 'does NOT run on this PR or in the merge queue';
11961+ // The plain rendering of ONE row, with counts that satisfy the renderer's own
11962+ // refusals, so the ablated copy below is rendered by the same function.
11963+ const plainRowLine = (row) => {
11964+ if (!row) return '';
11965+ const n = row.steps.length;
11966+ const lines = jobFilteredStepLines([row], { covering: 1, named: 1, steps: n, accounted: 0, unaccounted: n, conditionalSteps: 0 });
11967+ return lines.find((l) => l.startsWith(` - [${row.workflow} · ${row.job}]`)) ?? '';
11968+ };
11969+ for (const prefix of ['Dogfood Regression Gate', 'Dogfood Verify CLI']) {
11970+ const row = ledgerRow(prefix);
11971+ t(
11972+ `LIVE: a route-ledger.ts change schedules '${prefix}', and its row carries the full-battery fact read off its own if:`,
11973+ row?.fullBatteryOnly === true,
11974+ );
11975+ t(
11976+ '...its --commands line SAYS the job does not run on this PR or in the merge queue, ahead of the step list',
11977+ Boolean(row) && jobFilteredNotMeasuredLine(row).indexOf(NOT_ON_PR) > -1
11978+ && jobFilteredNotMeasuredLine(row).indexOf(NOT_ON_PR) < jobFilteredNotMeasuredLine(row).indexOf(' step(s):'),
11979+ );
11980+ t("...and so does its row in the plain rendering, on the row's own line", plainRowLine(row).includes(NOT_ON_PR));
11981+ // ABLATION, in place: the same row with the carried fact stripped. The
11982+ // statement must vanish from both lines — rendered from the flag, or not at all.
11983+ const stripped = row ? Object.fromEntries(Object.entries(row).filter(([k]) => k !== 'fullBatteryOnly')) : null;
11984+ t(
11985+ '...ABLATED — the carried fact stripped from the row — the statement disappears from BOTH lines',
11986+ Boolean(stripped)
11987+ && fullBatteryOnlyClause(stripped) === ''
11988+ && !jobFilteredNotMeasuredLine(stripped).includes(NOT_ON_PR)
11989+ && plainRowLine(stripped) !== ''
11990+ && !plainRowLine(stripped).includes(NOT_ON_PR),
11991+ );
11992+ }
11993+ const prJobRow = ledgerRow('Build Core');
11994+ t(
11995+ 'LIVE NEGATIVE CONTROL: Build Core, scheduled by the same path and run on the PR, carries the fact as FALSE and no statement',
11996+ prJobRow?.fullBatteryOnly === false
11997+ && !jobFilteredNotMeasuredLine(prJobRow).includes(NOT_ON_PR)
11998+ && plainRowLine(prJobRow) !== ''
11999+ && !plainRowLine(prJobRow).includes(NOT_ON_PR),
12000+ );
12001+
1193112002 // ── Type-check lanes (#19172): the negatives are the live over-matches a SUBSTRING reading produces here ──
1193212003 t('a `tsc --noEmit` or `-p <config>` invocation is a lane',
1193312004 isTypeCheckInvocation('pnpm --filter @objectstack/spec exec tsc --noEmit') && isTypeCheckInvocation('npx tsc -p tsconfig.test.json'));
0 commit comments