fix(pm): the enqueue bar names expected skips — a path-filtered job's skip is not a failure, and a check reports the skips outside the declared roster - #18357
Conversation
… skip is not a failure, and a check reports the skips outside the declared roster SKILL.md :629 is re-keyed in place (112 B -> 119 B, 812 / 812 lines): every check is `success` or an expected skip, the expected set being the roster `scripts/pm/check-expected-skips.mjs` declares. The check reads a head's check-runs (--pr / --head / a pre-fetched payload) and exits 0 when every skip is in the roster, 4 naming a skip outside it (classified as a filter miss or a same-suite dependency skip), 3 when the head cannot be read, carries no check-runs, or has a check-run still running. The roster is tied to the workflows: --self-test parses each row's workflow and asserts the job, its name, its `if:` and the gate spelling, so a renamed, deleted or re-gated job reddens the roster instead of letting it rot into memory. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr
Contract reviewServed-tier: Reviewing seat: ① Derived judgments
② Semver levelNot applicable — nothing published moves; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)— PR #18357 / #18308改了什么:入队门槛原文「PR 上每一个 check 全绿」在本仓库永远不成立——路径过滤的 job 在每个 PR head 上都以 验证:本席在该 head 的独立 worktree 上重跑:自检 99/99;ESLint 0;ratchet 812/812、:629 119 B;id-lint、frame-sync、self-test-wired 绿; 要你做的:审 PR #18357,同意即 APPROVE( 顺带:dev 问要不要把 Generated by Claude Code |
|
Ruling-C landing provenance — skills seat (signed off, 留守 on this PR), session Generated by Claude Code |
Fixes #18308
What
.claude/skills/pm-dispatch/SKILL.md:629 read 「入队资格 = PR 上每一个 check 全绿,⛔ 不是 required 子集;required 集是队列强制的地板。」 — a bar no landing on this repository can satisfy, becauseskippedis the ordinary conclusion of a path-filtered job and the charter defined no state for it. Measured over the ten most recent landed heads (below), every head carried 8–19 skipped check-runs beside its successes; the seat's landings read 「success = green, skipped = not failed」 by an unstated convention.Two changes, both inside the claimed file surface:
SKILL.md :629, re-keyed in place (112 B → 119 B, ≤120 B; 812 → 812 lines; no issue number):
The bar now names the two states a check may be in —
success, or an expected skip — and points at the one machine-readable roster of expected skips. The third clause of the old line (「required 集是队列强制的地板」) did not fit the byte ceiling and is carried by AGENTS.md §7 (「the queue enforces only the required set」); the ⛔ clause is kept verbatim.scripts/pm/check-expected-skips.mjs(new;package.jsongainscheck:pm-expected-skips= its--self-test): given--pr Nor--head SHA(or a pre-fetched payload via--check-runs-json FILE|-), it reads the head's check-runs and judges everyskippedrun against a roster declared once, in the file, as data with a one-line reason per row. Exit register: 0 every skip is in the roster · 4 a skip is outside it (each named and classified: a filter miss, or a dependency skip when the same check suite holds a failed run; a rawmatrixtemplate in the name is read as "skipped before matrix expansion, i.e. a job-level gate — never a workflow-levelpaths:filter, which creates no check-run at all") · 3 NOT MEASURED (unresolvable sha, 404, network, no check-runs on the head, or a check-run still running — the skip set is not final). Report-only; the self-test pins structurally that the file carries nomethod:key and imports no writer.The roster is tied to the workflows, not remembered:
--self-testparses each row's workflow with theyamlpackage and asserts the job exists, carries the row's name, carries anif:, that theif:spells the declared gate (needs.filter.outputs.X != 'false', thegithub.event.actionexclusion, or the label literal), and — for ci.yml rows — that thefilterjob's output keeps its|| 'true'widening, which is what makes "the merge-queue build runs it" true. The audit is driven red in the self-test on a deleted, renamed, un-gated and re-gated job, a lost widening and an unreadable workflow.The roster (11 names), measured over ten landed heads
Build Corefilteroutputcoresaid false; REQUIRED context, judged on the queue buildTemporal Conformance (live PG + MySQL)Dogfood Regression Gate (${{ matrix.shard }}/3)Dogfood Regression Gaterunsif: always(), never skips)Dogfood Verify CLITest Core (${{ matrix.shard }}/6)coreORcrosspkgboth false (scripts/** is incrosspkg, so scripts/pm heads RUN it)Build DocsfilteroutputdocsConsole Pin GatefilteroutputconsoleCheck PR Sizeif:excludeslabeled/unlabeled/editedevents; each event is its own run on the same headAuto LabelCheck Changesetif:skips a PR carryingskip-changesetPacked-tarball smoke (opt-in)needs:pack-smokeNever skipped on any of the ten heads (and carrying no
if:):Lint & Repo Gates, the fourType Check ·lanes,TypeScript Type Check,Test CoreandDogfood Regression Gate(the aggregates),Governed Surface Queue Guard,filter, the four claim/keyword guards,Check Documentation Links,Close issues referenced in other repositories. Workflows with a workflow-levelpaths:filter (half-state-patrol.yml,board-snapshot.yml) produce no check-run at all on a non-matching head — they are absent on 6 of the ten heads, neverskipped— which is the measured basis for the "a skipped check-run is never apaths:filter" reading.Reverse verification (all at
7a1f99ea)--headon the ten landed heads #18298 · #18307 · #18311 · #18315 · #18316 · #18322 · #18326 · #18327 · #18328 · #18332Build Core,Build Docs,Check Changeset,Console Pin Gate,Dogfood Regression Gate (…/3),Dogfood Verify CLI,Packed-tarball smoke (opt-in),Temporal ConformanceLint & Repo Gatesmutated toskippedLint & Repo Gates (check suite 94780297729)and classifying itfilter-miss--head deadbeef…deadbeefNOT MEASURED — HTTP 422 — the API cannot resolve that sha--pr 18315(the head is looked up through the proxy)19 skipped check-run(s), every one in the roster;--pr 18308(an issue number, not a PR) → exit 3 (HTTP 404)--self-test--json; the structural pins)wc -l812 → 812; :629 112 B → 119 B;check-skill-line-ratchet: SKILL.md is 812 lines (ceiling 812; headroom 0)Gates (local, at
7a1f99ea)node scripts/pm/dispatch-gates.mjs --commands .claude/skills/pm-dispatch/SKILL.md scripts/pm/check-expected-skips.mjs package.jsonderived 45 commands; all 45 were run with the exit captured by redirect, and--ranreconciles:✓ dispatch-gates --ran: 45 derived famil(ies) accounted for — 40 run, 5 NOT-MEASURED (5 DERIVED from a recorded exit 3). The five NOT MEASURED are thedist/-reading families on an unbuilt tree (check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:sourcemap-no-sources-content,@objectstack/lint check:doc-formula-expressions— each printsPREREQUISITE NOT MET); this diff touches no package, so no build closure is owed locally and CI runs them built. Thepnpm check:pm-dispatch-gatesbattery was not derived, so it was not run.Named gates, verdict lines quoted:
check-skill-line-ratchet: SKILL.md is 812 lines (ceiling 812; headroom 0)·check-skill-id-lint: 27 file(s) clean·check-skill-frame-sync: the one declared copy of the decision frame is internally coherent·check-self-test-wired: every one of the 212 script(s) CI runs that ship a --self-test has that self-test run by CI(the new script is not in that population — see Acceptance notes) ·check-nul-bytes: OK (scanned 8707 text file(s))·check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces· ESLint (--no-inline-config) on the new file: exit 0 ·check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md: GOVERNED (.claude/**×1), exit 3 as designed.check-clause2-carriers.mjs --pairis run once this PR exists and its reading goes in the report comment.The one design choice, on the four axes: a roster declared in the check vs. deriving expectedness live from the workflows'
pathsfiltersBuild Corehave run on THIS diff?") is already answered for the required family by the platform: onmerge_groupci.yml'sfilterwidens every output to'true'(the|| 'true'half of the filter contract, now pinned by this check's self-test), so the family runs on the merged tree beforemainmoves. A live derivation would answer a question nobody measured a need for, at the cost below.if:and gate spelling — a rename or re-gate reddens CI), while an evaluator's drift is silent by construction (a wrong glob yields a confident "expected"). Contract-first: the workflow file is the contract, and the roster is a checked reading of it, not a copy of its path lists.Recommendation: the roster in the check (implemented). Should the seat prefer live derivation, nothing here blocks it — the roster rows already carry
workflow,joband the gate's outputs, which is the input a derivation would start from.Acceptance notes
check:pm-expected-skipsexists inpackage.json(mirroring the report-only siblings), but no workflow names it and lint.yml was outside this card's file surface, socheck-self-test-wired(correctly) does not count it and CI does not run its 99 cases. The completion is one lint.yml step beside the othercheck:pm-*steps (run: pnpm check:pm-expected-skips); left to the seat — 承接者:the skills seat, on this PR or a sibling. Noted, not filed.failure,cancelled,neutral, …) are printed loudly underother conclusionsand do not move this check's exit; the bar's success half is read from the same listing. A malformed--head(non-hex) is a usage error (exit 2), a well-formed sha the API cannot resolve is exit 3. Noted, not filed.filter-gated jobs the card did not list, including two REQUIRED contexts); the card's citation of a "platform-readings discipline (a skip is not a pass)" has no verbatim carrier — the nearest lines are AGENTS.md §7 (「Green means the gate-carrying jobs' conclusion is success」) andreferences/review-checklist.md:43. Recorded in the report, no card.origin/maindid not move under this branch after cut (ceb6b5fb).维护者速读(草稿)
改了什么:入队资格这一行改成「每个 check 为 success 或预期 skip」,并新增一个只读的检查脚本
scripts/pm/check-expected-skips.mjs:给它一个 PR 号或提交 SHA,它读出该提交上所有 check,凡是skipped的都对照脚本内声明的「预期 skip 名单」(11 个名字,每个带一句为什么会 skip 的机制),名单外的 skip 会被点名并退出码 4;读不到就退出码 3,绝不当作通过。为什么改:原来的「每一个 check 全绿」在本仓库任何一个 PR 上都做不到——路径过滤的 job 本来就以
skipped结束,实测最近十次落地每次都有 8–19 个 skip。席位一直靠「记得哪些通常会 skip」在判断,而真正要分辨的是「预期 skip」与「本该跑却没跑」。现在名单是机器可读的,并且自测会把名单逐条对照真实 workflow 文件校验(job 存在、名字一致、带条件、条件拼写一致),名单不会悄悄过期。风险与代价(含回滚):规则层只改一行(≤120 B、行数 812 不变);脚本只读不写、不接入任何门禁,CI 不因它变红。名单是名字层面的判断,不回答「这个 diff 是否本该触发某个 job」——必查项由合并队列在合并树上全量重跑兜底,这一点写在脚本头部。回滚 = revert 本 PR。
席位意见:(留空,席位定稿)
你要做的:本 PR 触及
.claude/**(规则层),需要你的 APPROVED;之后由席位落地。是否把该脚本的自测接进 lint.yml(一行pnpm check:pm-expected-skips)由席位决定,本 PR 未动 lint.yml。Generated by Claude Code