pm-dispatch: restore needs:contract-review as a marker for a PR awaiting its at-tier review - #19993
Conversation
…ing its at-tier review The label comes back as a visibility marker only, never a gate: - references/contract-review.md gains a 〈等复核标记〉 section: hang on the PR at ACCEPT when either clause-② limb hits and no same-form PASS is on the current head; clear when that PASS is on file or the PR merges or closes; single carrier = the PR; named readers = the maintainer's PR-list filter and each seat's patrol and handover; no check, workflow, queue guard or patrol script reads it. - references/landing-operations.md: the subagent-down line names the marker in place (line count unchanged, 119 bytes). - scripts/pm/ensure-pm-labels.sh: one main-repo row, colour bfdadc, description under the 100-character cap. Every layer ruling record 5770886272 retired (gate role, --pair, double carrier, independence pair) stays retired. Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq Co-authored-by: Claude <noreply@anthropic.com>
The marker tracks "landing still owes an at-tier PASS on the current head"; a FAIL starts a patch round and the PR still owes that PASS, so only a PASS on file, a merge or a close clears it. Stated in the rule and in the roster comment so a seat does not clear on FAIL and re-hang at the next ACCEPT. Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Rendered in-seat by the ① Derived judgments
② Semver levelNo published package touched ( ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
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-reviewback 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 reviewrecord alone, and nothing in this diff reads the label.What changed — 3 files, +28 / -2
.claude/skills/pm-dispatch/references/contract-review.md.claude/skills/pm-dispatch/references/landing-operations.mdscripts/pm/ensure-pm-labels.shneeds:pack-smoke: colourbfdadc, a 95-character-d, and a comment block naming the label's readersThe rule as landed (contract-review.md :32-:36):
is:pr is:open label:needs:contract-review, and each seat's patrol and handover.landing-operations.md :13, before and after:
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.ymljobpr-size→scripts/pr-labels.mjs --size. It POSTs the computedsize/*label, then sends a targeted DELETE only for stalesize/*labels (planSizeWritesloops over the size family and nothing else). The job is skipped onlabeled/unlabeled/edited.pr-automation.ymljobauto-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.ymlare documentation,protocol:*, ci/cd, dependencies, tests and tooling; none is aneeds:*label.lint.ymlrunsnode scripts/pr-labels.mjs --self-test, which pins that no write plan emits a PUT. It also runsnode scripts/check-whole-set-label-write.mjs, which reds on a whole-setPUT /issues/{n}/labelsin any spelling anywhere in the repo. That verb (third-party labelers, and alabelsfield written through MCP) is what removed this label in the gate era.stale.yml(actions/stale) removes only its ownstalelabel. It closes a PR after 37 idle days, and a close is already a clear trigger in the rule.half-state-patrol.ymlrunssweep-closed-cards.mjs --write, which stripsPM_RESIDUE_LABELS(thepm:*state labels) from closed cards only.merge-queue-triage.ymladds labels to its anchor issues only.fleet-write.ymlruns only the ops a seat names.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-reviewon PR #19962, PR #19968, #19955 and #19953 is byobjectstack-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
22c9473c86packages/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.b05a88136dpackages/spec/src/ui/view.form.ts). Same as above.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, andscripts/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.mdand.claude/agents/os-dev.mdare untouched too; the claim excluded them.Acceptance notes
checkdefaults tousingper policy in the contract, but the write check ignores a USING-only policy when a sibling declarescheck— state the composition, or rule OR semantics #19953 PRs. The REST objects carry nopull_requestkey, so they are cards; the claim amendment already reads them that way.tenancy.organizationFieldfrom the authorable surface (#19054) #19618; 44 items in all, 4 of them open). The filter readsis:openand no script reads the label, so they are inert. Nothing in this PR touches them.## Contract reviewrecord. 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 incontract-review.md; andSKILL.mdis outside this PR's surface.Pending after merge — the seat's, not this PR's
ghrunsbash scripts/pm/ensure-pm-labels.sh --reconcileonce. The live object isedededwith an empty description (read 2026-09-24T14:46Z), and create-if-missing never changes an object that already exists.Tests — on
84f4580enode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; three-dot vs merge baseba77509ee) 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-skipsandpnpm check:pm-governed-prose. All 36 exit 0, each exit code captured before any pipe.--ranreconciliation: 「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-proseandpnpm check:nul-bytesare green.pnpm --filter @objectstack/lint run check:doc-formula-expressionsfirst exited 3 (PREREQUISITE NOT MET:@objectstack/formula/@objectstack/lintnot built). That run measured nothing. Afterpnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lintunderscripts/pm/os-verify-lock.sh(VERDICT command-exit 0), the rerun exited 0.bash -n scripts/pm/ensure-pm-labels.shexits 0. A fakeghon PATH ranensure-pm-labels.sh --reconcile, exit 0: the new row issuedlabel create needs:contract-review -R objectstack-ai/objectstack -c bfdadc -d …and the matchinglabel edit … --color bfdadc --description …with the same string.node scripts/pm/check-governed-merges.mjs --teston the three paths returns GOVERNED, Tier S (.claude/** ×2);scripts/pm/ensure-pm-labels.shis not on the register.pnpm lintand the CI-only families (the shard attestation, the test-completeness reader and the type-check lanes) are left to CI.Generated by Claude Code