Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 34 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ on:
# branch-protection-required check MUST carry this trigger, or queue builds
# wait forever on a check that never reports.
merge_group:
# ── THE HOURLY FULL RUN (#16467) ─────────────────────────────────────────
# ── THE SCHEDULED FULL RUN (#16467), every 4 hours since #25771 ──────────
# `push` above now computes the Test Core package set with `--affected`
# against `github.event.before`, so a merge no longer re-tests the whole
# workspace it just tested in the queue. Something still has to run the FULL
Expand All @@ -48,16 +48,22 @@ on:
# This is that run. Minute 0 rather than an offset minute ON PURPOSE, and
# against the sibling convention in test-nightly-tiers.yml: the reading this
# feeds — `scripts/ci/select-shard-timings-run.mjs` — wants one complete run
# per hour far more than it wants a short queue wait, and a run that starts
# a few minutes late is still the hour's run. What it must NOT do is collide
# with the next hour's, which the concurrency group below decides.
# per window far more than it wants a short queue wait, and a run that starts
# a few minutes late is still that window's run. What it must NOT do is
# collide with the next one, which the concurrency group below decides.
#
# EVERY 4 HOURS, not hourly (#25771, maintainer ruling Q6 = C): the job logic
# is unchanged — the run is still the full battery and still finds flakes on
# an unchanged SHA — at six runs a day instead of twenty-four, most of which
# re-tested a SHA an earlier run had already tested. Every runner-minute it
# spends competes with PR and merge-queue capacity.
#
# ⛔ This is a NEW TRIGGER KEY, not a widening of `push:`. The ⛔ above still
# binds: `push:` stays filtered to `main`.
schedule:
- cron: '0 * * * *'
- cron: '0 */4 * * *'
# The same full battery, on demand: an operator who has just landed a fix for
# a red hourly run should not have to wait up to an hour to see it answered.
# a red scheduled run should not have to wait up to 4 hours to see it answered.
# Treated as `full` by the selection script for the same reason `schedule` is.
workflow_dispatch:

Expand All @@ -68,7 +74,7 @@ on:
# run that tests ITS OWN merge: Test Core computes `--affected` against
# `github.event.before` (see `on.schedule` above), so a push run that does not
# finish leaves that merge's diff with no Test Core verdict on `main` until the
# next hourly run. Main merges land every ~9–16 min and a push run takes ~27,
# next scheduled full run. Main merges land every ~9–16 min and a push run takes ~27,
# so under the old shared key `ci-CI-push-refs/heads/main` the next merge
# cancelled it: 31 of 40 push runs on 2026-10-23 01:03Z–11:03Z, most of them
# inside "Run this shard's tests". (The Turbo seed is NOT what was lost: the
Expand All @@ -86,7 +92,7 @@ on:
# ⭐ `github.event_name` IS LOAD-BEARING TOO (#16467). A `schedule` and a
# `workflow_dispatch` run on `main` both fall through to `refs/heads/main`;
# without the event name they share one group and an on-demand run cancels the
# hourly one (and, under any key that drops `github.event.after`, the next
# scheduled one (and, under any key that drops `github.event.after`, the next
# merge cancels both, which is how 36 of 60 hourly-era push runs died).
#
# ⛔ Do not "simplify" either segment away. `scripts/ci/scheduled-full-run.mjs
Expand All @@ -98,8 +104,8 @@ on:
# those events too).
#
# `cancel-in-progress` stays `true`: with a per-commit push group it never
# fires on a push, and for everything else it is the policy. Two hourly runs
# overlapping means one of them is over an hour old, and an hour-old
# fires on a push, and for everything else it is the policy. Two scheduled
# runs overlapping means one of them is over 4 hours old, and a 4-hour-old
# measurement of `main` is strictly the worse of the two. Test Core's wall clock
# is well inside the cadence (~39 shard-minutes across the shards), so this is
# the exceptional path, not the normal one.
Expand All @@ -109,7 +115,7 @@ concurrency:

