Skip to content

Commit 770dd18

Browse files
committed
ci(nightly-tiers): pull_request paths trigger and a path-less population declaration
dispatch-gates refuses a family reachable only from a scheduled workflow, so the nightly carries the patrol posture: a pull_request trigger paths-filtered to its own file and the switch reader. The reader declares no-path-population (its self-test drives a temp fixture) and resolves vitest's CLI entry by walking up from require.resolve('vitest') instead of spelling a subpath the derivation read as a population. Concurrency keyed per ref so a PR run never queues behind main's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
1 parent d2fab7a commit 770dd18

2 files changed

Lines changed: 53 additions & 12 deletions

File tree

.github/workflows/test-nightly-tiers.yml

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,14 @@ name: Nightly Tiers
6262
# regex over the log — the log is presentation, the report is the contract.
6363
#
6464
# ⛔ A run on any ref but `main` NEVER writes to the board — a
65-
# `workflow_dispatch` on a branch proves the transport, the sharding and the
66-
# rendering on a real runner and publishes the would-be card to the run
67-
# summary instead. That is how the card that landed this was smoke-tested.
65+
# `workflow_dispatch` on a branch, or the `pull_request` run below, proves the
66+
# transport, the sharding and the rendering on a real runner and publishes the
67+
# would-be card to the run summary instead. That is how the card that landed
68+
# this was smoke-tested.
6869
#
6970
# ⛔ This job is NOT a required context and must never become one: it runs on
70-
# no pull request at all, so a ruleset naming it would block every PR forever
71-
# on a check that cannot report.
71+
# no pull request but one editing its own two files, so a ruleset naming it
72+
# would block every other PR forever on a check that cannot report.
7273

7374
on:
7475
schedule:
@@ -79,6 +80,23 @@ on:
7980
# (05:00) have started and well before `showcase-smoke` (07:00).
8081
- cron: '29 5 * * *'
8182
workflow_dispatch: {}
83+
# ⛔ PATHS-FILTERED TO THIS FILE AND THE SWITCH READER IT INVOKES, and that
84+
# filter is the whole reason this trigger is allowed to exist: a change to
85+
# the nightly itself is exercised before it merges — the posture every patrol
86+
# in this repo keeps, and the one `scripts/pm/dispatch-gates.mjs` pins
87+
# tree-wide (a family reachable only from a scheduled workflow is refused
88+
# there by name). A trigger that fires only on a pull request editing these
89+
# two files puts ≈ 15 minutes of e2e on no unrelated PR's critical path.
90+
#
91+
# ⛔ Do NOT widen this list toward the tests themselves: a `packages/**`
92+
# entry here would put the two tiers back on the per-PR path, which is the
93+
# thing the card this landed removed. A `pull_request` run never writes to
94+
# the board — its `github.ref` is the merge ref, not `main`, and the board
95+
# write is gated on `main` by name.
96+
pull_request:
97+
paths:
98+
- '.github/workflows/test-nightly-tiers.yml'
99+
- 'scripts/nightly-tiers.mjs'
82100

83101
# Least privilege. The shards read the repo and publish artifacts of their own
84102
# run; the report job writes issues and nothing else — no label on anybody's
@@ -87,10 +105,12 @@ permissions:
87105
contents: read
88106
issues: write
89107

90-
# One nightly at a time. A dispatch overlapping the schedule would have two
91-
# `report` jobs racing the same lookup, and the loser would mint a duplicate.
108+
# One nightly at a time PER REF. A dispatch overlapping the schedule on `main`
109+
# would have two `report` jobs racing the same lookup, and the loser would mint
110+
# a duplicate; a `pull_request` run never writes, so it need not queue behind
111+
# `main`'s run — keying the group by ref keeps exactly the writers serial.
92112
concurrency:
93-
group: test-nightly-tiers
113+
group: test-nightly-tiers-${{ github.ref }}
94114
cancel-in-progress: false
95115

96116
env:

scripts/nightly-tiers.mjs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ import { fileURLToPath } from 'node:url';
9090
import { isEntrypoint } from './invoked-as.mjs';
9191
import { workspacePackageDirs } from './workspace-enumerator.mjs';
9292

93+
// dispatch-gates: no-path-population -- the --self-test (the one invocation a pull request runs, through test-nightly-tiers.yml's paths trigger) drives a temp fixture workspace and reads no tracked file; --packages, --check and --failing-files sweep the live workspace only inside the nightly run itself, which is placed by that workflow's own paths trigger, so a hint here would be a second spelling of one population
94+
9395
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
9496

9597
// ---------------------------------------------------------------------------
@@ -238,15 +240,34 @@ export function judgeCollection(name, tierFiles, queueListed, nightlyListed) {
238240
return problems;
239241
}
240242

241-
/** `vitest list --filesOnly` in `pkgDir` under `mode`, package-relative paths, sorted. */
242-
function vitestListedFiles(pkgDir, mode) {
243+
/**
244+
* The `vitest.mjs` CLI entry of the vitest THIS package resolves: from its main
245+
* entry, walk up to the directory whose manifest is named `vitest`. Resolved
246+
* rather than spelled as a subpath so the module carries no path-shaped
247+
* literal for the dispatch derivation to read as a population declaration.
248+
*/
249+
function vitestCliEntry(pkgDir) {
243250
const require = createRequire(path.join(pkgDir, 'package.json'));
244-
let vitestEntry;
251+
let dir;
245252
try {
246-
vitestEntry = path.resolve(path.dirname(require.resolve('vitest/package.json')), 'vitest.mjs');
253+
dir = path.dirname(require.resolve('vitest'));
247254
} catch {
248255
throw new Error(`${pkgDir}: vitest is not resolvable from this package -- install the workspace before --check`);
249256
}
257+
for (;;) {
258+
const manifest = path.join(dir, 'package.json');
259+
if (existsSync(manifest) && JSON.parse(readFileSync(manifest, 'utf8')).name === 'vitest') {
260+
return path.join(dir, 'vitest.mjs');
261+
}
262+
const parent = path.dirname(dir);
263+
if (parent === dir) throw new Error(`${pkgDir}: walked to the filesystem root without finding vitest's package root`);
264+
dir = parent;
265+
}
266+
}
267+
268+
/** `vitest list --filesOnly` in `pkgDir` under `mode`, package-relative paths, sorted. */
269+
function vitestListedFiles(pkgDir, mode) {
270+
const vitestEntry = vitestCliEntry(pkgDir);
250271
const out = execFileSync(process.execPath, [vitestEntry, 'list', '--filesOnly'], {
251272
cwd: pkgDir,
252273
env: { ...process.env, [TIER_ENV]: mode },

0 commit comments

Comments
 (0)