The foreign-changeset gate landed on main at 0ffb4963e (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
D — 只改补救语(推荐)
门禁强度一分不减,只把拒绝时那句话改对:点名「你的改动可能让这条待发布说明变成假的」,并指向已有的人工确认路径。不新增标签、不放宽接受集,一次 revert 可回退。四棱同向 D。
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() in scripts/check-empty-changeset.mjs refuses every foreign M/D with exactly one remedy:
Remedy: rename yours; restore theirs from base.
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>^:
| population |
n |
verdict |
non-Version-Packages commits with a foreign M/D |
9 |
exit 1 — all nine |
…carried skip-changeset (whole job exempt) |
4 |
exempt in practice |
| …added their own changeset ⇒ cannot take the exemption |
5 |
hard red, wrong remedy |
| control: changeset-adding commits, no foreign row |
3 |
exit 0 — control fires |
The five: #18011 · #17845 · #17709 · #16750 · #16636.
Load-bearing instance — ed7243d52 lands boolean support for sum/avg/min/max and 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
- ✅ The refusal body and the
::error file=… annotation in scanForeign().
- ⛔ No logic change.
--diff-filter=MD, --no-renames, the merge-base derivation, the changeset-release/main exemption and the job-level skip-changeset exemption all stay exactly as they are.
- ⛔ No new label (B refused). ⛔ No diff-shape allowance (C refused). ⛔ No accept-set or public-surface movement ⇒
Clause-②: no.
Acceptance
- The refusal names both classes: the collision (rename yours; restore theirs from base) and the deliberate correction (your change may have made this pending note false), and routes the second to the confirmation path the report already describes, rather than prescribing the restore.
- The
::error annotation 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-test stays green, and the new case is registered in SELF_TEST_BATTERIES with the roster size floor moved — a battery that never ran must stay distinguishable from one that passed.
- ⛔ No net change to the number of PRs the gate refuses. State that in the PR body with a reading, not an assertion.
Dedup words
foreign changeset · check-empty-changeset · scanForeign remedy · restore theirs from base · release note made false
Generated by Claude Code
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