# ── THE TWO SPEEDS (#22998) ──────────────────────────────────────────────────
# A `pull_request` or `merge_group` run carries the FAST SET: `Build Core`,
# `Test Core` (affected-only) and `Build Docs`. `main`'s hourly `schedule` run,
# `Test Core` (affected-only) and `Build Docs`. `main`'s 4-hourly `schedule` run,
# its `push` runs and an on-demand `workflow_dispatch` carry the FULL BATTERY:
# the fast set plus the three jobs below that spell
#
Expand Down Expand Up @@ -243,7 +249,7 @@ jobs:
# That failure direction is the dangerous one: every filter output would
# come back `false`, `|| 'true'` supplies nothing (the output exists and
# says `false`), and every downstream `!= 'false'` guard SKIPS — so the
# hourly full run would have been an entirely green, entirely empty run
# scheduled full run would have been an entirely green, entirely empty run
# of nothing at all. An allow-list makes each new event fail toward
# THE FILTER CONTRACT's half 1 instead: the step is skipped, its outputs
# are the empty string, `|| 'true'` fills them in, and everything runs.
Expand Down Expand Up @@ -628,7 +634,7 @@ jobs:
# (p + s + 10m) and the cap-deferred path (p + s + 20m) -- land before the
# new value on every observation, AND the slowest `Test Core (N/8)` job
# wall is at or under 80% of it. Read the pull_request and merge_group
# runs, not the hourly schedule alone: scheduled runs mostly replay the
# runs, not the 4-hourly schedule alone: scheduled runs mostly replay the
# cache their commit's push run saved. It carries no other expiry — a
# raise with no revert condition beside it becomes permanent by
# forgetting — and the stall guard stays the primary hang detector.
Expand Down Expand Up @@ -772,7 +778,7 @@ jobs:
# `main` was on before this merge landed (#16467). A zero
# sha -- a first push, or a force-push that rewrote
# history -- falls back to the FULL list, loudly.
# schedule the FULL list: the hourly run IS the full battery, and
# schedule the FULL list: the 4-hourly run IS the full battery, and
# workflow_ the on-demand rerun of it. Neither carries a diff base,
# dispatch and neither should: they are the run that says whether
# the whole workspace is green on `main`.
Expand Down Expand Up @@ -1266,6 +1272,11 @@ jobs:
# attestation pair below to "match" this step — that pair is the
# credential, not diagnostics, and check:shard-attestation pins its
# unguarded shape statically.
# Retention: 4 days on a `schedule` run, 1 day otherwise (#25771). The
# weekly shard-timings refresh accumulates the scheduled runs whose
# summaries are still downloadable; hourly, one day held 24 of them, and
# at every 4 hours it takes four days to hold the same 24. Every other
# event's summary is read only inside its own run, so it stays at 1 day.
- name: Publish this shard's turbo run summary
if: always()
continue-on-error: true
Expand All @@ -1274,7 +1285,7 @@ jobs:
name: test-core-run-summary-${{ matrix.shard }}-of-8
path: .turbo/runs/
if-no-files-found: ignore
retention-days: 1
retention-days: ${{ github.event_name == 'schedule' && 4 || 1 }}

# ── Predicted vs measured: this shard's timing drift (#16173, #16465) ──
#
Expand Down Expand Up @@ -1490,10 +1501,11 @@ jobs:
--shard "${{ matrix.shard }}/8" \
--out "$RUNNER_TEMP/test-timings/shard-${{ matrix.shard }}.json"

# 1 day, matching the run-summary upload above: this is an intermediate
# the aggregator consumes minutes later in the SAME run. The 14-day
# retention this card asks for is on the MERGED table, published by
# `Test Core`.
# Retention matches the run-summary upload above (4 days on `schedule`,
# 1 day otherwise, #25771): the aggregator consumes this minutes later in
# the SAME run, and the shard-timings refresh reads the scheduled runs'
# captures beside their summaries. The 14-day retention this card asks
# for is on the MERGED table, published by `Test Core`.
- name: Publish this shard's test timings
if: always()
continue-on-error: true
Expand All @@ -1502,7 +1514,7 @@ jobs:
name: test-core-timings-${{ matrix.shard }}-of-8
path: ${{ runner.temp }}/test-timings/
if-no-files-found: ignore
retention-days: 1
retention-days: ${{ github.event_name == 'schedule' && 4 || 1 }}

# A stall's full diagnostic reports (JS stacks, libuv handles, heap
# summary per process) outlive the in-log digest — keep them so a #4250
Expand Down Expand Up @@ -1836,7 +1848,7 @@ jobs:
# driver-memory, driver-mongodb, service-analytics) — the other backends the
# temporal conformance matrix holds to one standard. Its NAME still says
# "live PG + MySQL" on purpose: it was a required check until #22998 moved
# the job to the full battery, and its readers (the hourly run's red card,
# the job to the full battery, and its readers (the scheduled run's red card,
# merge-queue triage history, every instruction that names it) find it by
# that literal — renaming it is a contract change, not a tidy-up.
temporal-conformance:
Expand Down Expand Up @@ -2733,7 +2745,7 @@ jobs:
# too (`no caches are enabled`) and re-executes everything.
#
# Credentials go only to runs whose code has landed or is landing:
# `merge_group`, and any run on refs/heads/main (push, the hourly
# `merge_group`, and any run on refs/heads/main (push, the 4-hourly
# schedule, workflow_dispatch on main). A `pull_request` run, and a
# dispatch on any other branch, receives neither TURBO_TOKEN nor the
# signing key, so code a PR adds can neither upload to the remote nor
Expand Down
28 changes: 15 additions & 13 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
name: CodeQL Security Analysis

