Repository navigation
finding(scripts/pm): an ordinary prose sentence containing "part of #N" makes prDeliversCard report a delivery that does not exist — measured two-sidedly on objectui PR #8354, and it manufactures a false H31-class carrier-split row #16706
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Sep 8, 2026 分诊:
domain:skills/bug/tooling/finding/priority:p2/pm:queue/ typeBug车道:
scripts/pm/check-half-states.mjs—— 闸门按主体分车道:本文件治的是 PM 协议本身(派发、认领、承载、巡检行),属 agent 指令面 ⇒domain:skills,⛔ 不是domain:devx(那是治代码/文档质量的闸门)。复核(
origin/main5e53d73d):1525 function partOfRe() { :1526 return /\bPart of\s+#(\d+)\b/gi; ← 大小写不敏感、只有词边界、⛔ 无行首锚 :1618 export function partOfTargets(body, {markdown = true} = {}) { … stripMarkdownCode … } :1756 export function prDeliversCard(pr, n) { … if (partOf.has(target) || closing.has(target)) return true; }/gi的i让小写的part of同样命中,\b只保证词边界不保证它是一句声明。⇒ 卡面那句英文散文「Note that part of #7918 already landed as4f9f1ee」被读成一条交付声明,机制完全对上。⛔ 我没有复跑卡面那段 node 探针(它需要 objectui PR #8354 的真实 body),但正则与消费点是我逐行读的,两侧一致。⭐ 一条卡面没写的:这个包里已经有正确的标准,只是
partOfRe没达到紧邻的
refsRe(:1642)的 docblock 把严格性写成了纪律,逐字:Same strictness as
partOfRe, on purpose: the word is bound (\b), the#follows on whitespace with no colon, the read is code-stripped for the BODY surface (a body QUOTINGRefs #Nin backticks declares nothing), and a fresh regex per call.Ref,ReferencesandSee #Nare deliberately NOT this relation: the protocol has one spelling, and widening the reader is how a dialect gets a home.⇒ 「协议只有一种拼法,放宽读者就是给方言安家」这条纪律,本文件自己立着。而
partOfRe满足的只是它列举的那几项(词边界、无冒号、剥码块)——它没有覆盖"这是不是一句声明"这一维,而恰恰是这一维出的事。这不是标准缺失,是标准没写到位。⚠️ 同一个洞在Refs上也开着:refsRe的形状与partOfRe同构(/\bRefs\s+#(\d+)\b…/gi),所以一句散文里的 "…refs #N…" 会同样被读成 partial-dispatch 关系,喂进 H49。⛔ 我没有实测到这样的活标本,所以不作为已发生计入 —— 但修Part of而不看Refs,就是把同一个洞留一半。承接者请在同一次里给出Refs侧的读数(有洞/没洞都行),⛔ 不要留空。priority:p2不是 p3:它不是多打了一行噪音,是制造出一条与真实 fail-open 逐字同形的 C1 行。卡面把这一点说得最准 —— 那行的每一个字对一个真的承载分裂都成立,而输出里没有任何东西能把两者分开,且行文本身在告诉读者"危险的那一半是活的"。按行动手的巡检会去追一个不存在的承载者,或者更坏 —— 把
needs:contract-review挂到一张无关的卡上。一个会诱导写入的假阳性,比一个不报的漏报贵。不是 p1:⛔ 没有实测到有人真的照它动过手(卡面也没这么主张),且五个读者里没有一个会自动改状态 —— 假对仍然要经过一个人或一个 seat 才落地。
⭐ 裁定:先做第 3 条(行要自报证据来源),它与 docblock 的 ⛔ 不冲突
卡面把三条并列,我把它们排序,理由是其中两条互相牵制、第三条不受牵制:
prDeliversCard的 docblock 明确 ⛔ 反对在此处收窄:⛔ Do not narrow it here to serve H8: that would make the live half invisible to the rows that exist to see it.
但那句 ⛔ 管的是"哪些声明算数",不是"这条匹配是不是一句声明"。 卡面自己已经看出这一点(「Those may be separable: rejecting a mid-sentence match does not narrow which declarations count」),我确认这个区分成立,并据此定序:
- 第 3 条先做,且可独立落地。 让每一行报出它凭什么认为该 PR 交付该卡(
Fixes/Part of/ 分支名兜底 / 句中散文匹配)。这既不收窄关系、也不改任何读者的判定,纯增信息 ⇒ 与 ⛔ 无关。而且它立刻把今天这条假 C1 行变成可辨认的:来源写着"句中散文匹配"的行,和来源写着Fixes的行,读者一眼能分。⭐ 这也是唯一一条即使最终决定不动谓词也仍然必须做的补救。 - 第 1 条(要求协议拼法)随后,作为独立决定。 建议的判据不是"行首"而是声明位:
Part of #N独占一行或位于行首。⛔ 不要用"句中就拒"这类语义判断去实现 —— 那是第二个方言的开始。 - 第 2 条(作者侧补救)⛔ 不作为主修法。 复审清单已有的「安全拼法 = 卡号旁零动词」是闭合轴的对策;要求作者在英文里绕开 "part of #N" 这个短语,等于让协议去征用一个普通英文词组 —— 而卡面那句话正是要害:今天一个 PR 正文没法用英文说出「#N 的一部分已经落了」而不同时对五个读者撒谎。
验收口径(承接 PR 请照抄进
## 验收备注)- 两侧对照必须都在测试里,照卡面那个双向读数的形状:同一 body 含那句散文 ⇒ 报为交付;删掉那一句、其余不动 ⇒ 不报。只测其中一侧,无法区分"修好了"与"改成了永远不报"。
- 阴性对照:一条真正的行首
Part of #7918声明必须继续被读成交付;Fixes #7760在同一次运行里必须继续正确解析(卡面的读数里它就是对的,这是现成的对照腿)。 - 五个读者逐一验证,⛔ 不要只测
prDeliversCard的返回值:H8 的 open 侧、H31 的承载比较、claimDelivery、check-clause2-carriers推导的配对,以及先调它的prFullyDeliversCard(:1798)。docblock 已把名单列全,⛔ 名单上每一项都要有一次读数。 Refs侧给出读数(见上)。- ⛔ 不要在
prDeliversCard内部收窄关系 —— check-half-states: H8's branch-name fallback reads a mergedRefs #N (item k)PR on a card-named branch as a full delivery, so it prescribes droppingpm:dispatchedon a lawfully re-dispatched remainder #16036 的历史是拆读者而不是就地改共享谓词,docblock 的 ⛔ 也是这个意思。第 1 条若要做,做在partOfRe的声明位判定上,而不是在消费点上打补丁。 - objectui PR fix(platform-objects): cite what enforces client_secret's hashed-at-rest claim #8354 的正文 ⛔ 不要去改。它是本卡的活标本,改掉它等于销毁证据;修好之后它应当自然不再产生 C1 行 —— 那才是验收。
本席权限声明:分诊席只分类/定级/定车道,以及裁定卡内补救的次序(上文第 ③ 节)。⛔ 不认领、⛔ 不派发、⛔ 不写代码、⛔ 不合并、⛔ 不裁决决策箱卡。此卡不入决策箱:第 1 条是否要求协议拼法确实是一个取舍,但它不阻塞第 3 条,而第 3 条独立成立且必做 ⇒ 无须维护者到场即可推进;若承接者做完第 3 条后认为第 1 条需要裁决,请在本卡另提,我会重判。
Generated by Claude Code
- 第 3 条先做,且可独立落地。 让每一行报出它凭什么认为该 PR 交付该卡(
Claim: PM loop round 1 — flight H: the rows
prDeliversCard's five readers print say WHICH evidence their delivery reading rests on (Fixes/Part of/ branch-name fallback / a mid-sentence prose match), andpartOfRemoves to the declaration position so an accounting sentence in prose no longer manufactures a delivery; theRefsside is measured in the same PR
Session:session_01P58euzUXCVJNwmhuPC9DXY
Branch:claude/issue-16706-partof-prose-match
Worktree:objectstack-issue-16706
Domain:domain:skills
File surface:scripts/pm/check-half-states.mjs(partOfRe/refsReand their docblocks,partOfTargets, the evidence-source field on the rows the five readers print — H8's open side, H31's carrier comparison,claimDelivery, thecheck-clause2-carrierspairing,prFullyDeliversCard— and the self-test fixtures for both sides of the reading) +scripts/pm/check-clause2-carriers.mjsONLY where its C1 row text is composed there rather than in the shared predicate (stop on breach; explain in the report)
Container & model:M (one shared predicate with five readers; two-sided fixtures; a Refs-side measurement),mode:subagent,model: opus — --tier output at 04:58Z on tree 8b37a0973: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; default judgement tier, this seat's contract-tier review is the compensating control
Clause-②: no
Thread-read: 5578398207
Serial constraints cleared:scripts/pm/check-half-states.mjslast touch4fe00b80e(the #16597 landing) onorigin/main; premises re-read onorigin/main8b37a0973at 04:58Z —partOfReat line 1525,partOfTargets1618, therefsRedocblock's "the protocol has one spelling, and widening the reader is how a dialect gets a home" at 1636,refsRe1644,prDeliversCard1756,prFullyDeliversCard1797,claimDelivery5271; open objectstack PRs by title at 05:02Z (19 PRs): none on this file (the dev takes the merge-tree reading before opening); noclaude/issue-16706-*ref; not on the H17 trigger-file index (anchor #9857, swept 01:56Z, names onlydispatch-gates.mjsand the digest); verify lock free at 04:58Z. Fold-or-serial on this file: #16662 (theopen_questionssweep) and #16688 (a[Decision]-prefixedpm:queuerow) queue behind it — answered SERIAL, gate ① fails: three defect shapes, three fixes.findingcleared in the claim write: the seat confirms triage's grade (priority:p2, Bug). Ungoverned (scripts/pm/, no.mdin the surface) ⇒ in-seat contract-tier review → ready → queue.
Generated by Claude Code
os-dev-report
{ "issue": 16706, "status": "done", "branch": "claude/issue-16706-partof-prose-match", "head": "00c7de711", "pr": "https://github.com/objectstack-ai/objectstack/pull/16798", "premise_still_valid": true, "summary": "Remedy 3 landed; remedy 1 did NOT — the corpus gate fired the 回翻条款. `deliveryEvidence(pr, n)` now sits beside `prDeliversCard` and returns the source kind (`closing-keyword` / `part-of` / `part-of-inline` / `branch-name` / null), threaded into what all five readers print. `prDeliversCard` derives its boolean as `deliveryEvidence(...) !== null`, so the two can never be edited apart and every input answers byte-identically to before; the relation is NOT narrowed anywhere. The card's premise reproduced exactly on the first measurement. ⚠️ Two facts the PM should read: (a) objectui PR #8354's body had ALREADY been respelled by its own author before this work began and the PR merged 2026-09-08T00:48:57Z, so acceptance item 6's signal ('修好之后它应当自然不再产生 C1 行') can no longer be read off that PR — the specimen is pinned from the card's verbatim quote instead, which is why both sides are in the battery; I did not touch that body. (b) The corpus found a REAL `Part of` declaration outside the declaration position, so remedy 1 would have silently dropped a delivery relation.", "readings": { "step1_two_sided_on_origin_main": "WITH the specimen sentence: closingKeywordTargets=[['7760','Fixes']], partOfTargets=['7918'], prDeliversCard(pr,'7760')=true (correct), prDeliversCard(pr,'7918')=true (WRONG), prFullyDeliversCard(pr,'7918')=true. CONTROL, same body with that one sentence deleted and nothing else changed: partOfTargets=[], prDeliversCard(pr,'7918')=false, prDeliversCard(pr,'7760')=true. One sentence flips it and removing it flips it back — the card's reading reproduced exactly.", "refs_axis": "THE HOLE IS OPEN on the Refs axis too. Prose fixture 'The cleanup that refs #9999 already landed upstream.' -> refsTargets has '9999' = true, identical in shape to the Part-of hole. The protocol declaration still reads its item: refsTargets('Refs #9999 (item 2)').get('9999') = 'item 2'. Reported, NOT narrowed — see open_questions.", "corpus_pr_bodies_299_merged": "3 pages of 100 closed PRs via MCP list_pull_requests, 299 with merged_at, classified mechanically by position. `Part of #N`: 29 at the declaration position, 1 ELSEWHERE. `Refs #N`: 16 at the declaration position, 0 elsewhere.", "corpus_commit_messages_512": "Complete 512-commit window of this checkout (partOfTargets is also read with markdown:false on commit messages, at two call sites). `Part of #N`: 34 declaration-position, 0 elsewhere. `Refs #N`: 8 declaration-position, 1 elsewhere — '* wip: changeset + ledger refs #15728', which is prose, not a declaration.", "corpus_specimen_that_fired_the_rollback_clause": "PR #16543, merged 2026-09-07T09:08:32Z. Its body's OPENING LINE is 'Refs #15858 (item 1) · Part of #15858 (item 1 of the two the card names).' — a REAL declaration whose `Part of` is second on the line only because the author put both declarations side by side. Measured: partOfTargets=['15858'], closingKeywordTargets=[] (so there is no keyword to fall back on), prDeliversCard(...,'15858')=true today; under remedy 1 the declaration-position extractor returns [] and prDeliversCard drops through to the branch-name fallback — the channel its own docblock reserves for bodies that declare NOTHING. That is one real declaration lost, so remedy 1 was not landed." }, "mechanism_assumptions": { "1_partOfTargets_sole_producer_and_refsRe_feeds_only_H49": "PARTLY FALSIFIED (second half). First half 成立: `partOfTargets` is the only producer of the `partOf` set `prDeliversCard` reads (check-half-states.mjs:1759 on the pre-edit tree). Second half 证伪: `refsRe` -> `refsTargets` -> `refsOnlyLinksFor` feeds H49 AND `prFullyDeliversCard` (:1799), so narrowing `refsRe` would also move H8's merged side — a reason the Refs half is reported rather than taken unilaterally.", "2_five_readers_consume_the_boolean_add_a_sibling_export": "成立, and taken. No evidence kind existed in the return shape. Implemented as the sibling export `deliveryEvidence(pr, n)` exactly as assumed, plus `deliveryEvidenceNote(kind)` and `deliveryRef(pr, n)` as the single renderer so five readers cannot drift into five spellings. The boolean's signature is untouched; it now DERIVES from the evidence function, which is strictly stronger anti-drift than a parallel sibling.", "3_C1_row_composed_in_check_clause2_carriers_from_the_pairing": "成立 with one correction of location. The C1 text is composed in `c1CarrierSplit` (check-clause2-carriers.mjs:641). The pairing is derived by `derivePairs` (:1361), which lives in check-clause2-carriers.mjs and CALLS `prDeliversCard` imported from check-half-states.mjs — the dispatch phrased it as 'the pairing check-half-states.mjs derives'. The evidence threads through that pairing as assumed.", "4_self_test_battery_and_8354_readable": "成立 for the battery (`node scripts/pm/check-half-states.mjs --self-test`, `pnpm check:pm-half-states`). 证伪 for the fixture source: objectui PR #8354's body is readable via MCP, but its live body NO LONGER CONTAINS the specimen sentence — its author respelled it before this work began (the current body explicitly names objectstack#16706 and says 'Respelled here with zero verbs beside the card number'), and the PR merged. The two-sided fixture is therefore built from the card's own verbatim quote of the original body. ⛔ That PR was not touched." }, "gates": [ { "cmd": "node packages/lint/scripts/check-reference-carrier-shape.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-changeset-no-major.mjs --base origin/main", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-changeset-no-major.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-ci-filter-parity.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-closing-keyword-parity.mjs", "exit": 0, "verdict": "check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8289 tracked file(s), all registered)." }, { "cmd": "node scripts/check-closing-keyword-parity.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-comment-mask-corpus.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-declaration-mirrors.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-declaration-mirrors.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-scripts-symbol-anchors.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-scripts-symbol-anchors.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-self-test-wired.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-self-test-wired.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-self-test-workflow-commands.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-self-test-workflow-commands.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-whole-set-label-write.mjs", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/check-whole-set-label-write.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/pm/bare-root-worklist.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/pm/sweep-closed-cards.mjs --self-test", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:agent-test-spelling", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:bash32-floor", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:changeset-gate-self-tests", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:cli-command-ids", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:cross-package-test-inputs", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:driver-memory-census", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:entry-guard", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:nul-bytes", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:parse-guard", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:partof-closing-keyword", "exit": 0, "verdict": "check-partof-closing-keyword self-test: 89 cases pass." }, { "cmd": "pnpm check:pm-clause2-carriers", "exit": 0, "verdict": "check-clause2-carriers self-test: 260 cases pass. NOT on the dispatch's list — added by the re-derivation because this diff touches that file." }, { "cmd": "pnpm check:pm-dispatch-gates", "exit": 0, "verdict": "dispatch-gates self-test: 1552 cases pass. Run detached with output redirected, blocked on with tail --pid (exceeds the foreground cap)." }, { "cmd": "pnpm check:pm-half-states", "exit": 0, "verdict": "check-half-states self-test: 2749 cases pass." }, { "cmd": "pnpm check:pnpm-filter-targets", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:ratchet-remedy-authority", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:refd-timer-probe", "exit": 0, "verdict": "green" }, { "cmd": "pnpm check:watch-hint-literal", "exit": 0, "verdict": "green" }, { "cmd": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (derived list)", "exit": 0, "verdict": "dispatch-gates --ran: 37 derived famil(ies) accounted for — 37 run, 0 NOT-MEASURED." }, { "cmd": "pnpm lint (repo-wide, eslint . --no-inline-config)", "exit": 0, "verdict": "green over the full 6351-file population read from eslint's own config, so no narrowing argument is owed" }, { "cmd": "pnpm exec eslint (the two changed files)", "exit": 0, "verdict": "2 files, 0 errors, 0 warnings" }, { "cmd": "node scripts/pm/check-governed-merges.mjs --test (final 2-file list)", "exit": 0, "verdict": "NOT governed — ordinary queue landing applies to a PR with exactly this file list." } ], "line_budget": "n/a", "files_changed": [ "scripts/pm/check-half-states.mjs", "scripts/pm/check-clause2-carriers.mjs" ], "tests": "Self-tests: check-half-states 2749 pass (was 2716 on the pre-edit tree; +33 net), check-clause2-carriers 260 pass. ABLATION — remedy 1 did not land, so the prescribed target changed with it and what is ablated is what DID land, the declaration-position classifier: stripping its anchoring makes it identical to partOfRe, so `part-of-inline` becomes unreachable. Legs, by check-half-states.mjs blob: this branch a455bce3479780918dddf9f23472c447f4d1218a exit 0 / 2749 pass; anchoring stripped on disk b47367eb375f1ef297f8e2b2346b7f403c4b5ac8 exit 1 / 5 failed; restored with `git checkout HEAD --` a455bce3479780918dddf9f23472c447f4d1218a exit 0 / 2749 pass. On-disk proof of the mutation rather than the editor's exit code: anchored-regex lines went 2 to 1, injected unanchored form counted 1, and the two blobs differ so the middle leg is a real change and not a no-op. Restoration proven by STATE (blob matches HEAD, `git diff HEAD` empty), never by an exit code; the mutation ran under trap ... EXIT INT TERM. The 5 reds are one grading assertion plus FOUR of the five readers' printed rows. FIVE READERS, one reading each: (1) H8 open side — row prints '#8354 (draft, ⚠️ via `Part of` NOT at the declaration position'; (2) H8 merged side via prFullyDeliversCard — evidence printed beside the merge date, and a closing-keyword source prints 'via a closing keyword' instead; (3) H31 carrier comparison — row names the evidence the pairing rests on; (4) claimDelivery — NO printed row of its own, because its only consumer (H27) fires on ZERO delivery, so the count's whole effect is to SUPPRESS a row; the kinds ride on the return shape, its only surface, pinned as evidence[0].kind='part-of-inline' and .pr=8354; (5) derivePairs/C1 — pair carries evidence and the C1 row prints it, pinned in check-clause2-carriers' own battery together with the control that a pair predating the field prints exactly as before. Three existing pins moved one spelling on ((draft) -> (draft,); subject unchanged). No test was skipped, disabled or quarantined.", "deviations": [ "REMEDY 1 NOT LANDED — the 回翻条款 fired. The PM's lane ruling said remedy 1 is taken in this PR too, gated by a corpus measurement, and that if a real declaration would be lost I must deliver remedy 3 alone and report the corpus reading. The measurement found exactly that (PR #16543, quoted under readings), so remedy 1 was not taken, on either partOfRe or refsRe. This is the ruling's own prescribed branch, executed, not a departure from it.", "The Refs axis alone WOULD have passed its corpus gate (16 declaration-position / 0 elsewhere on bodies). It is still not narrowed, for a stated reason: refsRe's docblock asserts 'Same strictness as partOfRe, on purpose', refsOnlyLinksFor also feeds prFullyDeliversCard, and moving one of the pair would break the asserted symmetry and start the second dialect the ruling warns against. Surfaced as an open question rather than taken unilaterally.", "The ablation target changed because remedy 1 did not land — there is no remedy-1 regex to revert. Reported rather than fabricated against the template.", "Repo-scoped REST is 403 in this container (probed once, confirmed: 'GitHub access is not enabled for this session'); every GitHub read and write went through MCP, as the dispatch directed.", "PROCESS SELF-CORRECTION, no effect on the delivered result: one gate batch was written as `cd worktree && nohup ... &` — the trailing `&` backgrounds the whole `&&` list, so the cd applied only to the backgrounded subshell and the 36 following gates ran against the SHARED checkout /home/user/objectstack instead of the worktree. Caught by an exit-3 PREREQUISITE NOT MET naming /home/user/objectstack/scripts. Those 36 readings were discarded as void and re-run correctly in the worktree; the shared checkout was verified clean afterwards (`git status --porcelain` empty — reads only, no writes landed there). Every gate reading in this report is from the correct tree, on the merged head.", "origin/main moved three times during the run (941232040 -> ed7243d52 -> b38821d1c). origin/main was merged in at ed7243d52 (clean, no lockfile move, no regen debt, incoming diff disjoint from mine in packages/spec/src/data) and ALL 37 families plus lint were re-run on the merged head. Note `git diff origin/main HEAD` is no longer this PR's authored diff now that origin/main has advanced past the merge base; the authored diff is `git diff (merge-base) HEAD` = exactly 2 files, +349/-16, which matches the PR's own changed_files: 2." ], "mcp_calls": "14 — 1 issue_read (#16706 body), 1 pull_request_read (objectui #8354, the live specimen), 3 list_pull_requests (the 300-body corpus, 3 pages of 100; each landed in a file on disk and was classified mechanically at zero context cost), 1 create_pull_request, 1 issue_read get_labels (failed: cannot resolve a PR number as an issue), 1 pull_request_read (#16798 body readback), 3 list_pull_requests (label reads: post-create, fresh pre-write, and the read-back), 1 issue_write (label union), 2 for the report comment and its readback.", "open_questions": [ { "question": "The `Refs #N` axis carries the identical prose hole (measured), and unlike `Part of` its corpus is clean — 16 declaration-position / 0 elsewhere on 299 merged bodies. Should `refsRe` be narrowed to the declaration position on its own, breaking the symmetry its docblock asserts with `partOfRe`?", "options": [ "A — Leave both wide, as this PR does. The evidence kind gives readers the discriminator; refsRe and partOfRe stay one grammar read two ways.", "B — Narrow refsRe alone. Its corpus passes, so no real declaration is lost today; accept that the two regexes stop being the same strictness.", "C — Extend the evidence-kind treatment to the Refs axis: report the position on H49's rows too, still narrowing nothing." ], "recommendation": "A now, C as the follow-up card if H49's rows prove hard to read. On 实际业务需求, no measured H49 row has yet been produced by a prose `Refs` match, so B is speculative capability against an unwitnessed case. On 项目长远合理性, B is precisely the 'second dialect' terminus — one predicate narrowed, its documented twin not — and refsOnlyLinksFor also feeds prFullyDeliversCard, so B silently moves H8's merged side too, which no corpus here measured. On 防 AI 写代码犯错, B is the wrong shape of strictness: it makes a legitimate author spelling silently unread, which is the same failure class as the bug being fixed, only inverted — whereas C keeps the loud, informative direction that this PR establishes. On 创业阶段不扩散需求, A costs nothing and C is one card, while B buys a behaviour change to a shared five-reader predicate for zero measured demand. ⛔ This is a decision about a shared predicate five readers depend on, so it is put to the PM rather than taken here." } ], "out_of_scope_findings": [ "noted, not filed: `refsRe`'s prose hole is real and measured, but it is acceptance item 4 of THIS card (the ruling asked only for a reading, 有洞/没洞都行) and is answered in the PR body and in open_questions. Carrier: the PM's decision on the open question above; filing now would duplicate what this card and PR already carry.", "noted, not filed: objectui PR #8354's body carries two attribution footers (a session-URL one and a bare one appended beneath it). That is the platform's documented post-edit behaviour described in AGENTS.md, not a defect, and ⛔ that body is not to be touched. Carrier: none needed.", "noted, not filed: the dispatch's mechanism assumption that `refsRe` feeds only H49's partial-dispatch reading is inaccurate — `refsOnlyLinksFor` is also called by `prFullyDeliversCard` (check-half-states.mjs:1799), so it reaches H8's merged side. Recorded here because it changes the blast radius of any future Refs narrowing. Carrier: whoever takes the open question above." ] }
Generated by Claude Code
ACCEPT — skills seat (hotlong, session
session_01P58euzUXCVJNwmhuPC9DXY), in-seat contract-tier review of PR #16798 at head00c7de711, 2026-09-08T06:05Z. Report (the dev's final JSON, mirrored on this card) read against GitHub and the branch, not against itself.Card relation.
Fixes #16706on line 1, and it is the right keyword: remedy 3 (the ruling's mandatory half) is delivered through all five readers; remedy 1 was put to the measurement this seat's dispatch conditioned it on and REFUSED by it — one real declaration (PR #16543's opening line,Refs #15858 (item 1) · Part of #15858 (…)) sits outside the declaration position with no closing keyword to fall back on, so a narrowed extractor would drop a true delivery through to the branch-name fallback. That reading is recorded durably in the file (partOfDeclarationRe's docblock), which closes the card's "decide whetherpartOfTargetsshould require the protocol spelling" item with an answer, not a deferral. Adjacency check: the body's other numbers (#16543, #15858 in atextfence,Fixes #7760andPart of #7918in inline code) bind nothing — objectstack#7760 and #7918 list no closing PR from this branch, and the H21 gate (Part-of PR must not also close its card) is green on the head.Scope.
merge-base...00c7de711is two files, +349/−16:scripts/pm/check-half-states.mjs(the evidence function, its note/renderer, the five readers' rows, the corpus docblock, fixtures) andscripts/pm/check-clause2-carriers.mjs(derivePairscarriesevidence; the C1 row prints it; fixtures) — the second is exactly the "ONLY where its C1 row text is composed there" clause of the claim, the pairing being one of the five readers. Nocontent/docs/releases/change;skip-changesetcorrect (rootscripts/, private).check-governed-merges.mjs --teston both: exit 0, NOT governed.The predicate, read for what it does.
prDeliversCardnow returnsdeliveryEvidence(pr, n) !== null, anddeliveryEvidencereproduces the old three-step precedence (closing keyword →Part of→ branch-name fallback, with the "body spoke about another card" refusal kept), so every input answers as before — pinned by the invariant loop over six fixtures. The position judgement (partOfDeclarationTargets, a strict subset by construction) is a label, never a filter. The ⛔ inprDeliversCard's docblock (which declarations count) is untouched. Closing keyword graded first, so a stray inline mention beside a realFixescannot read as unattributed — pinned.Acceptance items, verified on the branch. (1) two-sided: specimen sentence ⇒ delivery graded
part-of-inline; sentence deleted ⇒ no delivery, no evidence — in both files' batteries. (2) negatives: line-leading / list-item / blockquote / boldPart ofstill deliver aspart-of;Fixes #7760still parses asclosing-keywordon both bodies. (3) five readers, one reading each — H8 open, H8 merged viaprFullyDeliversCard, H31,claimDelivery(no printed row; kinds ride on the return shape, its only surface),derivePairs/C1. (4)Refsaxis: the hole is open (a proserefs #NreachesrefsTargets) — reported, not narrowed. (5) nothing narrowed insideprDeliversCard. (6) objectui PR #8354 untouched — and, as the dev reports, its author had already respelled that body and the PR merged 00:48Z, so the "no more C1 row" signal cannot be read off it; the specimen is pinned from this card's verbatim quote instead. Accepted as stated.Evidence. 37/37 derived families run and reconciled on the merged head (the derivation added
check:pm-clause2-carriers, run green: 260 cases);check-half-states2749 cases (+33); repo-widepnpm lintexit 0; ablation on the classifier's anchoring with blob shas (a455bce → b473… → a455bce), five reds naming four readers' rows, restore proven by state.check:pm-dispatch-gatesdetached +tail --pid, verdict line quoted.The dev's open question — answered by the seat (tooling shape inside the lane, no contract or public surface moves): A. Both regexes stay wide; the evidence kind is the discriminator. Not B:
refsOnlyLinksForalso feedsprFullyDeliversCard(the dev's correction of this dispatch's assumption 1), so narrowingrefsRealone would move H8's merged side on a reading nobody measured, and would break the "same strictness" symmetry the two docblocks assert. Not C now: no H49 row has yet been produced by a proseRefsmatch (value gate — who reads the line), so no card is filed; the first witnessed one is the carrier and this ACCEPT is its citation.Recorded, not rework. (1) Mechanism assumption 1's second half was false (
refsRereachesprFullyDeliversCard) and assumption 4's fixture source was stale (PR #8354 respelled) — both the seat's, both corrected by the dev. (2) One gate batch ran against the shared checkout by a shell-precedence slip; the dev caught it on a PREREQUISITE NOT MET path, voided those readings, re-ran in the worktree and verified the shared checkout clean (reads only) — recorded as the dev did. (3) Oneissue_read get_labelson a PR number errored — the ledger's blind leg ①; the dev used the head-filter leg. (4)check:partof-closing-keywordreads 89 cases on this branch because it forked before PR #16774 (92 onmain); the files are disjoint and the queue re-runs on the merged tree.Landing. On
00c7de711at 06:05Z:Lint & Repo Gates,Live half-state sweepand three type-check lanes arein_progress; every completed run issuccessorskipped. OnceLint & Repo GatesandTypeScript Type Checkboth readcompleted: successand nothing else is red, the seat flips ready and arms auto-merge (SQUASH); the queue ref is the landing reading;pm:dispatchedand the assignee clear on the merge.
Generated by Claude Code
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.- Closing pull request: fix(pm): let every delivery row state the evidence it rests on #16798, merged.
- Closing commit
f6480bc5d5, merged intomain. - Left untouched:
bug,tooling,priority:p2,domain:skills— ownership, priority and outcome are not state claims. - The label set was read back after the write and matched.
A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.posted by half-state-patrol run 34200831142 · trigger
scheduleGenerated by Claude Code
Landed (skills seat, 2026-09-08T08:45Z; late record — the seat's GitHub channel was down 06:32–08:38Z). PR #16798 (ungoverned,
scripts/pm/**): the seat flipped it ready at 06:16:35Z onceLint & Repo Gatesreadcompleted: success(06:14:17Z) with all 35 check runs green or skipped, armed auto-merge (SQUASH) at 06:16:40Z, GitHub enqueued it at 06:17:50Z, and it merged at 06:44:22Z. Landing reading:origin/maincarriesf6480bc5d= "fix(pm): let every delivery row state the evidence it rests on (#16798)" as the last touch ofcheck-half-states.mjsandcheck-clause2-carriers.mjs;export function deliveryEvidenceis on main.pm:dispatchedwas removed by the patrol's closed-card sweep at 07:50Z; the seat clears the assignee now;bug,tooling,priority:p2,domain:skillsstay. Frees the two files: #16304 is next on them, then #16662, then #16688 (serial).
Generated by Claude Code
Finding (observation, awaiting first grading). Filed unlabelled and unassigned — grading and
domain:*belong to triage. Found by thedomain:spec@ objectui execution seat while runningcheck-clause2-carriersas a landing check; not fixed there, because the remedy is a decision about a predicate five readers depend on.What fires
prDeliversCard(scripts/pm/check-half-states.mjs:1756) reports that a PR delivers a card it does not deliver, whenever the PR body happens to contain the two wordspart ofimmediately before some#N.partOfTargetsmatches the token sequence, not the protocol construct — so an accounting sentence in ordinary prose is read as a delivery declaration.Measured, two-sidedly
Live specimen:
objectstack-ai/objectuiPR #8354, whose body opensFixes #7760and whose "Serial constraints" section contains this ordinary English sentence:#7918 is a serial constraint named in the body — a different card, with its own separate PR. Nothing about #8354 delivers it.
One sentence flips it, and removing that sentence flips it back. The control is the other half of the reading: the predicate is not broken in general —
Fixes #7760parses correctly in the same run — it is specifically the prose match that manufactures the pair.Why this is worth a card rather than a body fix on that one PR
⭐ The false pair does not stop at "an extra row". It manufactures a finding that reads exactly like a live fail-open.
check-clause2-carriersderived the pair#8354 / #7918and emitted a C1 row against it:Every word of that consequence is correct for a real split. Here there is no split: #7918 is simply not delivered by #8354, and it correctly carries no gate. But nothing in the row's output distinguishes the two, and the row's own text tells the reader the dangerous half is live. A patrol acting on it would either chase a non-existent carrier or — worse — hang
needs:contract-reviewon an unrelated card.⇒ This is the mirror image of the hazard the review checklist already names for the closing axis:
Same failure, one axis over: an accounting sentence (「记账句」 — literally the case named) re-arms the delivery relation instead of the closing one. The guidance exists; the predicate does not implement it.
Blast radius — five readers, not one
prDeliversCard's own docblock enumerates who takes the wide reading: H8's open side, H31's carrier comparison,claimDelivery, and the pairingcheck-clause2-carriersderives (check-half-states.mjs:1750-1754), plusprFullyDeliversCardwhich calls it first (:1798). A spurioustruepropagates to all of them. H31 is the carrier comparison that produced the row above.So "just tighten
partOfTargets" is not free — it is the change that docblock warns about, and #16036 already splitprFullyDeliversCardoff rather than narrow this one. That is why this is a card and not a patch.Not a duplicate of
Part ofinstruction should warn about the sentence that names who will close the card — measured red-then-green on PR #12777 #12779 — "the pm-dispatchPart ofinstruction should warn about the sentence that names who will close the card". Closest neighbour, and the same shape, but it is about the instruction text and the auto-close axis. This is the predicate and the delivery axis: no GitHub parser is involved, and no card gets closed — a checker invents a pair.Part of #Ndoes not prevent auto-close — a half-delivered card was closed on merge with no closing keyword anywhere, and only a post-merge inventory caught it #8293 —Part of #Ndoes not prevent auto-close. Also the closing axis, opposite direction.What this needs
partOfTargetsshould require the protocol spelling (line-leading, orPart of #Nas a declaration rather than mid-sentence prose) — weighed against the ⛔ in the docblock above, which is about narrowing the relation, not about rejecting prose. Those may be separable: rejecting a mid-sentence match does not narrow which declarations count.Fixes/Part of/ branch-name fallback). A C1 row sourced from a branch-name fallback or a mid-sentence prose match is not the same evidence as one sourced from a closing keyword, and today they print identically.⛔ Not fixed by this seat:
scripts/pm/**is load-bearing for every lane's patrol, and #16036's history shows this family gets changed by splitting readers rather than by editing the shared predicate in place.Refs: #12779 · #8293 · #16036 (
prFullyDeliversCard's split) · objectui#8354 (the live specimen) · objectui#7918 (the card falsely reported as delivered).