Repository navigation
Commit 7aa82a3
ci(gates): every Lint and Type Check gate runs after a red and each lane ends in one verdict step; Test Core continues past a red package (#23212)
Fixes #23191
Clause-②: no
**Governed surface, Tier H** (`.github/workflows/**`, and `AGENTS.md`):
this PR stays a draft until the maintainer approves it. No seat readies,
queues or arms it.
## What the ruling binds (verbatim, #23177 ruling B, comment 6139629628,
item 1)
> **A1, as the card wrote it, in this form:** every gate step of a `Lint
· *` lane and of `Type Check · *` keeps running after a red
(`continue-on-error: true` on the gate steps only; setup, install and
build steps that later steps depend on stay fail-fast), and each lane
ends with one verdict step that reads every gate's conclusion, prints
the names of the red ones and exits 1 — the job's conclusion is still
red, never silent. Test Core runs `turbo run test …
--continue=dependencies-successful` so one package's red does not stop
the others; its verdict stays red. The #13690 ordering text (`lint.yml`
151–159) and the "⛔ not continue-on-error" note (502–503) are rewritten
in the same PR to say what now holds: the order no longer shortens a red
run, the verdict step is what keeps the red. Governed surface: draft
until the maintainer approves.
## What changes
### `.github/workflows/lint.yml`
- **240 gate steps** across the 8 `Lint · *` lanes and the 4 `Type Check
· *` lanes now carry `id:` and `continue-on-error: true` (eslint 2, repo
& tooling 60, docs & code 55, CI/release/engine 58, scoped erasure 2,
scoped slot/corpus 3, scoped tooling 3, PM dispatch-gates 1, source
gates 33, workspace 2, debt ledger 2, consumer gates 19). Nothing else
about any of them moved: base and head were parsed and every step
compared with those two keys removed, and all 240 are identical (same
`run:`, `if:`, `env:`, order and comment).
- **Fail-fast, untouched:** checkout, Node and pnpm setup, the store
path, the pnpm cache, the Turbo cache restore, the install, the
gate-family selector (`id: gate-families`), the three workspace builds,
and `Save Turbo cache (main only)`.
- **Every lane ends with `Lane verdict`**: `if: ${{ !cancelled() }}`,
env `OS_LANE_STEPS: ${{ toJSON(steps) }}` and `OS_LANE_JOB_STATUS: ${{
job.status }}`, `run: node scripts/ci/lane-verdict.mjs`. The two
aggregates (`Lint & Repo Gates`, `TypeScript Type Check`) are unchanged:
they read each lane's `result`, and a lane with a red gate still
concludes `failure` because its verdict step exits 1.
- **The unmeasured-tail reporter pair is replaced by the verdict** in
the 8 Lint lanes, and with it each lane's `actions: read` scope (it
bought only the reporter's jobs-API read). Behind a red gate the tail is
now empty by construction; behind a failed setup step the verdict lists
the gates that never ran, offline, from the `steps` context.
- **One new gate step**, `Lane verdict self-test`, in `lint-ci-engine`
beside `Aggregator roster gate`: the instrument's own self-test, the one
place the outcome-versus-conclusion read can go red (on a green tree
both readings agree).
- **The two notes the ruling names are rewritten.** The #13690 `GATE
ORDER IS DELIBERATE` note (the ruling's 151–159) is now `EVERY GATE
RUNS, AND ONE VERDICT STEP KEEPS THE RED`: what is fail-fast and why,
why `continue-on-error` alone would trade the red for silence, what the
verdict reads, what a new gate step needs, the one gate that feeds
another (`check:authorable-surface` writes the tree `check:docs` reads;
the second refuses loudly on a missing tree), and that the order no
longer shortens a red run. The `⛔ Nor is it continue-on-error` note
(502–503) stood in the reporter pair's block in `lint-eslint`; that
block is gone, and the verdict step at the same site carries the
rewritten note. The `lint` aggregate's header and the
`lint-repo-tooling` / `lint-eslint` headers that repeated "stops at its
first red" are corrected too.
### `scripts/ci/lane-verdict.mjs` (new; its header is the authority)
- Reads the lane's **roster from the workflow file**
(`GITHUB_WORKFLOW_REF` names it, `GITHUB_JOB` the job): every step whose
`continue-on-error` is set. The `steps` context holds only steps with an
`id:`, so a roster taken from the context could never see a gate added
without one; read from the file, such a gate is **UNREAD and red**,
naming it.
- Reads each gate's **`outcome`, never its `conclusion`**: under the
flag a failed step's conclusion is `success`.
- `skipped` is two facts, told apart by what is certain: a gate with its
own `if:` in a job still `success` was turned off by that condition (the
scoped families); any other `skipped` is "did not run", red.
- A failed fail-fast step (`job.status` not `success`) is red and names
the gates it hid. Zero gates, an unparseable context or a missing input
is a refusal (exit 3, PREREQUISITE NOT MET), never a pass.
- Prints every gate with its verdict, one `::error` annotation per red
gate, and a job-summary table only when not green. Self-test: 31 cases
over 9 named batteries with a floor and a handshake (the AGENTS.md
shape).
### `.github/workflows/ci.yml` (Test Core)
- Both turbo invocations of `Run this shard's tests` pass
`--continue=dependencies-successful`.
- A red leg no longer `break`s the leg loop: the CLI slice leg runs
after a red whole-package leg too, `STATUS` keeps the first non-zero
exit and the step exits with it, so the shard is red exactly when it was
before. The `⛔ A failing leg STOPS the remaining ones` note is rewritten
with the wall argument: the legs together are the shard's own planned
work, so a red shard takes what a full one takes, inside the budget the
50-minute wall was sized to.
- The attestation pair, the completeness guard, the timing steps and
`check-shard-attestation`'s static pins are untouched (`pnpm
check:shard-attestation` green below).
### `AGENTS.md` (one sentence)
`That job runs its gates sequentially, so the first stale artifact masks
every one behind it` became false with this change; it now says the
lanes run every gate after a red, so one run reports every stale
artifact. This file is outside the claim's declared surface; it is here
because the change makes the sentence wrong.
### `scripts/report-unmeasured-gate-tail.mjs` (deleted)
Its only caller was the step pair this PR replaces; nothing else in the
tree imports or names it (`git grep` over the whole tree: zero hits
outside the two files this PR edits). Its self-test wiring left with it,
and `check:self-test-wired` is green below.
## The PR's mechanism assumptions, measured
1. **`outcome` versus `conclusion`.** GitHub records `outcome` before
`continue-on-error` is applied and `conclusion` after it, so a failed
gate under the flag reads `outcome: failure`, `conclusion: success`. The
verdict reads `outcome`; the self-test battery `outcome, never
conclusion` feeds it exactly that shape and goes red if `conclusion`
were read. The live half (that the runner really records it so, and that
a skipped scoped gate appears in the context as `skipped`) is what the
proof run below reads.
2. **Existing infrastructure.** No gate forbids a step-level
`continue-on-error` on these lanes: the guards that read the key pin
job-level flags (`check-required-contexts`) or named steps elsewhere
(ci.yml's attestation pair, `pr-automation.yml`, release.yml's gate job,
the queue guard's toolchain install), none of which this touches. The
`lint.yml` note about #16454 is about ci.yml's timing steps and is
unchanged. The aggregators and `check-aggregator-roster.mjs` are
unchanged and green. The unmeasured-tail printing is replaced, not
duplicated.
3. **Test Core.** A three-package turbo 2.11.5 probe (`a#test` exits 1,
`c` depends on `a`): with the flag `b` and `c` ran and turbo exited 1
with `Failed: a#test`; without it the run stopped at `a` (`Tasks: 1
successful, 4 total`), exit 1. The flag moves no task hash: `--dry=json`
on this tree, plain against flagged, 5 of 5 identical, so main-seeded
caches still replay.
4. **The proof on CI.** The branch carries one THROWAWAY commit,
`5301220aa`, and its revert: a dead docs anchor that reddens `Docs
anchors resolve to real headings` (the first gate of `Lint · repo &
tooling gates`, 59 gates behind it) and a deliberately red test in
`@objectstack/types` (one Test Core shard). The final head's tree is
byte-identical to `5a7b95e15`, the commit before the throwaway. The
readings from that run's jobs API (every later gate's step conclusion,
the lane and aggregate conclusions, the verdict's named red, the Test
Core shard's turbo tally) are in the dev report comment on #23191; this
body was written once, at creation, before that run finished.
## Local verification
Run on `1e695d6b1` (this branch merged with `origin/main` at
`44e2d505c`). The next commit, `5a7b95e15`, changed comments only (the
rewritten note in `lint-eslint`, one sentence of the verdict script's
header); the same union re-run on `5a7b95e15` was still running when
this body was written, and its reconciliation is in the dev report.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 68 derived commands; all 68 run and
reconciled with `--ran`: **64 exit 0, 4 NOT MEASURED** (exit 3,
PREREQUISITE NOT MET: `check:dts-closure`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure`, `check:sourcemap-no-sources-content` read
built `dist/` trees; this diff changes no package source, and CI's Type
Check and Build Core lanes run them). Among the 64:
`check:pm-dispatch-gates`, `check:required-contexts`,
`check:shard-attestation`, `check:select-gate-families`,
`check:self-test-wired`, `check:self-test-workflow-commands`,
`check:step-collectors`, `node scripts/check-aggregator-roster.mjs`
(gate and self-test), `check:workflow-status-functions`,
`check:workflow-step-name-quoting`, `check:nul-bytes`,
`check:pm-governed-prose`, `check:type-check-debt`, `node
scripts/ci/lane-verdict.mjs --self-test`.
- A simulation of the in-job verdict over the real `lint.yml`, every
lane, with a context built from the file: all 12 lanes OK when every
gate succeeds (the four scoped lanes as "turned off by their own if:"),
and every lane with an unconditional gate FAILED naming it when its
first gate's outcome is `failure` under conclusion `success`.
- ESLint, narrowed and proved: the diff's only linted file is
`scripts/ci/lane-verdict.mjs` (`eslint --no-inline-config --format
json`: 1 file, 0 errors, 0 warnings); `eslint.config.mjs` never enables
type-aware linting, so no untouched file's verdict can move, and the
deleted script has no importer.
## Acceptance notes
- `check-test-completeness.mjs`'s NEITHER branch (a package that printed
no summary on a run that did not complete is a note, not red) was
written for a turbo that stopped at the first red. Under `--continue`
nothing is cancelled except a failed build's dependents, so that note
could now be upgraded to red for a silent package beside an unrelated
red. Not done: it is the gate's own judging logic, which this card does
not touch. Carrier: none.
- A gate step added later without `continue-on-error` is fail-fast again
(still red, one red per cycle); without an `id:` the verdict reds the
lane naming it. The new note above `jobs:` says both. No structural gate
for the first case was added (new gates default to no).
## 维护者速读(草稿)
- **改了什么**:`Lint · *` 八条与 `Type Check · *` 四条车道里的 240
个门禁步骤,前一个红了后面照跑;每条车道最后一步「Lane verdict」读每个门禁的结果,点名红的并退出 1,车道和两个必需聚合(`Lint
& Repo Gates`、`TypeScript Type Check`)照样红。Test Core 的 turbo 加
`--continue=dependencies-successful`,一个包红不再停掉同分片其它包。原「未测尾巴」报告步骤由裁决步取代,脚本删除。
- **为什么改**:按 #23177 裁决 B 第 1 项(A1)。过去一个红门禁会跳过同车道其后所有门禁、turbo
也在第一个红包处停下,一轮 CI 只能看到一个红,每个藏住的红多花一轮 CI 与一次 Version PR 刷新。
- **风险与代价(含回滚)**:门禁命令与判定一字未改,只改编排与汇总;红的运行现在会跑满(时长等于绿的运行,在现有 job
时限内)。风险点是裁决步本身:若它读错字段会让红变绿,所以它读 `outcome` 不读 `conclusion`、名单从 workflow
文件读、读不到即红,并有自测与 CI 实证。回滚:revert 本 PR 即恢复原 fail-fast 与报告步骤。
- **席位意见**:
- **你要做的**:审阅后批准(Tier H,受管面须维护者批准);批准前本 PR 保持草稿。
---
_Generated by [Claude
Code](https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 917b1a0 commit 7aa82a3
5 files changed
Lines changed: 1271 additions & 1213 deletions
File tree
- .github/workflows
- scripts
- ci
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
912 | 912 | | |
913 | 913 | | |
914 | 914 | | |
915 | | - | |
916 | | - | |
917 | | - | |
918 | | - | |
919 | | - | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
920 | 944 | | |
921 | 945 | | |
922 | 946 | | |
| |||
938 | 962 | | |
939 | 963 | | |
940 | 964 | | |
941 | | - | |
| 965 | + | |
942 | 966 | | |
943 | 967 | | |
944 | 968 | | |
| |||
1009 | 1033 | | |
1010 | 1034 | | |
1011 | 1035 | | |
1012 | | - | |
| 1036 | + | |
1013 | 1037 | | |
1014 | 1038 | | |
1015 | 1039 | | |
1016 | | - | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
1017 | 1044 | | |
1018 | 1045 | | |
1019 | 1046 | | |
| |||
0 commit comments