Repository navigation
Commit a26776d
ci: lint.yml's scheduled run skips the battery when a green push run already covers the sha (3 of 3) (#27085)
Fixes #26071
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6416984681
## 维护者速读
- 改了什么:`lint.yml` 每 4 小时的定时运行新增第一个 job `schedule-gate`:同一 commit
上已有跑完且全绿的 push 运行时,其余 14 个 job(12 条 lane 和 2
个汇总)全部跳过;PR、合并队列、push、手动触发的行为不变。
- 为什么改:这是裁决 A 的第 (i) 步((ii)(iii) 已在 #26512、#26570 落地)。定时运行测的是 main 的
head,而这个 commit 的 push 运行已经跑过同一套门禁。实测最近 13 次定时运行:push
运行最后都是绿的,但定时运行启动时已经跑完的只有 4 次。所以按裁决的写法,大约 4/13 会跳过,其余照常全量跑。
- 风险与回滚:门禁 job 一律偏向「照跑」:API 报错、403、读不懂返回、push 还在跑,都按「不跳过」处理;门禁 job
本身崩溃,各 lane 也照常运行。新增的风险面是 `check-required-contexts.mjs`
的一条接线断言,现在多接受一种精确拼写。回滚:revert 本 PR。
- 先看:`.github/workflows/lint.yml` 里的 `schedule-gate` job 和两个汇总
job(`lint`、`typecheck`)的 `if:`;`scripts/check-required-contexts.mjs` 的
`pinLanesCarryNoArrangeableIf`。
- 两分钟验证:`pnpm check:required-contexts && pnpm
check:workflow-status-functions && pnpm check:pm-expected-skips`,应看到三组
✓。合并后在 Actions 里看下一次 `Lint & Type Check` 定时运行:要么只有 `Schedule gate` 一个
job 在跑、其余显示 skipped,要么(push 还没跑完时)全量运行。
- 需要你做的:这是 Tier H(改 `.github/workflows/**`)。请审阅并 Approve,之后由席位落地。
## What changes
Step (i) of ruling 6416984681 (letter A), the last of the card's three
serial PRs. Steps (ii) and (iii) landed as #26512 (`3fcafcd3`) and
#26570 (`9bfd6a62`).
- **New first job `schedule-gate`** (`Schedule gate · skip when a green
push run covers the commit`). Job-level `if: github.event_name ==
'schedule'`, `permissions: actions: read` only, no checkout,
`timeout-minutes: 10` (the file's job-wall floor). It runs one step, `gh
api
repos/REPO/actions/workflows/lint.yml/runs?event=push&branch=main&head_sha=SHA&status=completed&per_page=10`,
and keeps only runs with `event == push`, `status == completed`,
`conclusion == success` and `head_sha == github.sha`. It sets
`skip=true` only on a positive match. Every other outcome sets
`skip=false`: no run, a red run, a run on another sha, a failed read
(403 included), or a body that does not parse. The step has no `set -e`,
so a read failure cannot fail the job. If the job crashes anyway, its
output reads empty and the lanes still run.
- **The 12 lanes** each gain the same two lines, `needs:
[schedule-gate]` and `if: ${{ !cancelled() &&
needs.schedule-gate.outputs.skip != 'true' }}`. On any event other than
`schedule` the gate job is skipped and its output is the empty string.
`!cancelled()` runs a job past a skipped or failed need, so each lane
runs as before. This is ci.yml's existing `!cancelled() &&
needs.filter.outputs.*` idiom. No lane body changed: a yaml-parsed
comparison against `HEAD` finds every job identical apart from
`needs`/`if`.
- **The two required aggregates** (`Lint & Repo Gates`, `TypeScript Type
Check`) keep `always()`. Each gains one schedule-only conjunct: `${{
always() && !(github.event_name == 'schedule' &&
contains(needs.*.result, 'skipped')) }}`. On any other event that
conjunct is true, so `always()` decides exactly as before, and a skipped
required context can never appear on a PR or a queue build. Their
`needs:` and rosters are unchanged, so `check:aggregator-roster` and
their run-time roster verdicts are unaffected.
- **`on:` comment block** now describes the skip, how it fails closed,
and why ci.yml's schedule is not gated (there `push` tests the affected
set only). The `schedule:` cron, the other triggers and the
`concurrency:` key are byte-identical.
- **`scripts/check-required-contexts.mjs`**: the wiring assertion "the
lane hosting the required-context pin must carry NO job-level `if:`"
went red, because the pin lane (`lint-ci-engine`) now carries the skip.
It now admits exactly one shape and still refuses every other `if:`.
That shape is the lane's byte-exact skip `if:`, plus `needs:` on
`schedule-gate`, plus a `schedule-gate` job whose `if:` is byte-exactly
`github.event_name == 'schedule'`. There are six new self-test cases in
both directions, and the battery floor rises from 13 to 23, its measured
count (17 before, plus the 6). `REQUIRED_CONTEXTS` is untouched.
Job-by-job, before → after. Before this PR every job ran on every event.
| job | push | pull_request | merge_group | workflow_dispatch |
schedule, green push run already completed on the sha | schedule, none
(or still running, red, unreadable) |
|:--|:--|:--|:--|:--|:--|:--|
| `schedule-gate` (new) | skipped | skipped | skipped | skipped | runs,
`skip=true` | runs, `skip=false` |
| 12 lanes (`Lint · *`, `Type Check · *`) | runs → runs | runs → runs |
runs → runs | runs → runs | runs → **skipped** | runs → runs |
| `Lint & Repo Gates` (required) | runs → runs | runs → runs | runs →
runs | runs → runs | runs → **skipped** | runs → runs |
| `TypeScript Type Check` (required) | runs → runs | runs → runs | runs
→ runs | runs → runs | runs → **skipped** | runs → runs |
## Tests
All local readings are on head `8fd56eab` (the final commit, after
merging `origin/main` `5cba8fc2`).
<details><summary>Derived gate union: 61 of 61 exit 0</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths; 2 paths vs merge base
`5cba8fc2f`, 186 changed lines) derived 61 commands. All 61 ran with
exit codes captured before any pipe, all 0. Among them:
`check:required-contexts`, `check:workflow-status-functions` (58
job-level `if:`, 29 read `needs.*.outputs.*`, all naming a status
function), `check:pm-expected-skips` (206 self-test cases),
`check:aggregator-roster`, `check:ci-filter-parity`,
`check:shard-attestation`, `check:select-gate-families`, `node
scripts/ci/scheduled-full-run.mjs --self-test`, `check:nul-bytes`, and
`check:pm-dispatch-gates` (fast tier, 1935 cases). `node
scripts/pm/dispatch-gates.mjs --ran FILE --repo
objectstack-ai/objectstack`: `61 derived famil(ies) accounted for — 61
run, 0 NOT-MEASURED (a DERIVED zero — all 61 recorded an exit code and
none of them is 3)`.
The first derivation, on the lint.yml-only commit, named 42 families. 41
were green and `check:required-contexts` was red with `wiring: the lane
hosting the required-context pin must carry NO job-level if:`, which led
to the gate change above.
</details>
<details><summary>Reverse verification on the live file (committed
first, trap-restored)</summary>
Each mutation was made through `node scripts/ablation-replace.mjs`
(anchor hit 1 → 0, blob changed), run against `node
scripts/check-required-contexts.mjs --self-test`, and restored with `git
checkout HEAD -- ABSOLUTE_PATH` under an `EXIT INT TERM` trap.
- Leg A: the `schedule-gate` job's `if:` widened to `github.event_name
!= 'merge_group'` (the gate could now say skip on a PR): exit 1,
`wiring: the lane hosting the required-context pin must carry NO
job-level if: other than the scheduled-run skip`.
- Leg B: the pin lane's `if:` respelled
`needs.schedule-gate.outputs.skip == 'false'`: exit 1, same finding.
- Leg C: the battery floor raised past its count: `registered 23
case(s), below its pinned floor of 999`. That shows the six new cases
register, and the floor is now 23.
- Restore proof: `git hash-object` of both files equals the `HEAD` blob,
`git diff HEAD` and `git status --porcelain` are empty, and the
self-test exits 0 again.
</details>
<details><summary>The gate step's behaviour, offline and live</summary>
- Offline: the step's `run:` body was extracted from the parsed YAML and
driven with a fake `gh` that applies the real `--jq` filter through
`jq`. Results: green push run on the sha → `skip=true` (it picks the
success run past a cancelled one on the same sha); red → `false`;
success on another sha → `false`; empty listing → `false`; HTTP 403 →
`false` (`the runs API did not answer (… HTTP 403)`); non-JSON body →
`false`. The step exits 0 in all six cases.
- Live: the step's exact query, read against the 3 newest scheduled
runs' shas, returns `total_count=1` with one green push run each; an
all-zero sha returns `total_count 0`.
- YAML: `yaml` parse with no errors; `on` keys, `on.schedule` and
`concurrency` are identical to `HEAD`. `actionlint`: NOT MEASURED,
because it is not on PATH in this container.
- `ui/scripts/__tests__/merge-queue-reporting.test.ts`, the one test
file naming the edited gate script (in prose), ran under the verify
lock: `Test Files 1 passed (1)`, `Tests 4 passed (4)`, `VERDICT
command-exit 0`.
- No package source is touched, so no package build, test or typecheck
is owed. `test:repo`, `pnpm lint` and whole-tree scans are CI's. No
dogfood run: there is no `!` in the title and no `(narrowing)` arm.
</details>
## Acceptance notes
- **Premise P1, corrected by measurement.** The card measured that each
of the 12 newest scheduled runs repeated a commit whose push run ended
`success`, and that still holds for all 13 newest. But the ruled
mechanism skips only on a **completed** green push run, checked when the
scheduled run starts, and that holds for 4 of 13: `f708cf22`,
`6390fe52`, `f143cd88`, `05f7758f`. In the other 9, the push run of the
same commit finished 8 to 18 minutes after the scheduled run started
(each push run's last job `completed_at` vs the schedule's
`run_started_at`, 2026-10-26T06:25Z → 2026-10-28T04:14Z). A scheduled
run starts late past its cron slot and `main` merges many times a day,
so the head it tests is often a commit whose push run is still in
flight. Those 9 run the full battery exactly as today; nothing is lost,
only less is saved. Waiting for an in-progress push run would have
skipped all 13, but that is a runner-spend shape the ruling did not
name, so it is raised to the seat rather than built here.
- **What a skipped run reports.** The gate job succeeds and every other
job is skipped, so the run's conclusion is expected to be `success`. The
first post-merge scheduled run is the measurement.
`scheduled-full-run.mjs --release-gate` reads the newest
`schedule`/`push` run on the nearest commit. On a skipped commit that is
either this run or the green push run of the same sha, so both answer
`success`, as the battery did. `scheduled-full-run-card.yml` files only
on `failure`/`timed_out` and is unaffected.
- **Live-state reads.** Gates in `lint-ci-engine` that read GitHub state
(`issues: read`, `pull-requests: read`) are not re-read at schedule time
on a skipped run. The next merge's push run re-reads them.
- **Scope.** The edit to `scripts/check-required-contexts.mjs` sits
outside the claim's literal file surface ("a gate that reads that leg by
name") but inside the dispatch's instruction to re-point what asserts on
the changed shape. It touches only the wiring predicate and its
self-test; `REQUIRED_CONTEXTS` is unchanged. No other gate read the
shape: the 61-family union is green.
- The file's ⭐ WHERE A GATE LIVES block still says every job runs on
every `main` run. The scheduled skip is the one exception, stated in the
`schedule-gate` header. The block itself was left unedited, per the
scope fence.
- Region-disjoint from open draft #27067 (typecheck-workspace hunks near
the turbo summarize lines): a local `git merge-tree --write-tree` of
this head with `b782ee2d` exits 0 and writes a tree. `lint.yml` is not
routed to the custom merge driver.
## Verification
- Tier H (`.github/workflows/**`): the PR stays draft until an
authorized approval. The seat lands it; nothing here readies, arms or
enqueues it.
- CI on this PR runs the whole `lint.yml` (no paths filter). On
`pull_request` the new `Schedule gate` check-run reads `skipped`, which
`check-expected-skips` admits as `non-pr-event schedule; derived`. Every
lane and both aggregates run as before.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent dc87dfb commit a26776d
2 files changed
Lines changed: 177 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
31 | 49 | | |
32 | 50 | | |
33 | 51 | | |
| |||
42 | 60 | | |
43 | 61 | | |
44 | 62 | | |
45 | | - | |
| 63 | + | |
| 64 | + | |
46 | 65 | | |
47 | 66 | | |
48 | 67 | | |
| |||
197 | 216 | | |
198 | 217 | | |
199 | 218 | | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
200 | 274 | | |
201 | 275 | | |
202 | 276 | | |
| |||
300 | 374 | | |
301 | 375 | | |
302 | 376 | | |
303 | | - | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
304 | 383 | | |
305 | 384 | | |
306 | 385 | | |
| |||
429 | 508 | | |
430 | 509 | | |
431 | 510 | | |
| 511 | + | |
| 512 | + | |
432 | 513 | | |
433 | 514 | | |
434 | 515 | | |
| |||
511 | 592 | | |
512 | 593 | | |
513 | 594 | | |
| 595 | + | |
| 596 | + | |
514 | 597 | | |
515 | 598 | | |
516 | 599 | | |
| |||
1934 | 2017 | | |
1935 | 2018 | | |
1936 | 2019 | | |
| 2020 | + | |
| 2021 | + | |
1937 | 2022 | | |
1938 | 2023 | | |
1939 | 2024 | | |
| |||
2953 | 3038 | | |
2954 | 3039 | | |
2955 | 3040 | | |
| 3041 | + | |
| 3042 | + | |
2956 | 3043 | | |
2957 | 3044 | | |
2958 | 3045 | | |
| |||
4090 | 4177 | | |
4091 | 4178 | | |
4092 | 4179 | | |
| 4180 | + | |
| 4181 | + | |
4093 | 4182 | | |
4094 | 4183 | | |
4095 | 4184 | | |
| |||
4246 | 4335 | | |
4247 | 4336 | | |
4248 | 4337 | | |
| 4338 | + | |
| 4339 | + | |
4249 | 4340 | | |
4250 | 4341 | | |
4251 | 4342 | | |
| |||
4390 | 4481 | | |
4391 | 4482 | | |
4392 | 4483 | | |
| 4484 | + | |
| 4485 | + | |
4393 | 4486 | | |
4394 | 4487 | | |
4395 | 4488 | | |
| |||
4576 | 4669 | | |
4577 | 4670 | | |
4578 | 4671 | | |
| 4672 | + | |
| 4673 | + | |
4579 | 4674 | | |
4580 | 4675 | | |
4581 | 4676 | | |
| |||
4722 | 4817 | | |
4723 | 4818 | | |
4724 | 4819 | | |
| 4820 | + | |
| 4821 | + | |
4725 | 4822 | | |
4726 | 4823 | | |
4727 | 4824 | | |
| |||
5411 | 5508 | | |
5412 | 5509 | | |
5413 | 5510 | | |
| 5511 | + | |
| 5512 | + | |
5414 | 5513 | | |
5415 | 5514 | | |
5416 | 5515 | | |
| |||
5656 | 5755 | | |
5657 | 5756 | | |
5658 | 5757 | | |
| 5758 | + | |
| 5759 | + | |
5659 | 5760 | | |
5660 | 5761 | | |
5661 | 5762 | | |
| |||
5858 | 5959 | | |
5859 | 5960 | | |
5860 | 5961 | | |
| 5962 | + | |
| 5963 | + | |
5861 | 5964 | | |
5862 | 5965 | | |
5863 | 5966 | | |
| |||
6387 | 6490 | | |
6388 | 6491 | | |
6389 | 6492 | | |
6390 | | - | |
6391 | | - | |
| 6493 | + | |
| 6494 | + | |
| 6495 | + | |
6392 | 6496 | | |
6393 | 6497 | | |
6394 | 6498 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
235 | 235 | | |
236 | 236 | | |
237 | 237 | | |
238 | | - | |
| 238 | + | |
239 | 239 | | |
240 | 240 | | |
241 | 241 | | |
| |||
991 | 991 | | |
992 | 992 | | |
993 | 993 | | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
994 | 1024 | | |
995 | 1025 | | |
996 | 1026 | | |
| |||
3277 | 3307 | | |
3278 | 3308 | | |
3279 | 3309 | | |
| 3310 | + | |
| 3311 | + | |
3280 | 3312 | | |
3281 | | - | |
3282 | | - | |
| 3313 | + | |
| 3314 | + | |
3283 | 3315 | | |
3284 | 3316 | | |
3285 | 3317 | | |
| |||
3358 | 3390 | | |
3359 | 3391 | | |
3360 | 3392 | | |
3361 | | - | |
| 3393 | + | |
3362 | 3394 | | |
| 3395 | + | |
3363 | 3396 | | |
3364 | 3397 | | |
3365 | 3398 | | |
| |||
3370 | 3403 | | |
3371 | 3404 | | |
3372 | 3405 | | |
| 3406 | + | |
3373 | 3407 | | |
3374 | 3408 | | |
3375 | 3409 | | |
| |||
3401 | 3435 | | |
3402 | 3436 | | |
3403 | 3437 | | |
| 3438 | + | |
| 3439 | + | |
| 3440 | + | |
| 3441 | + | |
| 3442 | + | |
| 3443 | + | |
| 3444 | + | |
| 3445 | + | |
| 3446 | + | |
| 3447 | + | |
| 3448 | + | |
| 3449 | + | |
| 3450 | + | |
| 3451 | + | |
| 3452 | + | |
| 3453 | + | |
| 3454 | + | |
| 3455 | + | |
| 3456 | + | |
| 3457 | + | |
| 3458 | + | |
| 3459 | + | |
| 3460 | + | |
| 3461 | + | |
| 3462 | + | |
| 3463 | + | |
| 3464 | + | |
| 3465 | + | |
| 3466 | + | |
| 3467 | + | |
3404 | 3468 | | |
3405 | 3469 | | |
3406 | 3470 | | |
| |||
0 commit comments