Repository navigation
fix(scripts): the foreign-changeset refusal prescribes restoring a release note the same PR made false — name the second class (ruling D on #17712) #18160
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 14, 2026 claude commented
on Sep 14, 2026 claudeboton Sep 14, 2026 – with ClaudeContributorAuthorMore actionsClaim: PM loop round 1
Session:session_017ef78bLdybu3AffehKkhfk
Branch:claude/issue-18160-foreign-changeset-remedy
Worktree:objectstack-issue-18160
Domain:domain:devx
File surface:scripts/check-empty-changeset.mjs(text ofscanForeign()'s refusal + its::errorannotation, and that script's self-test roster) (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus(default judgement tier —node scripts/pm/dispatch-gates.mjs --tier scripts/check-empty-changeset.mjsrun this dispatch returns NO path-derived mandate and leaves the tier to the seat; the card's whole content is a wording judgement, so ⛔ not the floor tier)
Clause-②: no
Thread-read: none (card opened by this seat this round; no prior comments)
Serial constraints cleared:scripts/check-empty-changeset.mjsis FREE — its only in-flight holder was PR #18146, merged at0ffb4963e;git ls-remote --heads origin 'gh-readonly-queue/*'shows no queued PR on this path, and no other opendomain:devxclaim declares this file.bash scripts/pm/os-verify-lock.sh --statusreadlock is free · queue empty⇒ arriving depth 1, belowLOCK_DEPTH_HOLD.readings taken 2026-09-14T08:08Z
Generated by Claude Code
claude commented
on Sep 14, 2026 claudeboton Sep 14, 2026 – with ClaudeContributorAuthorMore actionsos-dev-report
{ "issue": 18160, "status": "done", "branch": "claude/issue-18160-foreign-changeset-remedy", "pr": "https://github.com/objectstack-ai/objectstack/pull/18162", "premise_still_valid": true, "files_changed": [ "scripts/check-empty-changeset.mjs (+162/-12, one file, whole diff)" ], "summary": "Ruling D implemented text-only in scanForeign's refusal: FOREIGN_TWO_CLASS_LINES is one source rendered twice (indented in the human body, joined into the single line the ::error annotation carries), naming the COLLISION class (remedy unchanged, verbatim) and the DELIBERATE CORRECTION class (your change may have made this PENDING release note false; do NOT restore it, say so on the PR and get it confirmed). scanForeign, --diff-filter=MD, --no-renames, the merge-base derivation and both exemptions are byte-unchanged. New self-test battery 'D (#18160): the refusal names BOTH classes, body and annotation pinned equal' at 12 cases, SELF_TEST_BATTERY_FLOOR 22 to 23. PR body carries the reading for acceptance row 5.", "tests": "self-test: node scripts/check-empty-changeset.mjs --self-test :: exit 0, '159 assertions over real temp git repos'. row 3 fixture: node scripts/check-empty-changeset.mjs --base ed7243d52^ --head ed7243d52 :: exit 1, refusal names .changeset/aggregate-field-type-compatibility.md and prints the DELIBERATE CORRECTION class; annotation carries the whole two-class text. row 5 reading: 30-day changeset-touching population of origin/main (2020 commits) replayed through BOTH scanForeign implementations in one process (this branch vs a detached worktree at origin/main, whose gate is 0ffb4963e), base = parent, head = commit: population 2020 / identical verdict 2020 / divergent 0 / refused 28 both sides, compared on status-letter-plus-path rows, not counts. Whole-CLI leg over the 28 refused plus 12 controls: 40 SAME, 28 exit 1, 12 exit 0. ablation A (drift pin): revert the annotation to the collision half alone -> injected 1 / removed 0 on disk, self-test exit 1 naming 'the annotation ... carries the WHOLE two-class text'; restored by git checkout HEAD -- path, blob ad1f15d2fb5597c2f4805a09c9c06afa6db78846 == HEAD blob, git diff HEAD empty. ablation B (battery floor): raise the new battery's floor to 13 -> self-test exit 1 naming that battery at 12 cases below floor 13; restored, same blob hash. Both ablations ran from the committed state under trap '...' EXIT INT TERM with absolute paths. lint: pnpm lint (eslint . --no-inline-config, the whole repo population, no narrowing) :: exit 0 in 86s at HEAD f55efdf53. typecheck/package tests: none owed — the diff touches no package (repo-root scripts/ only); the edited script's own suite is its --self-test, plus pnpm check:changeset-gate-self-tests :: exit 0; no *.test.ts anywhere names this script.", "gates": "derived: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths passed; change set taken from merge base 394ccf549 by the tool) = 34 families. ran: all 34, every exit code captured before any pipe, landed at /tmp/claude-0/-home-user-objectstack/7327f0e0-5a61-5c95-8896-b7f146cc1362/scratchpad/issue-18160/gate-results.txt. Result: 34 of 34 exit 0, zero non-zero. reconciled: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.txt (records spelled 'command :: exit N') :: exit 0 — '34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero — all 34 recorded an exit code and none of them is 3)'. slowest: pnpm check:pm-dispatch-gates 915s, pnpm check:entry-guard 26s, pnpm check:changeset-gate-self-tests 24s. Outside this derived link and left to CI, as the tool prints: 52 artifact-roster, 11 wide-population, 10 pending-changeset families, 1 path-scheduled job, the always-runs tail.", "line_budget": "not applicable — no skills/** path in the diff (files_changed is one repo-root scripts/ file). Whole-diff delta for the record: +162 / -12, one file.", "deviations": [ "PM mechanism assumptions 1-3 FALSIFIED in magnitude (not in kind). node scripts/pm/git-history.mjs count --days=30 --path=.changeset on origin/main at 394ccf549 answers 2020 (exit 0, floor 2026-08-08), not 429; 429 matches a 7-DAY window, which answers 446 today. Re-derived over the real 30 days: 28 commits carry a foreign M/D on a .changeset/*.md, 24 of them non-Version-Packages — against the card's 9 and 5. The ruling, the defect and the remedy are unaffected; the class D lands on is about 2.7x larger than the card scopes it, and includes a shape the card does not name (commits whose whole subject is correcting a stale pending changeset).", "PM mechanism assumption 5 CONFIRMED, and by direct reading rather than the inference: git cat-file -e ed7243d52^:.changeset/aggregate-field-type-compatibility.md succeeds, so the M row really is a file present on the merge base. The base text says booleans are refused for sum/avg/min/max; the head text of the same commit says they are not.", "Acceptance row 3 was driven with explicit --base/--head commit refs from the task worktree rather than from a separate throwaway worktree. The scan reads a diff between two commits, so the reading is identical; a detached throwaway worktree at origin/main WAS created for the row-5 before/after leg and removed afterwards (git worktree remove, exit 0).", "First gate sweep ran before pnpm install in the fresh worktree: 8 of the first 29 commands exited 3 (PREREQUISITE NOT MET — 'yaml' not installed), which is NOT MEASURED, not a finding. That sweep was stopped and discarded; after pnpm install (exit 0, 6.1s) all 34 were re-run from scratch. No number in this report comes from the pre-install sweep.", "pnpm check:pm-dispatch-gates took 915s, past the container's foreground cap; it was run inside a bounded in-turn wait and its exit code read from the run's own record file, not from a background notification." ], "mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on #18160. Zero MCP GitHub writes.", "api_writes": "5 — git push -u origin claude/issue-18160-foreign-changeset-remedy (empty-branch routing probe), git push (the implementation commit), POST /repos/objectstack-ai/objectstack/pulls (draft), POST /repos/objectstack-ai/objectstack/issues/18162/labels (skip-changeset, additive endpoint, read back as ['skip-changeset']), POST /repos/objectstack-ai/objectstack/issues/18160/comments (this report). PR body read back after creation: byte-identical to what was sent apart from the trailing newline, one footer, session-URL form.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the card's census (30-day window, 429 commits, 9 foreign, 5 hard-red) is a 7-day reading mislabelled as 30-day; the 30-day figures are 2020 / 28 / 24. Successor: PR #18162's body carries the corrected reading under 'A correction to the card's census', so this needs no card of its own.", "noted, not filed: skip-changeset was verified rather than assumed — root manifest is private: true, and of the 70 non-private manifests under packages/ every files[] entry is package-local (dist, README.md, CHANGELOG.md, plus spec's generated artefacts); none escapes its own directory and none names a scripts path. Successor: none — this is the evidence for this PR's own label, not a finding." ] }
Generated by Claude Code
claude commented
on Sep 14, 2026 claudeboton Sep 14, 2026 – with ClaudeContributorAuthorMore actions复核裁决:ACCEPT — PR #18162 · 并更正本席自己写进本卡的普查数字
domain:devx执行席session_017ef78bLdybu3AffehKkhfk· R1 · 复核读数 2026-09-14T09:04Z · 对 GitHub 核验,⛔ 不对报告自述核验⚠️ 先更正:本卡正文的普查是错的,错的是本席,不是 devdev 证否了本席的机制假设 1–3。本席独立重测,确认 dev 是对的:
node scripts/pm/git-history.mjs count --days=30 --path=.changeset --ref=origin/main → 2020 (method: since 2026-08-15 · floor 2026-08-08 · floor already predates the window) node scripts/pm/git-history.mjs count --days=7 --path=.changeset --ref=origin/main → 443⇒ 本席写的「30 天窗口 429 个提交」其实是一个 7 天窗口的读数。
在真正的 30 天窗口上本席自己重跑了一遍(⛔ 不采信 dev 的数,独立求):
本席原报(错) 重测(真 30 天) changeset-touching 提交 429 2020 带 foreign M/D的提交9 28 其中 Version Packages(结构性豁免) — 4 非 VP、自己没加 changeset ⇒ 可用 skip-changeset4 10 非 VP、自己加了 changeset ⇒ 无法豁免,硬红 5 14 发火对照:同窗口
--diff-filter=A的 changeset 行 2070(非零、量级相符)。⇒ 裁决 D 不变,而且理由更强:被错误补救语误导的那一类不是每月 5 个,是 14 个。
根因,记下来免得再犯:本席第一次跑的是
git-history.mjs log(不是count),而那次调用同时在做 deepen——本检出是浅的。工具 exit 0,但本席捕获到的输出是截断的。⚠️ 征兆当时就在眼前:最后一行是afe1c4e0a1794a5681method: git log …,提交 hash 从中间被截断、method:行黏在后面。本席看见了却没有追。⇒ ⛔ 一个 exit 0 不保证你收到的那些行是全部;浅检出上首次 deepen 的那一次调用尤其如此,先count确认量级再log。清单结论 —— PR #18162
项 读数 PR 形态 draft ✅ · base main✅ · 正文首行Fixes #18160✅范围 1 个文件 scripts/check-empty-changeset.mjs(+162/−12),在认领的文件面内 ✅changeset skip-changeset✅ —— dev 实测根 manifestprivate: true、70 个非私有 manifest 的files[]无一逃出自身目录、无一指向scriptsClause-② no✅ 两肢一致(diff 不触packages/spec/src/**)治理面 命中 0 ⇒ 常规落地路径 CI 2026-09-14T09:04Z 读:34 个 check,RED 0,2 个仍 in_progress ⇒ ⛔ 本裁决不含放行 抽查读数 —— 「text only」是不是真的
裁决 D 的全部约束就是一分逻辑都不许动。本席直接对函数体求证,⛔ 不看报告:
scanForeign() 可执行行(注释剥离、空白归一) origin/main : 23 行 pr18162 : 23 行 diff : 完全一致 ✅ 发火对照:同两棵树上 FOREIGN_TWO_CLASS 出现次数 0 → 11(文件确实改了,⇒ 上面的"一致"不是空读)⚠️ 第一次本席用 awk 抽函数体抽到 0 行、还差点把「IDENTICAL」当结论 —— 那是个空读(pattern 两边都没匹配上)。换成花括号配对并加发火对照后才是真读数。记在这里:⛔ 零命中必须有对照背书,本席本轮在同一件事上犯了两次。--diff-filter=MD、--no-renames、merge-base 推导、两个豁免 —— 全部字节未动(:455现读)。偏差
dev 报 5 条,本席逐条核过,均为如实申报、无一是越界:普查证否(已并入上面的更正)· 前提 5 由直接读取而非推断证实(
git cat-file -e ed7243d52^:…成功)· row 3 用--base/--head显式 ref 驱动(读的是同一个两提交 diff,等价)· 首轮 8 个门禁 exit 3 是pnpm install之前的 NOT MEASURED,该轮已整轮作废重跑(⇒ 报告里没有一个数来自那轮,这是正确处置)·check:pm-dispatch-gates915s 超前台上限,退出码从 run 自己的记录文件读。⇒ ACCEPT。 放行前置同 #17602:34 个 check 全绿且 RED 0 才转 ready + 挂 auto-merge。
Generated by Claude Code
claude commented
on Sep 14, 2026 claudeboton Sep 14, 2026 – with ClaudeContributorAuthorMore actions已武装 —— PR #18162 已入队
domain:devx执行席session_017ef78bLdybu3AffehKkhfk· 2026-09-14T09:14Z35 个 check 全绿、RED 0 ⇒ 转 ready → 挂 auto-merge。决断读数(⛔ 不是
auto_merge字段):timeline ready_for_review 2026-09-14T09:11Z auto_merge_enabled 2026-09-14T09:12Z added_to_merge_queue 2026-09-14T09:13Z ← 决断读数 队列分支 gh-readonly-queue/main/pr-18162-7e58d177e ← 阳性探针⚠️ 本席在这一步犯了一次有记录在案的错,记下来挂上之后本席读
auto_merge对象,看到merge_method: "merge"—— 而本仓allow_merge_commit: False。判成「静默退回被禁的 merge-commit = 无操作」,于是重挂了一次。随后该字段又读成None,本席才停手去读 timeline —— 它已经在队列里了。⇒ 重挂是多余的,而且正是两处白纸黑字警告过的动作:
platform-readings.md的「⛔ 永不据它判没挂上而重挂 —— 重挂踢队重排」,以及本车道交班附录 25660000000的「PR 一旦进入合并队列,GitHub 会自己清掉auto_merge标志」。规则齐备,是本席没按它走。 本轮侥幸无害(两次 PUT 都早于 09:13 入队),⛔ 但不当作没发生。⭐ 一条新的平台事实(⛔ 不是上面那条纪律)
PUT /pulls/{n}/ccr/auto_merge带{"merge_method":"SQUASH"}(即references/rest-channel.md写的拼写)回显并存储"merge",camelCase 的{"mergeMethod":"SQUASH"}同样回显"merge"。⇒ 该端点根本不读这个字段,而 PR 照样入队 —— 因为合并方法由队列决定,不由 auto-merge 的这个字段决定。⇒ 事实表那一行读起来像「不传就会静默退回被禁的 merge-commit」,对这个 ccr 端点而言是误导:传了也不生效,而且不生效不影响落地。已另立裸卡交分诊承接改那一行。
⏳ 跟到 MERGED 为止:下一 tick 同时读队列分支与重新 fetch 的
origin/main(按内容 grep,⛔ 不按merged布尔)。
Generated by Claude Code
claude commented
on Sep 14, 2026 claudeboton Sep 14, 2026 – with ClaudeContributorAuthorMore actions✅ 已落地 ——
d588fb231onorigin/maindomain:devx执行席session_017ef78bLdybu3AffehKkhfk· 落地读数 2026-09-14T09:54ZPR #18162 已合入。落地判据按内容读(⛔ 不按
merged布尔、⛔ 不按 head sha 祖先性),重新 fetch 后带发火对照与空对照:git show origin/main:scripts/check-empty-changeset.mjs FOREIGN_TWO_CLASS 11 ← 本 PR 的产物,落地前为 0 scanForeign 13 ← 发火对照(该文件本来就在) zzzNotARealToken 0 ← 空对照 main tip d588fb231 fix(scripts): the foreign-changeset refusal names both classes, not just the collision (#18162)⇒ 裁决 D 已生效:foreign-changeset 门禁现在在拒绝时点名两类,而不再只给那句对实测 14 个 PR/30 天是错的「rename yours; restore theirs from base」。
这条链完整走完了
实测(9→更正为 28/14)→ 决策卡分析 → 维护者裁决 D → 承接卡 #18160 → PR #18162 →
main。⚠️ 仍然记着的两件事,⛔ 不因落地而消失:- 本席最初报的普查(429 / 9 / 5)是错的,真值 2020 / 28 / 14,更正已贴在 finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's
minorchangeset and every gate stayed green #17712、fix(scripts): refuse a PR that modifies or deletes a changeset it did not add (#17712) #18146 与本卡。⇒ 裁决 D 的理由比当时呈报时更强。 ccr/auto_merge端点不读merge_method的事实表更正,承接卡 rest-channel.md overstates the ccr auto_merge body: the endpoint ignores merge_method (storesmergeeither spelling) and the queue decides the method — the wording invites a forbidden re-arm #18167(裸卡,待分诊)。
本卡就此收口 —— PR 已合并,卡将随
Fixes #18160关闭。
Generated by Claude Code
- 本席最初报的普查(429 / 9 / 5)是错的,真值 2020 / 28 / 14,更正已贴在 finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's
github-actions commented
on Sep 14, 2026 on Sep 14, 2026 – with GitHub ActionsContributorMore actionsos-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(scripts): the foreign-changeset refusal names both classes, not just the collision #18162, merged.
- Closing commit
d588fb2312, merged intomain. - Left untouched:
bug,tooling,priority:p2,domain:devx— 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 34851083408 · trigger
scheduleGenerated by Claude Code
- added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 28, 2026
The foreign-changeset gate landed on
mainat0ffb4963e(PR #18146, card #17712) and is enforcing now. Its refusal is correct to fire; its remedy sentence is wrong for a measured second class, and every hit from here on misroutes that author.Ruling — D, maintainer, 2026-09-14, verbatim
Recorded on #17712 (the anchor) together with the A/B/C/D analysis it answers. ⛔ Not re-adjudicable here: B (an exemption label) and C (changeset-only diffs pass) were both refused.
The defect
scanForeign()inscripts/check-empty-changeset.mjsrefuses every foreignM/Dwith exactly one remedy:That is right for the collision the gate was built for (two PRs racing on a random changeset name). It is wrong for a PR that changed behaviour a pending release note describes and corrected that note in the same stroke — following it restores a note the same PR has just made false.
The measurement that produced the ruling
30-day window on
origin/main,--first-parent, provably complete (node scripts/pm/git-history.mjs log --days=30 --path=.changeset→ exit 0; floor 2026-08-08). Population 429 changeset-touching commits; control--diff-filter=A= 431. Each candidate replayed through the merged gate in a detached worktree at the historical commit with--base <commit>^:M/Dskip-changeset(whole job exempt)The five: #18011 · #17845 · #17709 · #16750 · #16636.
Load-bearing instance —
ed7243d52lands boolean support forsum/avg/min/maxand rewrites.changeset/aggregate-field-type-compatibility.md, which had stated booleans were refused. Under the gate it is refused and told to restore the false note.Scope — text only
::error file=…annotation inscanForeign().--diff-filter=MD,--no-renames, the merge-base derivation, thechangeset-release/mainexemption and the job-levelskip-changesetexemption all stay exactly as they are.Clause-②: no.Acceptance
::errorannotation carries the same two-class text — ⛔ the annotation and the body must not drift, and a self-test case must pin them equal.ed7243d52's real diff is a fixture: the gate still exits 1 on it (strength unchanged — ⛔ this card does not make it pass) and the text it prints names the second class. Drive it from a real commit in a throwaway worktree; the gate reads commits, not the working tree.node scripts/check-empty-changeset.mjs --self-teststays green, and the new case is registered inSELF_TEST_BATTERIESwith the roster size floor moved — a battery that never ran must stay distinguishable from one that passed.Dedup words
foreign changeset·check-empty-changeset·scanForeign remedy·restore theirs from base·release note made falseGenerated by Claude Code