# Deliberately NOT on pull_request: the analysis took ~4½ runner-minutes per
# PR sync, and everything a PR introduces is analyzed on main within minutes
# of the merge anyway (multi-agent merge cadence). Alerts surface on main's
# runs + the weekly deep pass instead of blocking/occupying PR runners.
# If branch protection ever required the "Analyze (javascript)" check, that
# requirement must be dropped along with this trigger.
# PR sync. If branch protection ever required the "Analyze (javascript)" check,
# that requirement must be dropped along with this trigger.
#
# DAILY, not on every push to main (#25771, maintainer ruling Q6 = C). The push
# trigger ran 100 analyses in 25.9 h, 86 of them cancelled by the next merge,
# and each one competed with PR and merge-queue runners. One daily run of
# `main` analyzes everything that landed the day before; the Monday deep pass
# it replaces ran the same query suite, so it folds into the daily one.
# `workflow_dispatch` re-analyzes `main` on demand, e.g. after landing a fix.
on:
push:
branches:
- main
schedule:
# Run at 02:00 UTC every Monday
- cron: '0 2 * * 1'
# 02:00 UTC every day
- cron: '0 2 * * *'
workflow_dispatch:

# A superseded analysis of an outdated commit has no value — cancel it. A newer
# main push cancels only an in-flight main analysis (the newer commit still
# gets analyzed).
# A superseded analysis of an outdated commit has no value — cancel it. An
# on-demand run cancels an in-flight analysis of the same ref (the newer
# `main` head still gets analyzed).
concurrency:
group: codeql-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/docs-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ on:
# snapshot owns :07 of 02, 08, 14 and 20. Of the six six-hour sets, only
# 0/6/12/18 and 1/7/13/19 have hours with nothing scheduled after :00; the
# first is taken because its :00 neighbours are short (release.yml's
# version-pr refresh, the hourly ci/lint runs), while 07:00 starts the
# showcase smoke. The MINUTE is off the top of the hour for the reason every
# version-pr refresh, the ci/lint full runs), while 07:00 starts the
# showcase smoke. Since #25771 those ci/lint runs are 4-hourly
# (00/04/08/12/16/20 :00), so they now meet this set only at 00 and 12 and
# 06/18 lose that neighbour; the choice still holds and nothing moved. The MINUTE is off the top of the hour for the reason every
# patrol here gives: scheduled runs queue behind everyone else's :00 cron.
#
# This job spends almost nothing of the per-repository GITHUB_TOKEN budget
Expand Down
26 changes: 14 additions & 12 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ on:
# queue builds or the queue stalls. This workflow has no PR-only steps, so
# the trigger alone is enough.
merge_group:
# ── THE HOURLY FULL RUN (#16467), the half this file owns ────────────────
# The card's ruling is that the hourly run covers BOTH required-context
# ── THE SCHEDULED FULL RUN (#16467), the half this file owns ─────────────
# Every 4 hours since #25771 (maintainer ruling Q6 = C), the same cadence as
# ci.yml's half and for the reason stated there; no step changed.
# The card's ruling is that the scheduled run covers BOTH required-context
# files, not just ci.yml, and the reason is measured rather than symmetric:
# the push-on-`main` run of THIS workflow is the only post-merge full-battery
# run of the families `scripts/ci/select-gate-families.sh` scopes away on
Expand All @@ -44,7 +46,7 @@ on:
#
# ⛔ A NEW TRIGGER KEY, not a widening of `push:`; see ci.yml's `on:` block.
schedule:
- cron: '0 * * * *'
- cron: '0 */4 * * *'
workflow_dispatch:

