Skip to content

skills(pm-dispatch): the contract-review PASS leaves its provenance comment on the PR, not only the card - #17856

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13417-pass-pr-pointer
Sep 12, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-13417-pass-pr-pointer

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #13417

A contract-review PASS left zero trace on the PR itself: the gate label cleared on both
carriers, get_reviews answering [], and the verdict living on the card only — so a
legitimate un-park was indistinguishable from an intruder read from the PR side. The Phase-3
rewrite of references/contract-review.md already requires a provenance comment at every
clear, and C6 of check-clause2-carriers.mjs already reads the review record on the PR or
the card. What was still open is exactly what the card measured: the provenance comment's
carrier was unpinned, so a seat recording both the record and the provenance comment on the
card side left the PR as blind as the incident found it.

The change — one line, references/contract-review.md :38

Before (119 B, origin/main 9c577c18a):

- 清标即落地:PASS ⇒ 同席同笔剥双载体;凡清标同笔留 provenance 评论,引记录 id 与所判 head。

After (119 B, this PR):

- 清标即落地:PASS ⇒ 同席同笔剥双载体;清标同笔落 PR provenance 评论,引记录 id 与所判 head。

The provenance comment is now pinned to the PR — the carrier that gets un-parked — while
still citing the record id and the judged head. The card-side one-line verdict is untouched
(载体纪律不动), :28 「复核记录 = 一条评论落 PR 或卡」 is untouched, and no script changed.

Measurements. 60 lines before, 60 lines after (ceiling 60, headroom 0 —
scripts/pm/check-skill-line-ratchet.mjs); the line is 119 B, one byte under the before-line's
own width and under the gate's MAX_LINE_BYTES = 120. No ceiling raise, no second line, no
density payment inside the file, and no cross-file move.

Wording adjusted from the dispatch's suggested shape, because the suggested shape does not
fit.
「清标同笔在 PR 上留 provenance 评论」 measures 126 B, over the 120-byte rule; the
cheapest spelling of that pin with 「在 … 留」 is 123 B. Two bytes-level adjustments bring it to
119 B without dropping a rule: the emphatic 「凡」 goes (its subject 「清标」 stays, so the clause
still speaks about every clear, not only this PASS stroke), and the pin is spelled with the
file's own verb for a comment landing on a carrier — 「落 PR」, as at :28
「一条评论落 PR 或卡」 — instead of 「在 PR 上留」. Every rule token survives: 同席, 同笔,
剥双载体, 清标, the PR pin, provenance 评论, 引记录 id, 所判 head.

Rulings this edit stands on

Acceptance reading

After this text, a PASS clear that follows it leaves the PR thread carrying a comment that
names the record id and the head it judged. get_reviews answering [] therefore no longer
reads as "no review happened" from the PR side: the observer's own reasonable read now lands on
the pointer instead of on an unexplained state change. Anchored evidence: the pointer wording is
absent on origin/main (git grep -n -i -E 'PASS at head|PR 上|落 PR|指针|pointer' over this
file → 0 hits, lit control git grep -c '清标' → 4) and present on this head (1 hit at :38).

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
in the worktree (1 path, no paths passed by hand) — 14 families, all run, all exit 0,
reconciled with --ran carrying each exit code: "14 derived famil(ies) accounted for — 14 run,
0 NOT-MEASURED (a DERIVED zero — all 14 recorded an exit code and none of them is 3)".

command exit
node scripts/check-closing-keyword-parity.mjs (and --self-test) 0 / 0
node scripts/check-comment-mask-corpus.mjs 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 (first run exit 3, PREREQUISITE NOT MET — not a measurement; @objectstack/formula + @objectstack/lint built under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, then re-run)
pnpm check:agent-test-spelling 0
pnpm check:doc-authoring 0
pnpm check:driver-memory-census 0
pnpm check:nul-bytes 0
pnpm check:pm-governed-merges 0
pnpm check:pm-skill-id-lint 0
pnpm check:pm-skill-ratchet 0
pnpm check:refd-timer-probe 0
pnpm check:skill-frame-sync 0
pnpm check:watch-hint-literal 0

Gate verdict lines quoted rather than a bare $? (exit captured before any pipe):

  • ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/contract-review.md is 60 lines (ceiling 60; headroom 0).
  • ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/contract-review.md: widest table row is 0 bytes (pin 0; headroom 0).
  • check-nul-bytes: OK (scanned 8483 text file(s) ... no raw ASCII control bytes).
  • ✓ check-skill-id-lint: 27 file(s) clean (pattern for three-plus-digit card ids).

The ratchet family was re-run after the final commit, on b54e3954e with a clean worktree:
exit 0, same two lines. Control reading, no script changed:
node scripts/pm/check-clause2-carriers.mjs --self-test → exit 0, "546 cases pass".

