Skip to content

skills(pm-dispatch): the single-writer set is what SINGLE_CLAIM_PATHS enumerates — said where the seat reads batch independence - #17089

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-17032-single-claim-set-is-enumerated
Sep 9, 2026
Merged

skills(pm-dispatch): the single-writer set is what SINGLE_CLAIM_PATHS enumerates — said where the seat reads batch independence#17089
os-zhuang merged 2 commits into
mainfrom
claude/issue-17032-single-claim-set-is-enumerated

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #17032

What this changes

.claude/skills/pm-dispatch/SKILL.md, the 候选与批次 block, lines 438–442 rewritten in place: 812/812 lines, every touched line ≤120 bytes. Direction (1) of the card — say it once, where a dispatching seat reads about batch independence. Directions (2) (the check's display name) and (3) (the gate's passing output) belong to the gate's own lane: scripts/check-single-claim-paths.mjs and the workflow are untouched. references/core-rules.md is untouched too (151/151): its clause states independence by constructed file surface and never used the word; the reader who needs the mechanism is on SKILL.md.

Before (854639b3, :438–442):

- 并行度以 `batch` 封顶;同批独立性按文件面不相交判,⛔ 不按包;`priority:p0` 可超 `batch`。
- 第 N 单派发前读 `scripts/pm/os-verify-lock.sh --status`:到达深度 ≥ `LOCK_DEPTH_HOLD`(= 2)即等。
- 到达深度 = `queue N:` 行数 + 1(待派 dev 的运行算作到达);`state:` holder 与 `parked` 行不计。
- 有效上限是锁宽的函数,⛔ 不是第二个 `batch`;`priority:p0` 可超 `batch`,⛔ 不越过深度等待。
- 阈值出处是深度表:深度 1/2/3/4 的 exit 99 率 1%/23%/36%/56%;等待不填槽,读数带 UTC 写进认领。

After (9bbd015cb, :438–442; widths 118 / 114 / 112 / 114 / 118 bytes, measured with printf '%s' LINE | wc -c):

- 并行度以 `batch` 封顶,等待不填槽;同批独立按文件面不相交判,⛔ 不按包、不按 check 名。
- single-writer 路径 = `SINGLE_CLAIM_PATHS` 所枚举;共享其它路径是普通并发,后落地方解冲突。
- 第 N 单派发前读 `scripts/pm/os-verify-lock.sh --status`:到达深度 ≥ `LOCK_DEPTH_HOLD`(= 2)即等。
- 到达深度 = `queue N:` 行数 + 1(待派 dev 的运行算作到达);`state:` holder 与 `parked` 行不计。
- 有效上限是锁宽的函数,⛔ 不是第二个 `batch`;`priority:p0` 可超 `batch`,⛔ 不越过深度等待。

Gloss of the two rewritten lines: parallelism is capped by batch and a wait does not fill a slot; same-batch independence is judged by disjoint file surfaces — not by package, and not by a check's name. The single-writer paths are exactly what SINGLE_CLAIM_PATHS enumerates (no count hardcoded — the script is the authority and a number would rot); two open PRs sharing any other path is ordinary concurrent work, and the cost is one merge resolution paid by whichever lands second.

Paid in place — the three deletions and where each lives instead

