Skip to content

Commit 19af43d

Browse files
pm-dispatch: restore needs:contract-review as a marker for a PR awaiting its at-tier review (#19993)
Fixes #19973 Clause-②: no ## 维护者速读(草稿) **改了什么**:把 `needs:contract-review` 恢复为「PR 在等达档契约复核」的可见标记。规则落在 `contract-review.md` 新增的一节(何时挂、何时摘、谁读,并写明没有任何检查读取它);`landing-operations.md` 里「子代理起不来」那一行原地改写,点名这个标记(行数不变);`ensure-pm-labels.sh` 加回这个标签的定义(新颜色,说明写明「只是标记,没有检查读它」)。 **为什么改**:您的原话「恢复 needs:contract-review,把这句原话写进一张 skills 车道的卡」,回答的是「只作等复核标记,不作闸门」那一问。上一班达档复核子代理连续被限流,12 个 CI 全绿的 PR 在等达档复核记录,只能靠翻座位贴才看得到;现在一个过滤 `is:pr is:open label:needs:contract-review` 就能列出来,交接也不必先读座位贴。 **风险与代价(含回滚)**:不新增任何门禁:没有 check、workflow、队列守卫或巡查脚本读它,落地仍只认 `## Contract review` 记录,标记丢了或多挂了都不会放行或拦下任何 PR。代价是派发席每个条款② PR 多两次标签写(ACCEPT 时挂,PASS 时摘)。已逐个核对本仓会写 PR 标签的 workflow:今天没有任何自动机制会摘掉手挂的 PR 标签。回滚 = revert 本 PR;GitHub 上的标签对象不受影响。 **席位意见**:(留空,待席位填写) **你要做的**:合并后请持有 `gh` 的人跑一次 `bash scripts/pm/ensure-pm-labels.sh --reconcile`,把现存标签对象的颜色与说明对齐(它现在是 GitHub 自动建的灰色、说明为空)。本 PR 的受管路径全在 `.claude/**`(Tier S),由席位在达档复核 PASS 后落地,不等您点合并。 ## Summary The maintainer's instruction recorded on #19973 — 「恢复 needs:contract-review,把这句原话写进一张 skills 车道的卡」, answering a question that proposed 「只作等复核标记,不作闸门」 — brings `needs:contract-review` back as a **visibility marker, never a gate**. Every layer that ruling record 5770886272 (letter B) retired stays retired: no queue-guard refusal, no `--pair`, no double carrier, no independence pair. The enqueue gate still decides on the `## Contract review` record alone, and nothing in this diff reads the label. ## What changed — 3 files, +28 / -2 | path | change | |---|---| | `.claude/skills/pm-dispatch/references/contract-review.md` | a new section, heading plus 5 rule lines (:30-:36); the :3 pointer now lists it. 28 → 36 lines, ceiling 60 | | `.claude/skills/pm-dispatch/references/landing-operations.md` | :13 rewritten in place to name the marker. 101 / 101 lines; that line goes 113 → 119 bytes | | `scripts/pm/ensure-pm-labels.sh` | one main-repo row after `needs:pack-smoke`: colour `bfdadc`, a 95-character `-d`, and a comment block naming the label's readers | The rule as landed (contract-review.md :32-:36): - it is only a marker, not a gate. The PR is the single carrier; a copy on the card is outside the rule and not required. - **hang**: at ACCEPT, if either clause-② limb hits and no same-form PASS is on file for the current head, the dispatching seat hangs it on the PR in the same stroke. - **clear**: when a same-form PASS is on file for the current head, the seat that posts it clears the marker in the same stroke. When the PR merges or closes, the dispatching seat clears it. **A FAIL does not clear it.** - **readers**: the maintainer's filter `is:pr is:open label:needs:contract-review`, and each seat's patrol and handover. - ⛔ no check, workflow, queue guard or patrol script reads it. Enqueue recognises only the same-form record, and the marker being present or absent changes no verdict. landing-operations.md :13, before and after: ```text - 子代理起不来 ⇒ 复核缺席,PR 留 draft 队列外等档;唯一旁路是维护者亲审,逐次为准。 - 子代理起不来 ⇒ 复核缺席,PR 带 `needs:contract-review` 留 draft 队列外;旁路仅维护者逐次亲审 ``` The line keeps three facts: the review is absent; the PR stays draft, outside the queue; and the maintainer's own review is the only bypass, per instance (唯一 … 逐次为准 → 仅 … 逐次). 「等档」 is carried by the marker itself, which already says the PR awaits its at-tier review. Keeping 「等档」 as well measured 125 bytes, over the 120-byte cap. ## Durability — what removes a PR label on this repo today (read at base `ba77509eee`) - `pr-automation.yml` job `pr-size` → `scripts/pr-labels.mjs --size`. It POSTs the computed `size/*` label, then sends a targeted DELETE only for stale `size/*` labels (`planSizeWrites` loops over the size family and nothing else). The job is skipped on `labeled` / `unlabeled` / `edited`. - `pr-automation.yml` job `auto-label` → `scripts/pr-labels.mjs --paths`. It only POSTs: 「Path labels are ADD-ONLY … So this half issues POST and has no DELETE at all」 (:225-:227). The keys in `.github/labeler.yml` are documentation, `protocol:*`, ci/cd, dependencies, tests and tooling; none is a `needs:*` label. - `lint.yml` runs `node scripts/pr-labels.mjs --self-test`, which pins that no write plan emits a PUT. It also runs `node scripts/check-whole-set-label-write.mjs`, which reds on a whole-set `PUT /issues/{n}/labels` in any spelling anywhere in the repo. That verb (third-party labelers, and a `labels` field written through MCP) is what removed this label in the gate era. - `stale.yml` (`actions/stale`) removes only its own `stale` label. It closes a PR after 37 idle days, and a close is already a clear trigger in the rule. - `half-state-patrol.yml` runs `sweep-closed-cards.mjs --write`, which strips `PM_RESIDUE_LABELS` (the `pm:*` state labels) from **closed cards** only. - `merge-queue-triage.yml` adds labels to its anchor issues only. `fleet-write.yml` runs only the ops a seat names. - objectui's labeler runs with `sync-labels: true`, but that is objectui's. This label is created in this repo only. ⇒ **Today no mechanism on this repo removes a PR label that a seat hung by hand.** The live carriers' event history agrees. Every labeled or unlabeled event for `needs:contract-review` on PR #19962, PR #19968, #19955 and #19953 is by `objectstack-fleet[bot]`, that is, by a seat. The only removal pair (PR #19962 at 11:27:07Z, #19953 at 11:27:41Z) was the spec seat's own stroke after an at-tier FAIL (comment 5813182458, 「Carriers stripped on the PR and on this card」), and both were hung again at 12:14Z. Losing a marker is also the safe failure: a waiting PR drops out of the filter, but nothing is released, because the queue guard reads the record. ## Live carriers at dispatch (read 2026-09-24T14:46Z) — ⛔ this PR changes no label on any of them | carrier | kind | what the rule says | |---|---|---| | #19962 | PR, draft, head `22c9473c86` | path limb hits (`packages/spec/src/security/rls.zod.ts`). The marker stays until a same-form PASS is on file for its current head; whoever posts that PASS clears it. Under the rule, the 11:27Z clear after the FAIL would not happen: a FAIL leaves the marker on. | | #19968 | PR, draft, head `b05a88136d` | path limb hits (`packages/spec/src/ui/view.form.ts`). Same as above. | | #19955 | card | a card copy is outside the rule and not required. What happens to it is for the spec seat that hung it. | | #19953 | card | same as #19955. | Aligning these four carriers is the dispatching seat's closeout step once the rule is on `main`, as the claim amendment on the card says. It is not part of this PR. ## Four scripts that still name the label as retired — unchanged, on purpose `scripts/pm/check-half-states.mjs` :11927 and :18308, `scripts/pm/check-skill-line-ratchet.mjs` :448 and :830, `scripts/pm/check-widening-tells.mjs` :673, and `scripts/pm/clause2-line.mjs` :11 and :306. Each one describes the **gate role** (a half-state row that patrolled it, a raise provenance, a dated census line, the ruling's summary, a measured incident). That role is still retired, so every sentence stays true. None of them reads the label, and this PR does not make any of them a reader. `AGENTS.md`, `SKILL.md`, `state-machine.md` and `.claude/agents/os-dev.md` are untouched too; the claim excluded them. ## Acceptance notes - The filer's reading on the card calls #19955 and #19953 PRs. The REST objects carry no `pull_request` key, so they are cards; the claim amendment already reads them that way. - 40 cards and PRs that are no longer open still carry the label from the gate era (for example PR #19666 and PR #19618; 44 items in all, 4 of them open). The filter reads `is:open` and no script reads the label, so they are inert. Nothing in this PR touches them. - Governed PRs on either landing tier, this one included, also wait on an at-tier `## Contract review` record. They are outside the restored marker's population, which is only the two clause-② limbs, the population the retired label had. Whether to widen it is left to the seat as an open question in the report. - `SKILL.md`'s state-model table does not list the marker. It is a PR label, not a card state; its rule lives in `contract-review.md`; and `SKILL.md` is outside this PR's surface. ## Pending after merge — the seat's, not this PR's - Someone holding `gh` runs `bash scripts/pm/ensure-pm-labels.sh --reconcile` once. The live object is `ededed` with an empty description (read 2026-09-24T14:46Z), and create-if-missing never changes an object that already exists. ## Tests — on `84f4580e` - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; three-dot vs merge base `ba77509ee`) derived 32 commands. The dispatch named four more: `node scripts/check-skills-token-ratchet.mjs`, `node scripts/pm/check-governed-queue-guard.mjs --self-test`, `pnpm check:pm-expected-skips` and `pnpm check:pm-governed-prose`. **All 36 exit 0**, each exit code captured before any pipe. `--ran` reconciliation: 「32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN」. - `pnpm check:pm-label-desc-cap`: 「39 label descriptions … all ≤100 characters (longest: 100, tooling)」 (38 on base). - `pnpm check:pm-skill-ratchet`: 「contract-review.md is 36 lines (ceiling 60; headroom 24)」 and 「landing-operations.md is 101 lines (ceiling 101; headroom 0)」. All lines are ≤120 bytes; :3 is at exactly 120. - `pnpm check:pm-skill-id-lint`: 「34 file(s) clean」. `pnpm check:skill-frame-sync`, `pnpm check:doc-authoring`, `pnpm check:pm-governed-prose` and `pnpm check:nul-bytes` are green. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first exited **3** (PREREQUISITE NOT MET: `@objectstack/formula` / `@objectstack/lint` not built). That run measured nothing. After `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under `scripts/pm/os-verify-lock.sh` (VERDICT command-exit 0), the rerun exited 0. - `bash -n scripts/pm/ensure-pm-labels.sh` exits 0. A fake `gh` on PATH ran `ensure-pm-labels.sh --reconcile`, exit 0: the new row issued `label create needs:contract-review -R objectstack-ai/objectstack -c bfdadc -d …` and the matching `label edit … --color bfdadc --description …` with the same string. - `node scripts/pm/check-governed-merges.mjs --test` on the three paths returns GOVERNED, **Tier S** (`.claude/** ×2`); `scripts/pm/ensure-pm-labels.sh` is not on the register. - A self-scan for control bytes on the three files finds none. - Not run locally: no package is touched, so there is no build closure and no package test or typecheck. `pnpm lint` and the CI-only families (the shard attestation, the test-completeness reader and the type-check lanes) are left to CI. --- _Generated by [Claude Code](https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent bfa23a8 commit 19af43d

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

‎.claude/skills/pm-dispatch/references/contract-review.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 契约复核细则(按面)
22

3-
见 landing-operations.md 〈入队与落地〉的条款②闸门;本文只放复核归属与资格。
3+
见 landing-operations.md 〈入队与落地〉的条款②闸门;本文只放复核归属、资格与等复核标记。
44

55
## 复核归属与资格(按面)
66

@@ -26,3 +26,11 @@
2626
- 例外:纯重生成 head 后移原记录继续管;判据机读已提交树;PR 落 `Regen-provenance:` 行。
2727
- ② PR check 全绿,⛔ 非 required 子集;例外:merge-base 同签名的红不计、按设计而红见 SKILL.md。
2828
- 签名 = 失败步 + 首错行,读 base check runs 的 API ⛔ 不凭口述;主干红止血立单不变。
29+
30+
## 等复核标记 `needs:contract-review`
31+
32+
- 只作等复核标记,⛔ 不是闸门;单载体 = PR,卡上副本在规则外、不是要求。
33+
- 挂:ACCEPT 时条款②任一肢命中而现 head 无同形 PASS 在案 ⇒ 派发席同笔挂于 PR。
34+
- 摘:现 head 同形 PASS 在案 ⇒ 写记录的席同笔摘;PR 合并或关闭 ⇒ 派发席摘;FAIL 不摘。
35+
- 读者 = 维护者过滤 `is:pr is:open label:needs:contract-review` 与席位巡查、交接。
36+
- ⛔ 无 check、workflow、队列守卫或巡查脚本读它;入队只认同形记录,标记有无不改判。

‎.claude/skills/pm-dispatch/references/landing-operations.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- 路径肢 = diff 触及契约面 `packages/spec/src/**` 非测试,含 error-code-ledger、`*.zod.ts` 契约 schema。
1111
- 声明肢 = 认领评论声明 `Clause-②: yes`,与路径无关;错误的 `no` 是可审计的假申报。
1212
- 交付后复核按面欠 ⛔ 不按车道(五面见 `references/contract-review.md`);双肢命中即 spec 车道。
13-
- 子代理起不来 ⇒ 复核缺席,PR 留 draft 队列外等档;唯一旁路是维护者亲审,逐次为准。
13+
- 子代理起不来 ⇒ 复核缺席,PR 带 `needs:contract-review` 留 draft 队列外;旁路仅维护者逐次亲审
1414
- PASS ⇒ ready、auto-merge;FAIL ⇒ 补丁轮;⛔ 免复核不放行。
1515
- DELIBERATE CORRECTION 红(`check-empty-changeset`):同 head 达档复核 PASS 记录即确认,⛔ 不等维护者。
1616
- 记录须点名被改 note、逐句判改写句,缺一不算;算即按 SKILL.md 三条件带红入队,门禁不改。

‎scripts/pm/ensure-pm-labels.sh‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,24 @@ done
325325
# would assert a kinship the paragraph above spends its length denying.
326326
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true
327327

328+
# needs:contract-review — a MARKER, never a gate; rules in
329+
# .claude/skills/pm-dispatch/references/contract-review.md 〈等复核标记〉. The
330+
# dispatching seat hangs it on the PR at ACCEPT when either clause-② limb hits
331+
# and no same-form PASS is on the current head; the seat posting that PASS, or
332+
# the dispatching seat at merge/close, clears it (a FAIL leaves it on). Named
333+
# readers: the maintainer's PR-list filter
334+
# `is:pr is:open label:needs:contract-review` and each seat's patrol and
335+
# handover. ⛔ No check, workflow, queue guard or patrol script reads it — the
336+
# queue releases on the `## Contract review` record alone.
337+
# Restored by the maintainer's instruction recorded on #19973, verbatim:
338+
# 「恢复 needs:contract-review,把这句原话写进一张 skills 车道的卡」 — answering a
339+
# question that proposed a marker, not a gate. The gate role, `--pair`, the
340+
# double carrier and the independence pair that ruling record 5770886272
341+
# retired stay retired. Main repo only: the clause-② contract surface lives
342+
# here. Colour bfdadc sits outside the state and red (blocked / decision)
343+
# families.
344+
gh label create needs:contract-review -R objectstack-ai/objectstack -c bfdadc -d "Marker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides" 2>/dev/null || true
345+
328346
# Routing labels exist only on the main backlog repo, and mark SEAM cards only
329347
# (file-at-destination ruling: pure sibling-repo fixes live in the target repo).
330348
#

0 commit comments

Comments
 (0)