No changeset: the diff is .claude/** only, which publishes nothing from any released package
(the fast lane in AGENTS.md's Post-Task Checklist step 3). skip-changeset applied on the PR.
The derivation's 14 changeset-dependent families are derived against a path that does not exist
and are correspondingly not in the run.

Acceptance notes

  • Two script comments now quote the OLD wording of :38 verbatim and go stale with this
    edit: scripts/pm/check-half-states.mjs (the H61 header, quoting the whole line "verbatim and
    untranslated") and scripts/pm/check-clause2-carriers.mjs (the C6 docblock, quoting
    「凡清标同笔留 provenance 评论,引记录 id 与所判 head」). Neither is gate-enforced — no script
    reads this file at runtime except the ratchet, for line counts, and H61's own pin asserts only
    「清标即落地」, which survives — and neither quote contradicts the new text; each is simply
    less specific than it (no carrier pin). Left untouched deliberately: the dispatch fences this
    card to references/contract-review.md with ⛔ no script change, and touching either script
    would add its own self-test surface to this PR. Successor: whoever next edits H61 or C6, or a
    quote-sync follow-up if the seat wants one.
  • Not filed as a card: prose drift in a comment is neither a reproducible defect, a declared
    contract violation, nor a metadata-authoring trap.

维护者速读(草稿)

改了什么:references/contract-review.md 第 38 行一句话,把 PASS 清标时那条 provenance
评论钉到 PR 上(原文只要求「留一条评论」,没说落哪个载体)。60 行进、60 行出,119 字节,
不动天花板、不动别的文件、不动任何脚本。

为什么改:合法的契约复核 PASS 在 PR 侧不留任何痕迹 —— 标签没了、get_reviews 读空、结论
只在卡上。观察者做了最合理的那次阅读(看 PR),得出「没人复审过」,于是善意回停已通过复审的
PR。这一次的实测代价:一张 p1/security 卡、两次回停、一次总监调查、两个 PR 空等约 40 分钟。
文内早有读者侧缓解(「get_reviews 读空 ≠ 未复审」),事故照样发生 —— 所以承重的是写者侧的
这一行。

风险与代价(含回滚):风险低。这是一行规则文字,不是机制:C6 已经同时读 PR 与卡两条线程,
本 PR 不动它,所以门禁语义零变化。代价是每次清标多一条 PR 评论(席位已在同一笔里写卡侧结论,
增量是一次 API 写)。回滚 = 撤销这一行,不留任何残留状态。两处脚本注释仍逐字引着旧措辞,已在
上面 Acceptance notes 里点名,未改动。

席位意见:(留待席位定稿)

你要做的:这是受管面(.claude/**)⇒ 只能人工合并。PR 保持 draft;确认这一行的措辞(尤其
「凡」字去掉后是否仍读作「每次清标」)后由维护者手工落地。


Generated by Claude Code

…omment on the PR, not only the card

The 清标即落地 disposition required a provenance comment at every clear but
left its CARRIER unpinned, so a clear recorded on the card side alone left the
pull request exactly as blind as the measured incident found it: the gate label
gone, `get_reviews` empty, and no comment on the PR thread — a legitimate
un-park indistinguishable from an intruder from the PR side.

Pin the comment to the PR, the carrier that gets un-parked, still citing the
record id and the judged head. One line, 60 lines before and after, 119 bytes.

Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
Co-authored-by: Claude <noreply@anthropic.com>
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读

事情:契约复审 PASS 之后,座位清掉闸门标签、翻 ready,但 PR 本身没有任何痕迹——只在卡上有一行结论。8 月 31 日的 #13412 事故就是这么来的:两个 PR 被合法复审放行,旁观的席位从 PR 侧看不到任何复审记录,当成入侵重新停回去,花掉一张 p1 安全卡、两次善意回停和一次总监调查。这张卡当时因为文件行数顶格被停放,等语料审计裁完;审计 Phase 2 已裁、文件已重写,本席今天放行并派发。

改了什么:references/contract-review.md 一行、一个字的差别——清标时的 provenance 评论从「留一条」改成「落在 PR 上」,仍引复审记录 id 与所审 head。60 行进 60 行出,不抬上限,脚本不动(检查脚本本来就同时读 PR 与卡两侧)。

风险:低。协议文本;门禁 14/14 绿。两处脚本注释里引用的旧原文会差一个从句,随下次动那两个脚本的 PR 顺手改。

只问一字:是否合并?


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 12, 2026 15:27
@os-zhuang
os-zhuang enabled auto-merge September 12, 2026 15:27
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 4351033 Sep 12, 2026
36 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13417-pass-pr-pointer branch September 12, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation needs-user-decision size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants