skills(pm-dispatch): the single-writer set is what SINGLE_CLAIM_PATHS enumerates — said where the seat reads batch independence - #17089
Conversation
…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
…ngle-claim-set-is-enumerated
维护者速读 — PR #17089(#17032,skills 席终稿,2026-09-09T07:36Z)改了什么 — 派发席读「同批独立性」的那几行(SKILL.md :438–442)原地改写两行:single-writer 路径只是 为什么改 — 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 字节; 你要做的 — 合并;或由 os-zhuang / hotlong 批准,本席随即入队。只需看深度表那半句要不要留。 Generated by Claude Code |
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.mjsand the workflow are untouched.references/core-rules.mdis 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):After (
9bbd015cb, :438–442; widths 118 / 114 / 112 / 114 / 118 bytes, measured withprintf '%s' LINE | wc -c):Gloss of the two rewritten lines: parallelism is capped by
batchand 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 whatSINGLE_CLAIM_PATHSenumerates (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
priority:p0可超batch(at :438)references/core-rules.md:47 says it again.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 bygit 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_limitreads the 15000/h form), thenGET /repos/objectstack-ai/objectstack/issues/comments?since=…&per_page=100&page=N&sort=created&direction=descpaged to a short page —/search/*was not used (MCP calls for the whole run: 0). Local regexsingle[- ]writer, case-insensitive, then classified by hand from each body's own seat markers (session id / lane / seat post).since=2026-09-08T00:00Z: 2,021 comments (created 09-07T11:37Z … 09-09T07:06Z), 21 pages, 68 hits across 50 issues.since=2026-09-05T00:00Z: 5,999 comments (09-05T07:24Z … 09-09T07:19Z), 60 pages, 122 hits. Widened because the gate itself landed 2026-08-18 (ba0a84685, PR ci: at most one open PR may modify a declared single-writer path #9598) and a two-day window cannot separate a personal error from a lane-wide one.system-context.mdx(106→107 twice, tree holds 108) #16919 and 5595150955 on [PM seat] domain:services — 🟢 os-trump · session_012zTkyNHJ7TkuN2oXtP5x37 · R25 · (consolidated-seat takeover of 09-09T01:4xZ withdrawn 06:3xZ — see brief) #6021 — and so are the services seat's own "Single-writer check, done before dispatch" claim paragraphs (sessionsession_012zTkyNHJ7TkuN2oXtP5x37: plugin-reports: a non-membertimezonesilently discards a report schedule's cron and fires it on the interval cadence forever — and the create-time guard cannot see it #16291, plugin-audit's read-audit rows back-datecreated_atto the VIEW instant through anisSystemreliance the audit hook never honoured, so every batchedsys_audit_logread row now gets the FLUSH instant #16829, plugin-auth:POST /two-factor/verify-totpon the enrolment lane echoesuser.twoFactorEnabled: falseafter the flag has flipped — the vendor's pre-rotation snapshot, whichtwo-factor-rotated-token-echorepairs fortokenonly #16535, [finding]verifyMcpAccessTokenresolves aclient_credentials(M2M) token to a principal, while its own docblock says such tokens carry nosuband are rejected #16418, [finding]AuthManagerstill carries two more independentbasePathnormalisers —getAuthIssuer()andgetMcpResourceUrl()— and one of them builds a malformed URL #16399, [finding] service-automation: the durable run-history row foldscancelled(andtimed_out) intofailed— after a restart the Runs surface cannot tell a cancelled run from a failed one #15223,Validate Package Dependenciesnow fails on every PR and will fail on main's next scheduled run — 15 advisories across 7 packages (2 Critical innext), all with fix versions, and no lockfile moved #16999, The #3424 admin override has no Console control: a record stranded behind an unstaffed approval is releasable only through the API #16679 ×2, Two branches bumping the same hand-maintained counter merge clean and produce a wrong sum with zero conflict — measured onsystem-context.mdx(106→107 twice, tree holds 108) #16919, service-analytics: on driver-sql (sqlite) the NativeSQLStrategy answersPOST /analytics/dataset/querywithout the object-level grant or the tenant wall — a user with NO read grant on an object gets its row count (200) whereGET /data/OBJECTanswers 403; the memory driver's path refuses both #16645, plugin-security: the first-user promotion picks the oldest authenticable user from an UNORDERED 50-rowsys_userwindow, so on the default driver a seeded job seeker became platform admin and owned every seeded row #16682). A zero is not what this sweep reads.Classification of the 122 (window B):
scripts/pm/**single writer is the objectstack seat; "single writer of the seat post"). A different subject.domain:devxscripts/check-regen-pending.mjsis a single-writer path, andNo other open PR may claim the same single-writer pathis a required check" — a repair deliberately NOT ported on that ground, three cardsdomain:servicesapproval-service.tswhile …" under "a required check"domain:clisession_01D47qPfEWVPmhguWgBZCi5Nrest-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 grounddomain:enginesession_01ARYe3yQTQCUFm5qPYNgKaJpackages/objectql/src/index.tsand the repo runs a single-writer-path check"domain:servicessession_012zTkyNHJ7TkuN2oXtP5x37harness.tsfencedomain:enginesession_01ADLdAs2pVcH17h9tZKWMBgNo other open PR may claim the same single-writer pathgate"domain:clisession_015QE8qk46e5CHJxyQEUjbf8rest-server.tsis a single-writer hot file"; "pnpm-lock.yamlis a generated single-writer path … check for a competing holder before editing it" — both after the 03:06 retractionpackage.jsonby #16805" as "true against the tree"; F2: "under single-writer this PR cannot enter the queue until #16805 lands"domain:engine09-06 13:24 (objectql: 11 error classes still spell their code as an inline literal, so a consumer cannot follow theby code, not instanceofconvention the docs already teach #16159, 5559511753: "The round measured the gate; I then re-readscripts/check-single-claim-paths.mjsonorigin/main");domain:cli09-07 11:12 (rest: the three server-builtfindDataliterals speak the canonical QueryAST; retirewireDialectQuery(consumer half of #16066) #16337, 5569766536: "⛔ The premise of the hold above is FALSE —rest-server.tsis not a CI-enforced single-writer path. Measured." —SINGLE_CLAIM_PATHShas one entry);domain:services09-09 03:06 ([PM seat] domain:services — 🟢 os-trump · session_012zTkyNHJ7TkuN2oXtP5x37 · R25 · (consolidated-seat takeover of 09-09T01:4xZ withdrawn 06:3xZ — see brief) #6021, 5595150955 — the card's provenance). The nextdomain:clisession re-adopted the model on 09-09: the correction did not survive a session boundary, which is exactly what a charter line is for.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/on854639b3: 0 hits; after: 1, SKILL.md:439; control wordbatch: 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 -l813 →node scripts/pm/check-skill-line-ratchet.mjsexit 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 withgit checkout HEAD -- PATH(trap-guarded, absolute path): marker count 0, 812 lines,git diff HEADempty,git status --porcelainempty, working-tree blob hash5e0b353e71d48de445714fd5987043dfa23b3b17equal toHEAD: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 pipeDerived 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.pnpm check:pm-skill-ratchet✓ … 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✓ check-skill-id-lint: 26 file(s) clean (pattern /#[0-9]{3,}/g)pnpm check:skill-frame-sync✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 filespnpm check:pm-governed-merges✓ check-governed-merges --self-test: 274 assertions …pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8021 text file(s) … no raw ASCII control bytes)pnpm check:doc-authoring✓ doc authoring guard: 398 files clean — no bare metadata literalspnpm check:pm-governed-prose✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfacesnode scripts/check-closing-keyword-parity.mjs(+--self-test)OK (3 parsers agree on all 9 keywords …)node scripts/check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 6410 files, 0 disagreenode scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 144 cases passpnpm check:agent-test-spelling·check:driver-memory-census·check:refd-timer-probe·check:watch-hint-literalpnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder the verify lock (4/4 turbo cache hits, lock held 1 s); first run exited 3 = the gate's ownPREREQUISITE NOT MET(unbuilt@objectstack/formula), NOT MEASURED, not a red. Verdict:✓ check:doc-formula-expressions self-test: 58 cases passedGoverned 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):⇒ draft, human merge. No seat flips it ready, queues it, arms auto-merge, or approves it.
skip-changeset:.claude/**ships in no package'sfiles[](fast lane, not published).Clause-②: noper the claim.Deviations from the dispatch, stated
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.single-claimoccurs nowhere in.claude/skills/**on the tree, before or after — it is not a control;batch(7 hits) was used instead.验收备注 (noted, not filed)
LOCK_DEPTH_HOLDis a name that exists only in SKILL.md and core-rules.md —scripts/pm/os-verify-lock.shneither defines nor prints it, so the depth table this PR removes was the only numeric provenance of the 2 anywhere in the tree; 承接者:无 (recorded here and in the commit that landed it).single-claimcontrol word (above); 承接者:the skills seat, next time it writes a grep control into a dispatch.维护者速读(草稿)
改了什么
.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 把它写在派发席判同批独立性时必读的那几行上。风险与代价(含回滚)
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