deleted from :438–442 why it is not a loss
priority:p0 可超 batch (at :438) the line three below it (:441, now :442) states it verbatim, with its qualifier ⛔ 不越过深度等待 — a byte-level duplicate.
读数带 UTC 写进认领 (at :442) :163 板面/树/队列读数恒带 UTC 取数时刻;认领、派发令、复核、轮报与座位贴皆同 already binds the lock reading in the claim; references/core-rules.md :47 says it again.
阈值出处是深度表:深度 1/2/3/4 的 exit 99 率 1%/23%/36%/56% (at :442) the one real content deletion. It is the provenance of LOCK_DEPTH_HOLD = 2, not an operative rule; the rule (:440 depth ≥ 2 ⇒ wait) stays. The file's own charter, quoted by the ratchet's verdict text: provenance is one line, stories live on cards, not in operational text — the table now lives in the commit that landed it, 21e36a2d3 (PR #16271), reachable by git log -S'23%/36%/56%' -- .claude/skills/pm-dispatch/SKILL.md. Reviewer's call; see 风险 below.

等待不填槽 was moved, not deleted: from :442 to :438, beside 并行度以 batch 封顶 — the same rule at the slot it qualifies.

The sweep the card asked for — done first

Channel: repo-scoped REST (probe GET /repos/objectstack-ai/objectstack/issues/17032 → 200; /rate_limit reads the 15000/h form), then GET /repos/objectstack-ai/objectstack/issues/comments?since=…&per_page=100&page=N&sort=created&direction=desc paged to a short page — /search/* was not used (MCP calls for the whole run: 0). Local regex single[- ]writer, case-insensitive, then classified by hand from each body's own seat markers (session id / lane / seat post).

Classification of the 122 (window B):

  • ~60 neutral: the check's display name quoted inside a green-check roster of a contract review / ACCEPT / adoption record. Not the model.
  • ~6 the lane-charter sense (scripts/pm/** single writer is the objectstack seat; "single writer of the seat post"). A different subject.
  • The over-read model — "any file two open PRs both change is a single-writer path the gate enforces" — acted on, by seat:
lane session when (UTC) what it did on that basis
domain:devx (no id in body) 09-05 18:48–20:05, #15993 · #15985 · #15948 "scripts/check-regen-pending.mjs is a single-writer path, and No other open PR may claim the same single-writer path is a required check" — a repair deliberately NOT ported on that ground, three cards
domain:services (seat post #6021) 09-05 18:54, #15981 "a PR opened for this card today would take approval-service.ts while …" under "a required check"
domain:cli session_01D47qPfEWVPmhguWgBZCi5N 09-06 04:49 #15620 · 11:19 #16091 · 19:37 #16337 · 19:38 #16114 · 09-07 02:49 #15907 "rest-server.ts … the single-writer lock on that file"; "CI enforces it"; "reserved single-writer path, held for #15405 and enforced by CI"; parallel dispatch declined on that ground
domain:engine session_01ARYe3yQTQCUFm5qPYNgKaJ 09-06 12:22, #16159 "both PRs edit the barrel packages/objectql/src/index.ts and the repo runs a single-writer-path check"
domain:services session_012zTkyNHJ7TkuN2oXtP5x37 09-08 09:50 → 09-09 03:00 the card's own instance: thirteen claims, the #16919 block, the harness.ts fence
domain:engine session_01ADLdAs2pVcH17h9tZKWMBg 09-09 00:03, #16344 card not dispatched: "the third writer on that file, against the repo's own No other open PR may claim the same single-writer path gate"
domain:cli session_015QE8qk46e5CHJxyQEUjbf8 09-09 05:11 #15405 · 06:10 #16813 "rest-server.ts is a single-writer hot file"; "pnpm-lock.yaml is a generated single-writer path … check for a competing holder before editing it" — both after the 03:06 retraction
isolated review seat (no id in body) 09-08 15:00, #16863 accepted "single-writer claim on package.json by #16805" as "true against the tree"; F2: "under single-writer this PR cannot enter the queue until #16805 lands"

Verdict: lane-wide, not personal — four dispatching lanes (services, cli, engine, devx) plus one review seat, at least twelve distinct comments acting on the model across four days, and the phrase's only in-repo source before this PR was the check's display name (grep -n -i single-writer .claude/skills/ on 854639b3: 0 hits; after: 1, SKILL.md:439; control word batch: 7 before, 7 after).

Reverse verification — the ratchet reads this file

On 9bbd015cb (fix committed first): one line appended → on-disk marker count 1, wc -l 813 → node scripts/pm/check-skill-line-ratchet.mjs exit 1 with its own verdict line ✗ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 813 lines; the ratchet ceiling is 812. Restored with git checkout HEAD -- PATH (trap-guarded, absolute path): marker count 0, 812 lines, git diff HEAD empty, git status --porcelain empty, working-tree blob hash 5e0b353e71d48de445714fd5987043dfa23b3b17 equal to HEAD:PATH, ratchet exit 0 again. The hash equality is the proof; the trap is only the crash path.

Gates — all on 9bbd015cb, exit captured before any pipe

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths passed; 16 commands) and reconciled with --ran: ✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED.

command exit the gate's own verdict line
pnpm check:pm-skill-ratchet 0 ✓ … SKILL.md is 812 lines (ceiling 812; headroom 0) · core-rules.md is 151 lines (ceiling 151; headroom 0)
pnpm check:pm-skill-id-lint 0 ✓ check-skill-id-lint: 26 file(s) clean (pattern /#[0-9]{3,}/g)
pnpm check:skill-frame-sync 0 ✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files
pnpm check:pm-governed-merges 0 ✓ check-governed-merges --self-test: 274 assertions …
pnpm check:nul-bytes 0 check-nul-bytes: OK (scanned 8021 text file(s) … no raw ASCII control bytes)
pnpm check:doc-authoring 0 ✓ doc authoring guard: 398 files clean — no bare metadata literals
pnpm check:pm-governed-prose 0 ✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
node scripts/check-closing-keyword-parity.mjs (+ --self-test) 0 / 0 OK (3 parsers agree on all 9 keywords …)
node scripts/check-comment-mask-corpus.mjs 0 ✓ comment-mask corpus sweep: 6410 files, 0 disagree
node scripts/pm/check-governed-queue-guard.mjs --self-test 0 ✓ … 144 cases pass
pnpm check:agent-test-spelling · check:driver-memory-census · check:refd-timer-probe · check:watch-hint-literal 0 each each gate's OK line in the run log
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under the verify lock (4/4 turbo cache hits, lock held 1 s); first run exited 3 = the gate's own PREREQUISITE NOT MET (unbuilt @objectstack/formula), NOT MEASURED, not a red. Verdict: ✓ check:doc-formula-expressions self-test: 58 cases passed

Governed predicate, copied from node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md .claude/skills/pm-dispatch/references/core-rules.md (exit 3):

governed-surface predicate: 2 of 2 path(s) hit the register (5 surfaces, repo-agnostic).
  ⛔  GOVERNED — a human merge is the review record for this PR (#9495 regime).
      No seat flips it ready, enqueues it, or arms auto-merge (AGENTS.md Prime Directive #14).
      .claude/** ×2 — the agent instruction tree (skills, agents, hooks, settings)

⇒ draft, human merge. No seat flips it ready, queues it, arms auto-merge, or approves it. skip-changeset: .claude/** ships in no package's files[] (fast lane, not published). Clause-②: no per the claim.

Deviations from the dispatch, stated

  • The dispatch named the script path as the thing to cite; the line cites SINGLE_CLAIM_PATHS — the exported enumeration itself. The path is 38 bytes and no ≤120-byte spelling carried the path plus both halves of the rule (the closest, single-writer 只有 \scripts/check-single-claim-paths.mjs` 枚举的路径;其余共享路径是普通并发,后落地方解冲突。, is 136 bytes); git grep SINGLE_CLAIM_PATHSlands on the array inscripts/check-single-claim-paths.mjs`. The count is not hardcoded (所枚举), per the dispatch's mechanism assumption 2.
  • The new rule needed two lines, not one; three deletions inside :438–442 paid them (table above). Two are duplicates; the depth-table provenance is not.
  • The sweep window was widened to 09-05 in a second pass (the card asked for "recent"; the two-day pass alone could not answer personal-vs-lane-wide).
  • The dispatch's suggested grep control word single-claim occurs nowhere in .claude/skills/** on the tree, before or after — it is not a control; batch (7 hits) was used instead.

验收备注 (noted, not filed)

维护者速读(草稿)

改了什么

.claude/skills/pm-dispatch/SKILL.md 候选与批次段 :438–442 原地改写,总行数不变(812/812)、每行 ≤120 字节。新增两条规则:同批独立性按文件面判、⛔ 不按包也不按 check 名;single-writer 路径只有 SINGLE_CLAIM_PATHS 枚举的那几条,两个 open PR 共享其它任何路径都是普通并发,代价是后落地的一方解一次冲突。为付行数,删掉了同段内两处重复(priority:p0 可超 batch 与 :441 逐字重复;读数带 UTC 写进认领与 :163 重复)和一处出处数据(LOCK_DEPTH_HOLD = 2 的深度表)。等待不填槽 只是挪了位置。门禁脚本与 workflow 一字未动。

为什么改

四天里四个车道的派发席(services、cli、engine、devx)加一个复核席,把 CI 检查名 No other open PR may claim the same single-writer path 读成了「任何两个 open PR 共同改的文件都是单写手路径」,据此挡了本可派发的卡、给在跑的 dev 划了不该划的围栏、拒绝了本可顺带的修复。三个席位各自实测证伪过一次,但每次都停留在该席位的评论里,下一个会话又把错误模型捡回来。仓库里除了检查名,没有任何一处写着这条规则;本 PR 把它写在派发席判同批独立性时必读的那几行上。

风险与代价(含回滚)

  • 风险一:删掉的深度表(深度 1/2/3/4 对应 exit 99 率 1%/23%/36%/56%)是 LOCK_DEPTH_HOLD = 2 的唯一树内出处;规则本身(深度 ≥ 2 即等)保留,出处退回落地它的提交 21e36a2d3(docs(pm-dispatch): read the verify-lock queue depth before each dispatch and hold at depth ≥ 2 (#14944 Q1 B) #16271)。若维护者认为出处必须留在文件里,替代方案是不写「不按 check 名」这半句并把两条新规则压成一条(信息损失在新规则一侧),或按棘轮头部的跨文件搬迁条款另立单处理。
  • 风险二:行内引用的是导出常量 SINGLE_CLAIM_PATHS 而非脚本路径(路径 38 字节装不下);常量重命名会让这行失锚,与引用路径的风险同级。
  • 回滚:单文件、单提交,git revert 即回到 854639b3 的五行原文。

席位意见

(留空)

你要做的

读一遍 :438–442 的五行,确认措辞与你对「同批独立性」和「single-writer」的本意一致;认可深度表出处退回 git 历史;人工合并(受管面,席位不翻 ready、不入队、不挂 auto-merge)。方向 (2)(改检查显示名)与方向 (3)(绿检查输出列出声明集)留给门禁所在车道自行决定。

🤖 Generated with Claude Code

https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd


Generated by Claude Code

…numerates, said where the seat reads batch independence

Two lines in the 候选与批次 block, paid in place at 812/812 and ≤120 B per line:
the declared single-writer set is the enumeration in SINGLE_CLAIM_PATHS, sharing
any other path between open PRs is ordinary concurrent work whose cost is one
merge resolution paid by whichever lands second, and batch independence is not
read off a package or a check's name.

Paid by three deletions in the same five lines: the `priority:p0` 可超 `batch`
clause that duplicated the line three below it verbatim; 读数带 UTC 写进认领, which
the UTC-reading rule earlier in the file already states for claims; and the
depth-table provenance of LOCK_DEPTH_HOLD, which now lives in the PR that landed
it (git history), per the file's own one-line-provenance charter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
@yinlianghui yinlianghui added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026 — with Claude
@github-actions github-actions Bot added size/xs documentation Improvements or additions to documentation labels Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

维护者速读 — PR #17089(#17032,skills 席终稿,2026-09-09T07:36Z)

改了什么 — 派发席读「同批独立性」的那几行(SKILL.md :438–442)原地改写两行:single-writer 路径只是 scripts/check-single-claim-paths.mjsSINGLE_CLAIM_PATHS 枚举的那几条(今天一条 .objectui-sha);两个 open PR 共享其它任何路径是普通并发,后落地的一方解一次冲突;同批独立按文件面判,⛔ 不按包、不按 check 名。付账:删掉一处 priority:p0 可超 batch 的重复(:441 仍在)、一处「读数带 UTC」的重复(:163 仍在),以及深度表的四个百分比(它是当初定 LOCK_DEPTH_HOLD = 2 的实测出处,规则本身与阈值都还在,出处留在落地它的 PR #16271)。

为什么改 — services 席一小时内三次按「两个 PR 共享的任何文件都是 single-writer 路径」这个不存在的规则行事(阻塞一张卡、给在飞 dev 发围栏、编出一个类别),因为唯一能读到的东西是 check 的名字。dev 按卡面要求先做了扫描:这不是一个席位的个人误读——services、cli、engine、devx 四个派发车道加一个评审席,四天里 ≥12 条评论按同一个错误模型行事,三次各自的纠正都没传到下一个席位。

风险与代价(含回滚) — 纯指令文本;回滚 = revert 本 PR。真实代价一处:深度表的百分比从技能里删掉了——若你希望阈值出处留在文本里,把那半句放回并另删一处即可(dev 在 PR 正文写了替代方案)。改 check 名(卡面方向 2)归门禁所属车道,本 PR 不动脚本。

席位意见 — 接受(契约复审档席内复核)。棘轮 812/151 未动;id-lint、frame-sync 绿;控制字节 0;每行 ≤118 字节;mergeable: true;CI 收敛中。

你要做的 — 合并;或由 os-zhuang / hotlong 批准,本席随即入队。只需看深度表那半句要不要留。


Generated by Claude Code

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

3 participants