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 |
…g seat reviews in-seat at CONTRACT_REVIEW_TIER, no seat list (objectstack-ai#18363) Fixes objectstack-ai#18349 Clause-②: no ## What changed The maintainer ruled (2026-09-16, recorded verbatim on the card) that the clause-② contract review is done in-seat by the dispatching seat at the contract-review tier, and that there is no longer a 「只在 spec 席」: > 1. 条款②复核 = 派发席在契约复审档席内做,记录写档位读数;不再有「只在 spec 席」。 > 2. 新 `packages/spec` 工作仍由 spec 席认领(现行规则,零延迟成本);顺手触 spec 的已派发卡照旧不转席。 Sentence 2 needs no text change (SKILL.md :527–:528 and contract-review.md :25 already say it). Sentence 1 re-keys the three lines that bound the review to a SEAT so that the served TIER is the condition — in place, no new line: | file:line | before (bytes) | after (bytes) | |---|---|---| | SKILL.md :513 | 「强制条款②:放宽接受集或扩大公开面的卡默认判断档施工;契约复审档复核只在 spec 席。」(118) | 「强制条款②:放宽接受集或扩大公开面的卡默认判断档施工;达档复核归派发席席内。」(112) | | SKILL.md :523 | 「契约复审档留给 skills 席与 spec 席的条款②复核,其余席位 ⛔ 永不起该档子代理。」(109) | 「条款②复核只在实测达档的席内做,记录写读数;未达档席 ⛔ 不审、不起该档子代理。」(114) | | contract-review.md :26 | 「逐车道:spec 席达档审契约增量;余席默认档自审加门禁、挂标;豁免仅独立性件与保险丝。」(118) | 「按档位:达档席内审契约增量;未达档默认档自审加门禁、挂标;豁免仅独立性件与保险丝。」(119) | Plus ONE new platform-readings.md row (:429, 113 B, in 「读数陷阱」 beside the other harness readings): 「harness 按内容拒写:同会话派发 PR 上 PASS 拒为 `[Self-Approval]`;同通道建卡、ACCEPT 照过。」 — measured 2026-09-16 on objectui PR objectstack-ai#9578: the harness auto-mode classifier refused a `VERDICT: PASS` record on a PR the same session had dispatched as `[Self-Approval]`, while a card create and an ACCEPT on the same channel were served seconds later; this seat's five same-shape PASS records of 2026-09-15 through `post-stamped.mjs` were not refused — so the reading is the channel-and-content pair, not the endpoint. Untouched: the SELF-REVIEW machine reading (contract-review.md :37 — `Implemented-by:` is the branch for a subagent dev) and the independence pair (:35–:36). Note: the card body and the ruling comment cite the 「逐车道」 line as contract-review.md :25 (read at `500c1b56`); on `origin/main` `1411cf2c` it is :26 (:25 is the 「归属该卡派发席」 line) — the edit went to the line by content, not by number. ## The one design choice — how the tier condition is spelled The question was how to spell the condition so a seat NOT at the tier cannot read itself as eligible. Options: (A) 「达档」 alone — a seat could read its configured tier as eligibility; (B) 「实测达档」 plus 「记录写读数」 — eligibility is the measured reading and the record must carry it (contract-review.md :29 already makes a record without `Served-tier:` no verdict; :53 already says a self-declared tier is not a reading), with the ⛔ half 「未达档席 ⛔ 不审、不起该档子代理」 closing the escape of spawning the tier subagent; (C) naming the `get_session` fields inline — over the 120-byte budget and already recorded at platform-readings :378 and contract-review :55. Chosen: B. On the four axes — 实际业务需求: the live case is real (objectui PR objectstack-ai#9578, a `Clause-②: yes` card held in the safe state) and every PM seat today measures at the tier, so in-seat review removes a handoff that had no executable spelling; 项目长远合理性: a tier condition is machine-checkable (`--pair` reads `Served-tier:`), a seat list is not; 防 AI 犯错: 实测 binds eligibility to a reading rather than a declaration, and the ⛔ half keeps the prohibition greppable; 创业阶段不扩散: no new mechanism, label, handoff artefact or line — three in-place re-keys and one fact row. ## Reverse verification BEFORE (`1411cf2c`): the three lines quoted above with their byte counts; sweep `grep -n '只在 spec 席\|其余席位 ⛔ 永不起该档\|spec 席达档审'` over SKILL.md and references = 3 hits (:513, :523, contract-review :26); `grep -c CONTRACT_REVIEW_TIER SKILL.md` = 1; SKILL.md 812 lines, contract-review.md 60, platform-readings.md 463; frame block :734–:755 md5 `3327d02c56f8a0eca88569dad2270f32`. AFTER (`55b467b0`): sweep = 0; constant count = 1 (unchanged); 812 / 812, 60 / 60, platform-readings 464 (ceiling 466; PR objectstack-ai#18359, open on other regions of the same file, nets +2, so 466 once both land and no 467th); frame md5 unchanged `3327d02c56f8a0eca88569dad2270f32`; `git diff --stat` = the three files and nothing else; zero raw control bytes in the touched files; every touched line ≤ 120 B (the ratchet's `MAX_LINE_BYTES`). ## Gates (run on `55b467b0`) `node scripts/pm/dispatch-gates.mjs --commands` over the three paths derived 18 commands; all 18 run; `--ran` reconciliation: 「18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN」. Green: `check:pm-skill-ratchet`, `check:pm-skill-id-lint`, `check:skill-frame-sync`, `check:nul-bytes`, `check:pm-governed-prose`, `check:pm-governed-merges`, `check:pm-half-states`, `check:required-contexts`, `check:doc-authoring`, `check:agent-test-spelling`, `check:watch-hint-literal`, `check:refd-timer-probe`, `check:driver-memory-census`, `check-closing-keyword-parity` (and its `--self-test`), `check-comment-mask-corpus`, `check-governed-queue-guard --self-test`, and `@objectstack/lint check:doc-formula-expressions` (first run exit 3 PREREQUISITE NOT MET — the formula and lint dist were not built; both built under the verify lock, re-run exit 0). Path face: `check-governed-merges.mjs --test SKILL.md` = GOVERNED (exit 3 is that verdict). The `check:pm-dispatch-gates` battery was not derived for these paths. `check-clause2-carriers.mjs --pair` on this PR: reading in the report comment on the card. ## Acceptance notes - Landing: the SKILL.md hunks are the rules layer, so this PR stays DRAFT; the seat hangs the four-piece and requests the authorized approval. No reviewer requested, no auto-merge, nothing flipped. - Region-level parallel: PR objectstack-ai#18357 (SKILL.md :626–:632) and PR objectstack-ai#18359 (platform-readings :38–:63 and :126–:133) are open on the same files in other regions; this branch was not rebased onto either — whichever lands second merges `origin/main`. - Nothing versioned moves (`.claude/**` is shipped by no package's `files[]`), so `skip-changeset`. - noted, not filed (承接者: the skills seat, on this card's patch round or a follow-up card): three lines OUTSIDE the declared file surface still spell the seat-bound rule and now read against the re-keyed lines — SKILL.md :641 「交付后复核由派发席席内完成:spec 席达档,余席默认档加门禁;记录 = 同形评论落 PR 或卡。」 (it escapes the sweep pattern: 「达档,」 not 「达档审」), contract-review.md :51 「保险丝只管 spec 席条款②复核与 skills 席…」 and :52 「余席条款②复核与总监席裁决非达档裁决,⛔ 不受本丝…」 (under the ruling every clause-② review is a tier verdict, so the fuse's population becomes every reviewing seat), and :41's 「档位按逐车道」. Not edited here: outside the surface, and the fuse's post-ruling shape (:51–:52) is a design choice rather than a mechanical re-key. `lanes/spec.md` and `lanes/skills.md`: no seat-bound restatement (spec.md :19, :20, :33 and skills.md :20 are trigger and gate lines only). - `dispatch-gates.mjs --tier` prints the tier's model literal rather than the constant name; this PR names only `CONTRACT_REVIEW_TIER`. ## 维护者速读(草稿) **改了什么**:把「条款②契约复核只在 spec 席做」改成「由派发席在契约复审档席内做,记录写档位读数;未达档的席不审、不起该档子代理」—— SKILL.md 两行、contract-review.md 一行原位改写,platform-readings.md 新增一行事实(harness 按内容拒写 PASS 裁决)。行数 812 / 812、60 / 60、464 / 466。 **为什么改**:您 2026-09-16 裁「同意」的两句里第一句要求去掉「只在 spec 席」;objectui 那张 Clause-② 卡在没有交接拼写的情况下被卡在安全态,席内达档复核是零延迟的路。第二句(新 spec 工作仍归 spec 席)现行文本已写,无需改。 **风险与代价(含回滚)**:三行改写全在 `.claude/**`,不发布、不动代码;回滚 = revert 本 PR 的一个 commit。残留:SKILL.md :641 与 contract-review.md :51–:52 仍按旧席位表述,本 PR 未动(超出派发面),需席位补一轮。 **席位意见**:(留空,席位定稿) **你要做的**:确认本 PR 的三行改写是否如实落了裁决第一句;确认后由席位走四件套 + 授权批准落地;顺带定 :641 / :51–:52 是并入本 PR 补丁轮还是另立卡。 --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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