feat(pm): H51 + H53 — the two contract-review carrier states no sweep reads, and the FAIL end-state H51 applies - #17138
Conversation
… label stroke, and the FAIL end-state it applies `needs:contract-review` is a dual-carrier gate whose verdict is a HANDOFF, and the handoff's second act is a LABEL STROKE. Twelve PRs took a verdict in one shift and no owning seat responded for 2-5 hours: the seats' sweeps read labels, not PR prose, so a verdict recorded only as a comment reached nobody. H31 is clean throughout — it compares the two carriers with each other, and both were correctly on. Two halves, both from the filing card: - `references/contract-review.md` 载体纪律 names the FAIL end-state, which the text never did (the PASS branch was precise, the FAIL branch said only 「转回相应工作态」 into a state model with no rework state). Paid in place by deleting the vague line it subsumes; ceiling untouched at 60/60. `lanes/director.md` 职责一 is rewritten in place to point at that single source rather than carry a second copy; 72/72 untouched. - H51 files a report-only row for an OPEN gated PR whose thread already holds a contract-review verdict for the CURRENT head, older than 60 minutes. Verdict- agnostic on purpose: both branches owe a stroke inside the window, and a row that parsed the verdict would be issuing one. A verdict naming an OLDER head is clean — the head moved, so the carrier is genuinely live again. The anchor is measured rather than quoted: three title dialects were live on the same day plus a director adoption record carrying the review verbatim, so the row reads the two things all four share — a `## Contract review` heading line and the head sha as a code span. Pinning the card's literal title would have been silent on two dialects while reporting a clean board. Reuses H48's `prCommentCache`, whose header reserved exactly this reader; `head.sha` and `labels` ride the open-PR list row, so head identity costs no request. The H52 self-test's `H51` reservation pin flips to its landed side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ
…m and no PR The gate rides an increment: 「PR 一存在即挂;报告先于 PR 到达则先挂卡侧」 says where it is hung, 「⛔ 不前瞻预挂」 says where it is not, and the invariant both serve is 「开着的载体恒 = 真实待审」. Fourteen open cards across two repos carried it with no `Claim:` comment and no PR at all — thirteen written the morning after a ruling restating the discipline merged into the text that seat reads. All were found by a director summon; no sweep could see them. H31 needs two carriers to compare, so with no PR it is INAPPLICABLE rather than clean, and its header deliberately declined the「gate on a card with no PR is premature」 shape because card-side-first is legal. The CLAIM leg is what lets this row read the half that premise excluded: a dev executing card-side-first has been dispatched, so its thread carries a claim. Claim present ⇒ silent. ⛔ Report-only, in H31's own register — the subject is a GATE, the row never removes a carrier and never asks a script to. Removal is a person's audited act. It BUYS a complete card thread per gated open card rather than reading a cache: the measured population is unassigned by construction and H2 buys a thread only for an ASSIGNED card, so a cache-only reading would report the whole target shape UNJUDGED while looking healthy. 9 gated of 599 open cards, single-page walks. H31's live `#10025` reading is pinned unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ
维护者速读 — PR #17138(#16836 + #16995 折叠,skills 席终稿,2026-09-09T12:38Z)改了什么 — 契约复核闸门 为什么改 — 一个班次里十二个 PR 拿到复核结论后 2 到 5 小时无人响应,最后是你先发现的(「已审 9 个 objectstack PR,为什么还是挂着待契约复审的 label」);席位的巡检读标签不读 PR 散文,只写成评论的结论谁也到不了。另一侧:裁定落地的第二天早上,分诊席又预挂了 13 个空载体,靠一次总监召唤才发现。两种都是「标签在说谎」而没有机器读得出来。你在 #16836 上的原话「立一张 domain:skills 卡(一句终态 + 一条 H 检查)」,本 PR 就是那一句加那一条,并把 #16995 的同形行折叠进来。 风险与代价(含回滚) — 两行都是 report-only,永不摘任何载体(摘除是有据可查的人的动作);每轮巡检多买少量评论线程(7 个挂标 PR 与 H48 共用缓存,9 张挂标卡各买一页),未判定的明写 UNJUDGED 不冒充干净。dev 实测复核评论标题在同一天有四种写法,所以 H51 认标题行 + head sha 两样,四种都钉了用例——写侧要不要收敛成一种是协议问题,本 PR 不动。回滚 = revert 两个 commit。 席位意见 — 接受。本席在分支头上复核:三行治理文本原文如上、宽度 120/110 B、两份账本零余量不动;被删句在 SKILL.md :644 有原文;两条谓词读过,新增行里无任何标签/assignee/PR 写入; 你要做的 — 看一眼 Generated by Claude Code |
Fixes #16836
Fixes #16995
Two report-only patrol rows on the
needs:contract-reviewgate, plus the one governed sentence that names the end-state the first of them tells a seat to apply. One file surface, two commits, one commit per card.What each card asked for, and what landed
#16836 deliverable 1 — the FAIL end-state (governed). The PASS branch was precise (「PASS ⇒ 同席同笔剥双载体,清标同笔留 provenance 评论引该 PASS」); the FAIL branch said only 「转回相应工作态(重派/返工)」 into a state model with no rework state, so nothing said what the label set looks like after a FAIL.
references/contract-review.md载体纪律 now names it:#16836 deliverable 2 — H51. An OPEN PR carrying
needs:contract-reviewwhose own thread already holds a contract-review verdict for the current head, older than 60 minutes. Verdict-agnostic on purpose: both branches owe a label stroke inside the window, and a row that parsed the verdict would be issuing one.#16995 half (1) — H53. An open card carrying the gate with no
Claim:comment and no open or merged PR delivering it: 「carrier without increment」. H31-style and report-only — it never removes a carrier. Half (2) is #17007 and is not addressed here.The one place the card's premise did not survive contact — H51's anchor
The card states the review-comment title as one literal format and calls the anchor structural on that basis. Measured against the live board it is not one format. Six contract-review comments across the seven gated open PRs, same day, in four shapes:
## Contract review (…, isolated seat) — PR #N @ \sha``## Contract review (clause ②) — … · head \sha``## Contract review at \TIER` — Verdict: …`**Director seat adoption record** …+ the review verbatim belowA regex pinned to the card's literal would have been silent on two of the three dialects plus every adoption record, while reporting a clean board — a phantom check on more than half of its own population. So the row reads the two things all four shapes share and nothing else: a
## Contract reviewheading line (line-anchored withm, H48'sMAINTAINER_BRIEF_MARKERregister — body-anchoring would drop the adoption record, and 「逐字采纳」 is one of the two legal acts on a subagent verdict) and the head sha as a code span, tested as a case-insensitive prefix of the PR'shead.sha. The loosenessmadmits — a comment merely quoting a heading — is answered by the second gate rather than by the regex: no head sha, no verdict on this head. Pinned as a case.Live behaviour on the board at the time of writing, both directions firing:
9b4c0af7f416904c1dceb0daba3d95a4f35157421b4ae4574d15856e3e8e05f5c96dfeb89a52eaaa4438H53 and the shape H31 deliberately declined
H31's header refuses 「gate label on a card with no PR carrier is premature」 because card-side-first is legal and expected (「报告先于 PR 到达则先挂卡侧」), and reporting it would flag the protocol's own prescribed sequence as a defect. The
Claim:leg is what lets H53 read the half that premise excluded, and it is not decoration: a dev executing card-side-first has been dispatched, so its thread carries a claim. Claim present ⇒ silent. Claim absent and no PR ⇒ nobody is executing anything. With no PR there is no carrier pair at all, so H31 here is not clean but inapplicable — the row says so in its own sentence, and H31's live#10025reading is pinned unchanged.Bands, and why neither row is
gateBoth are
state. Thegateband's own criterion is a gate whose absence reads as a green light, where 「被剥」 and 「从未挂过」 are indistinguishable — the reader H31/H35 are. Both new rows read the opposite direction: a carrier present and false. H51's nearest structural sibling is H48 (a verdict recorded, its second write missing, on an open PR, aged), which isstate. H53 is ⛔ notstalleither: whether a card-side carrier actually blocks dispatch is unmeasured — the filing thread says so and declines to grade on it — so the row does not claim the card is stopped.Cost — measured, and the decision stated either way
PM mechanism assumption B asked whether the sweep already holds the threads. Measured in the script: it does not, for either row.
prCommentCache— H48's, whose header reserved exactly this ("a future PR-comment reader finds a thread this row already bought here and pays nothing for it"). Governed and gated are independent properties, so the marginal cost is one issue-comment walk per gated open PR that H48 never visited: 7 gated of 16 open PRs, single-page walks. The head-identity leg costs nothing at all —head.shaandlabelsboth ride the open-PR list row already in hand.commentCachecannot serve it: H2 buys a thread only for an assigned card, and the measured population is unassigned by construction (a card nobody is executing has no assignee), so a cache-only reading would have reported the entire target shape UNJUDGED while looking perfectly healthy. Bounded by the gated population, not the open one: 9 gated of 599 open cards, single-page walks. Bought COMPLETE via H50's walk, because a full first page may hide the newestClaim:and reading "no claim" off a partial thread would invent the absence the row fires on.Both coverage clauses render unconditionally and both keep UNJUDGED apart from clean.
Ablation — each new pin shown to fail
Self-restoring script, absolute paths,
trap … EXIT INT TERM; every leg proves the mutation reached disk (target text count 1→0, injected text present, mutated blob hash ≠ the HEAD blob) and every restore leg is proven bygit diff HEADempty and the restored blob hash equal toHEAD's74441227870ada406df6e207f81c1cf562dd1cd1.H51 clean: a review comment on an OLDER head;a comment MENTIONING a review heading…; 2latestContractReviewOnHeadcases — 4 of 2976a verdict INSIDE the window is clean;one exactly AT it is clean too— 2 of 2976Claim:the same card once its thread carries a Claim:+ 3 boundary cases — 4 of 2976Part ofcases — 3 of 2976true,) occurs 80 times in the file, so its injection count is not a unique proof. The load-bearing half is — the target text went 1 → 0 and the blob hash moved.Gates
Re-derived on this diff with no paths (
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack) — identical set to the dispatch's lead, zero additions. All run, exit codes captured before any pipe:pnpm --filter @objectstack/lint run check:doc-formula-expressionsfirst exited 3 — PREREQUISITE NOT MET, which its own text calls "not a finding"; the two packages it needs were built under the shared verify lock (VERDICT command-exit 0) and it then exited 0. Repo-wide lint is a real reading rather than a declared narrowing:eslint . --no-inline-config --format jsonover its own population of 6,427 files, 0 messages, run at375c7961.Ceilings — paid by deletion, ⛔ no raise, ⛔ no re-wrap
contract-review.mdstays 60/60 andlanes/director.md72/72. The new sentence is paid by deleting the line it subsumes —contract-review.md:47「审计 FAIL 按状态机 label-flip 交回派发席补丁轮。」, which said the same thing without an end-state. ⛔ No merged bullets, ⛔ no cross-file move declared, ⛔ no ceiling touched. Every touched line is ≤120 bytes (new line: 120 B).On the director.md mirror — PM assumption C's explicit fork, answered with a measurement. The card asks for the sentence mirrored in
lanes/director.md职责一. A second full copy is what the ratchet's own cross-file-move rationale calls the defect ("a fact can be in the wrong FILE … read by that lane and missed by the other six"), and the director charter already points at this single source twice (:29 「归属与全链见../contract-review.md」, :71 「../contract-review.md为准」). So:36is rewritten in place — both halves of assumption C's disjunction at once, the in-place rewrite and the pointer:The 之二 enumeration and the ⛔ 空交接 line at
:37are untouched — the latter is where 「只贴复审评论不改标签 = 空交接」 already lives, alongsidestate-machine.md's 「交接即标签」, so this PR does not add a third copy of it.Governed terminal
.claude/**in the diff ⇒ draft PR, maintainer merge; ⛔ never ready, enqueued, auto-merged or approved by a seat. Verbatim (exit 3):skip-changesetmeasured, not assumed: 0 of the repo's package manifests carry afiles[]entry that could ship.claude/**orscripts/pm/**(positive control —packages/specfiles[]reads["dist","json-schema","liveness",…]).维护者速读(草稿)
改了什么 — 契约复核闸门
needs:contract-review上加两条只报告、不写标签的巡检行,外加一句治理文本。① H51:PR 还挂着闸门,而它自己的评论线程里已经有一份针对当前 head 的复核结论,超过 60 分钟没有任何标签动作。② H53:卡上挂着闸门,却既没有Claim:认领评论、也没有任何 PR —— 「有载体、无增量」。③contract-review.md补上 FAIL 的终态一句(此前只有 PASS 分支写清楚了)。为什么改 — 一个班次里十二个 PR 拿到了复核结论,2 到 5 小时无人响应,最后是维护者先发现的(「已审 9 个 objectstack PR,为什么还是挂着待契约复审的 label」)。席位的巡检读标签,不读 PR 散文,所以只写成评论的结论谁也到不了。另一侧:裁定落地的第二天早上,同一个分诊席又预挂了 13 个空载体 —— 靠一次总监召唤才发现。两种都是「标签在说谎」,而没有任何机器读得出来。
风险与代价(含回滚) — 风险低:两行都是 report-only,绝不摘任何载体(摘除是有据可查的人的动作)。代价是每轮巡检多买少量评论线程 —— 7 个挂标 PR(与 H48 共用缓存)加 9 张挂标卡,各一页。行数棘轮未抬,靠删掉被取代的那一行付账。回滚 = revert 两个 commit,巡检回到今天的状态,不影响任何已发布物。
席位意见 — (留空,待席位定稿)
你要做的 — 这是受管面(
.claude/**),按规矩只能您本人合并;席位不会转 ready、不入队、不挂 auto-merge。合并前值得看一眼的只有一处:contract-review.md新增的那句 FAIL 终态,和director.md由「转回相应工作态」改成指向该单源的那一行。Acceptance notes
contract-review.md描述档位保险丝处加一句:构建档位取自 harness 的model盖章或认领的Container & model:行,而 commit 的Co-Authored-Bytrailer 是会话署名常量、永远不是档位证据(总监席据该 trailer 扣过一个 PR(F7),后来撤回)。该留言自己写明「⛔ Not widening this card's two deliverables;if it does not fit … file it as its own docs-only card」,所以本 PR 没有把它做进来。查重已跑(RESTdomain:skills开卡 16 张 + 本地 grep,控制词contract-review命中 6 张 ⇒ 读数有效):无既有卡覆盖它。本 PR 以Fixes #16836关卡,该留言的落脚点会随之关闭 —— 正是 H52 记录过的「问题活得比卡长」形状。承接者:skills 席(本 fold 的派发席),contract-review.md降档保险丝段是落点。needs:contract-reviewwas pre-hung at triage again within hours of ruling A landing — 13 carriers on cards with no claim and no PR, and no patrol row reads "carrier without increment" #16995 half (2) (that is pm-dispatch: a seated session keeps the charter it loaded — a governed merge to SKILL.md or the seat's lane file is invisible to it until re-seated #17007), and [finding]domain:cli'spm:queuereads as 8 dispatchable cards and is 1 — five of them need a RULING, and the vocabulary already has a state for that #16688 / skills(pm-dispatch):pm:awaiting-maintainerhas a semantic contract and zero machine carrier — 49 of 55 cards in it are waiting for a JUDGEMENT, not for a hand, and nothing can tell the two apart #17017, which are serial behind this on the same file.H51was pinned as reserved atcheck-half-states.mjs:21840and that pin is evolved rather than deleted — it now readsHALF_STATE_FAMILY_BAND.H51 === 'state', doing the same job from the other side (the number is not free).H53verified free by grep overscripts/,.claude/and.github/.origin/main0da638cd(the dispatch readf836fb20; main moved before the worktree was cut). None of the newer commits touch the three files.🤖 Generated with Claude Code
https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ
Generated by Claude Code