# ── MEASURED 2026-08-25 (#12211) — a NEGATIVE result, recorded so it is not
Expand Down Expand Up @@ -95,7 +97,7 @@ on:
#
# ⭐ `github.event_name` is in the key for the reason ci.yml's concurrency block
# states in full (#16467): `schedule` and `workflow_dispatch` both fall through
# to `refs/heads/main`, so without it an on-demand run cancels the hourly one.
# to `refs/heads/main`, so without it an on-demand run cancels the scheduled one.
# `scripts/ci/scheduled-full-run.mjs --check-concurrency` evaluates THIS
# expression too and reds if two pushes, or a push and a schedule run, share a
# group, or if two schedule, dispatch or same-PR runs stop sharing one.
Expand Down Expand Up @@ -3514,8 +3516,8 @@ jobs:
run: pnpm check:select-shard-packages

# Rationale: `scripts/ci/scheduled-full-run.mjs` header (#16467).
- name: Hourly full run self-test and concurrency gate
id: hourly-full-run-self-test-and-concurrency
- name: Scheduled full run self-test and concurrency gate
id: scheduled-full-run-self-test-and-concurrency
continue-on-error: true
run: |
node scripts/ci/scheduled-full-run.mjs --self-test
Expand Down Expand Up @@ -4426,7 +4428,7 @@ jobs:
# only a name that DISAPPEARS can turn a live declaration dead, which is
# a structural change the selector already runs everything for. A group
# confined to product source, tests, docs or changesets skips it here and
# pays for it on `push` to main and on the hourly run.
# pays for it on `push` to main and on the 4-hourly run.
- name: A declared gate population reaches the tree
id: a-declared-gate-population-reaches-the-tree
continue-on-error: true
Expand Down Expand Up @@ -4542,7 +4544,7 @@ jobs:
# ROOT_DIR_WATCH_HINTS declaration, held against the root it really walks
# by its own self-test — so a group that changes no file under scripts/
# (and no root configuration) skips it. The selection step at the top of
# this job decides that, `push` on main and the hourly run keep it
# this job decides that, `push` on main and the 4-hourly run keep it
# unconditional, and the ruling that authorizes moving a tooling
# self-test off the PR path is quoted in the selector's header.
- name: scripts/ entry guards go through one predicate
Expand Down Expand Up @@ -4693,7 +4695,7 @@ jobs:
# anyway, and the trade is explicit rather than reasoned away: it imports
# the same derivation as the dispatch-gates and watch-hint literal steps, so its inputs are the
# workflow tree and the gate sources, and on a group touching neither the
# gap does move — to `push` on main and the hourly full run, which keep
# gap does move — to `push` on main and the 4-hourly full run, which keep
# the whole battery. Gate weakening is a maintainer floor; the sentence
# that authorizes this one is quoted in the selector's header.
- name: PM bare-root worklist self-test
Expand Down Expand Up @@ -4747,7 +4749,7 @@ jobs:
# `scripts/**/*.mjs`, `scripts/**/*.mts` and `scripts/**/*.sh`, and the
# rest of its read-set is the workflow tree and `.github/actions` it
# discovers the runnable self-tests from — so a group touching none of
# those skips it here and runs it on `push` to main and on the hourly
# those skips it here and runs it on `push` to main and on the 4-hourly
# full run.
- name: Self-test workflow-command gate
id: self-test-workflow-command-gate
Expand Down Expand Up @@ -4913,13 +4915,13 @@ jobs:
# three-file `packages/spec` diff, and ruling #208 on #19491 (R4) took
# those whole-tree reads off the PR path on the maintainer's sentence,
# quoted in the selector's header. ⛔ A defect in that wider set can now
# first appear on `main`; the push-on-main and hourly full runs are what
# first appear on `main`; the push-on-main and 4-hourly full runs are what
# bound it, and widening the skip further is again a maintainer call.
#
# ⚠️ Since #22991 the battery is TIERED by event, inside the gate rather
# than here: on `pull_request` and `merge_group` `pnpm check:pm-dispatch-gates`
# runs the fast tier; on `schedule`, `push` and any other event it runs
# both. Every case still runs at least hourly on the full run, and a
# both. Every case still runs at least every 4 hours on the full run, and a
# slow-tier red is a red on `main`. The step below is unchanged; the
# decision and its argument live in `scripts/pm/check-dispatch-gates.mjs`.
#
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1507,7 +1507,7 @@ jobs:
full-battery:
name: Full battery green at the version commit
# A PR or queue build carries ci.yml's FAST SET since #22998; the dogfood
# and temporal legs run only on main's push, hourly and
# and temporal legs run only on main's push, 4-hourly schedule and
# dispatch runs. So "tested before a release" is read HERE, as a gate: the
# newest completed `schedule` or `push` runs of `CI` and `Lint & Type
# Check` whose head is the version commit or a first-parent ancestor of it
Expand Down
Loading
Loading