You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(scripts): the foreign-changeset refusal names both classes, not just the collision
`scanForeign()` refused every foreign `M`/`D` with one remedy -- "rename yours;
restore theirs from base". That is right for the collision the gate was built
for, and wrong for a PR that changed behaviour a PENDING release note describes
and corrected that note in the same stroke: following it restores a sentence the
same PR has just made false.
The refusal now names both classes and routes the second to the existing human
confirmation path instead of to the restore. One source, two renderings: the
human body indents the block, the `::error` annotation joins it into a line, and
the self-test reads the block back out of the rendered body and requires it to
equal the annotation's text byte for byte, so neither rendering can drift.
Text only -- `--diff-filter=MD`, `--no-renames`, the merge-base derivation and
both exemptions are untouched, and the new battery pins the deliberate-correction
shape as STILL refused.
Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
// which had said booleans were refused). Where the two classes are rendered and
137
+
// how they are held equal is at `FOREIGN_TWO_CLASS_LINES`.
125
138
//
126
139
// Ruled 2026-09-13 (director seat, decision batch #130 item 3) on #17712, as
127
140
// option A'. The census the ruling rests on, taken on `origin/main` at
@@ -511,12 +524,42 @@ function report(violations) {
511
524
}
512
525
513
526
/**
514
-
* The one remedy the #17712 ruling names, verbatim. A constant because the
515
-
* self-test asserts the rendered report carries it: a refusal that names the
516
-
* offending file but not the way out sends an author to read this script.
527
+
* The remedy for the COLLISION class, verbatim as the #17712 ruling names it.
528
+
* A constant because the self-test asserts the rendered report carries it: a
529
+
* refusal that names the offending file but not the way out sends an author to
530
+
* read this script.
517
531
*/
518
532
exportconstFOREIGN_REMEDY='rename yours; restore theirs from base';
519
533
534
+
/**
535
+
* The remedy for the DELIBERATE-CORRECTION class (#18160, ruling D on #17712).
536
+
* It is the OPPOSITE act, and that is the whole point of naming two classes: a
537
+
* PR that changed behaviour a PENDING release note describes and corrected that
538
+
* note in the same stroke is refused CORRECTLY -- this text changes no verdict
539
+
* -- but following `FOREIGN_REMEDY` there restores a sentence the same PR has
540
+
* just made false. Measured instance: `ed7243d52` lands boolean support for
541
+
* `sum` / `avg` / `min` / `max` and rewrites
542
+
* `.changeset/aggregate-field-type-compatibility.md`, whose base text stated
543
+
* booleans were refused for exactly those four aggregates.
544
+
*/
545
+
exportconstFOREIGN_CORRECTION_REMEDY='do NOT restore it -- say so on the PR and get it confirmed';
546
+
547
+
// ONE source for the two-class remedy, rendered TWICE: as indented lines in the
548
+
// human body, and joined into the single line a `::error` annotation has to be.
549
+
// A reviewer who reads only the annotation on the diff and an author who reads
550
+
// only the job log must be told the same thing, so the two renderings are held
551
+
// equal by construction (one source) AND by assertion -- the self-test reads the
552
+
// block back OUT of the rendered body, normalises it to one line, and requires
553
+
// it to equal `FOREIGN_TWO_CLASS_TEXT` byte for byte (#18160 acceptance 2).
554
+
constFOREIGN_TWO_CLASS_LINES=Object.freeze([
555
+
'Two things produce this refusal and their remedies are OPPOSITE, so read which one you are before you act.',
556
+
`COLLISION -- you and another PR drew the same changeset filename, and yours overwrote theirs. Remedy: ${FOREIGN_REMEDY}.`,
557
+
`DELIBERATE CORRECTION -- your change may have made this PENDING release note false, and you rewrote it in the same stroke. Remedy: ${FOREIGN_CORRECTION_REMEDY}; restoring it from the base would put the false sentence back.`,
558
+
]);
559
+
560
+
/** The one-line rendering of {@link FOREIGN_TWO_CLASS_LINES}, for annotations. */
M: 'present on the merge base and CHANGED by this PR -- this is somebody else\'s release note',
522
565
D: 'present on the merge base and DELETED by this PR -- this is somebody else\'s release note',
@@ -530,7 +573,7 @@ function reportForeign(rows) {
530
573
console.error(
531
574
[
532
575
'',
533
-
`Remedy: ${FOREIGN_REMEDY}.`,
576
+
...FOREIGN_TWO_CLASS_LINES.map((line,i)=>(i===0 ? line : ` ${line}`)),
534
577
'',
535
578
'A changeset filename carries no meaning, so a collision looks like nothing: the',
536
579
'default word-pair names were designed for one human running the CLI at a time, and',
@@ -540,7 +583,7 @@ function reportForeign(rows) {
540
583
'replaced by yours, its own CI never re-runs, and the loss surfaces at release time in',
541
584
'the generated CHANGELOG, with the authoring PR long merged (#17712).',
542
585
'',
543
-
'Concretely:',
586
+
'Concretely, for the COLLISION class:',
544
587
'',
545
588
' 1. Restore their file exactly as it stands on the merge base:',
546
589
' git checkout <merge-base> -- <the file named above>',
@@ -553,14 +596,19 @@ function reportForeign(rows) {
553
596
'only thing that consumes them, and it runs on the release PR, which this gate never',
554
597
'judges.',
555
598
'',
556
-
'If you are deliberately correcting somebody else\'s release note, that is a decision',
557
-
'about a release, not a refactor -- say so on the PR and get it confirmed, rather than',
558
-
'routing around this gate.',
599
+
'For the DELIBERATE CORRECTION class there is no second command to run, and step 1',
600
+
'above is the one thing not to do: the note you rewrote describes behaviour THIS PR',
601
+
'changed, so restoring it from the base republishes a sentence that is now false, and',
602
+
'no label and no diff shape makes that safe. Correcting a pending release note is a',
603
+
'decision about a release rather than a refactor -- say so on the PR, naming the note',
604
+
'and what changed under it, and get it confirmed. That is the existing human path;',
605
+
'this gate stays red either way, and staying red is what puts the decision in front of',
606
+
'a person instead of routing around it.',
559
607
].join('\n'),
560
608
);
561
609
for(const{ file }ofrows){
562
610
console.error(
563
-
`::error file=${file}::${file} exists on the merge base and was not added by this PR, so changing or deleting it silently replaces somebody else's release note (#17712). Remedy: ${FOREIGN_REMEDY}.`,
611
+
`::error file=${file}::${file} exists on the merge base and was not added by this PR, so changing or deleting it silently replaces somebody else's release note (#17712). ${FOREIGN_TWO_CLASS_TEXT}`,
0 commit comments