|
109 | 109 | * their own, so a branch pushed to the default branch by any other route closes |
110 | 110 | * the cards its intermediate commits name. |
111 | 111 | * |
112 | | - * The remedy is the contract this repository already carries, cited rather than |
113 | | - * restated — the card relation is declared ONCE, in the PR body, and the merger |
114 | | - * takes the squash message from that body. ⛔ Nothing in this gate's output asks |
115 | | - * anyone to rewrite history: amend, rebase and force-push are forbidden here, |
116 | | - * and a red on an already-pushed branch is repaired the way this repo's merges |
117 | | - * already repair it, in the body and at the merge. |
| 112 | + * The contract is the one this repository already carries, cited rather than |
| 113 | + * restated — the card relation is declared ONCE, in the PR body, and no commit |
| 114 | + * carries a card trailer. |
| 115 | + * |
| 116 | + * ## RULE 2 — what the output may ask for, and when it may ask for nothing |
| 117 | + * |
| 118 | + * ⛔ Nothing in this gate's output asks anyone to rewrite history: amend, rebase |
| 119 | + * and force-push are forbidden here. That prohibition is absolute, which makes |
| 120 | + * the TIMING of a RULE 2 finding the whole story: |
| 121 | + * |
| 122 | + * - BEFORE the branch is pushed the remedy is ordinary work — reword the |
| 123 | + * commit messages, state the relation only in the body, push once. Nothing |
| 124 | + * published is rewritten and the check goes green. |
| 125 | + * - ONCE THE BRANCH IS PUSHED no author action clears the red. This gate reads |
| 126 | + * the PR's COMMIT LIST, so a new commit on top JOINS that list and leaves |
| 127 | + * the offending message in it; the only thing that would remove it is the |
| 128 | + * rewrite forbidden above. The red is PERMANENT for that branch, and saying |
| 129 | + * so plainly is the point of this section. An earlier revision instead told |
| 130 | + * the author to push reworded commits — which on a pushed branch IS the |
| 131 | + * forbidden rewrite — and a seat that read it instructed one; the dev's |
| 132 | + * refusal is what stopped it, not the wording. |
| 133 | + * |
| 134 | + * ⚠️ That earlier revision also had the merger "take the squash message from |
| 135 | + * that body". Measured, that is false: on 0a61db1f5, the squash of PR #16646, |
| 136 | + * the landed message is the branch commit's verbatim and carries its |
| 137 | + * `Refs #16624`, while the body's `Fixes #16624` appears nowhere in it. The |
| 138 | + * squash message is assembled from the COMMITS. This does not weaken RULE 2 — |
| 139 | + * it is RULE 2's premise: a trailer left on a pushed commit really does reach |
| 140 | + * the default branch's permanent history. |
| 141 | + * |
| 142 | + * What that costs depends on the spelling, and the output says so rather than |
| 143 | + * flattening it: Part-of and Refs land as a reference and move no card, while a |
| 144 | + * CLOSING keyword lands on the surface GitHub's parser reads. The asymmetry is |
| 145 | + * why the rule refuses all three at PR time rather than only the contradictory |
| 146 | + * ones. |
| 147 | + * |
| 148 | + * ⛔ Whether a pull request LANDS carrying this red is not this gate's call and |
| 149 | + * its output must not make it. The check run is advisory at the branch- |
| 150 | + * protection layer — absent from the required-context registry, and its workflow |
| 151 | + * subscribes to no `merge_group` event because a queue build carries no body for |
| 152 | + * it to judge — so that decision belongs to whoever lands the PR, under the |
| 153 | + * rules that bind them. This gate reports. |
118 | 154 | * |
119 | 155 | * ## Where the commit messages come from, and why the endpoint and not a walk |
120 | 156 | * |
@@ -314,7 +350,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ |
314 | 350 | 'Context reading: presence, not truthiness.': 4, |
315 | 351 | 'The wiring itself. A gate whose workflow step is deleted or whose': 6, |
316 | 352 | 'The predicate source this gate reuses must still be there to reuse.': 1, |
317 | | - 'RULE 2 — every card-relation spelling in a commit message is a finding,': 13, |
| 353 | + 'RULE 2 — every card-relation spelling in a commit message is a finding,': 19, |
318 | 354 | 'The regression fixture: the squash that assembled a contradiction no': 4, |
319 | 355 | 'RULE 2 delegates to the sweep extractors at the commit-message reading.': 3, |
320 | 356 | 'The commit list input. An absent, broken or empty list can never read': 8, |
@@ -505,10 +541,11 @@ export function commitTrailerFindings(commits) { |
505 | 541 | `message that lands on the default branch — a text no one writes and no one reviews, which ` + |
506 | 542 | `carries every trailer its inputs carried and can contradict itself where its parts did not. ` + |
507 | 543 | `The trailer is also live on its own. ${RELATION_CONTRACT} ` + |
508 | | - `Remedy: move this relation into the PR body, where it is stated once, and take the squash ` + |
509 | | - `message from that body at merge. ` + |
| 544 | + `Remedy, while this branch is still UNPUSHED: reword this commit message so the relation is ` + |
| 545 | + `stated only in the PR body. ` + |
510 | 546 | `⛔ Do NOT amend, rebase or force-push to remove it — rewriting pushed history is forbidden ` + |
511 | | - `here, and it is not what fixes this.`, |
| 547 | + `here; on an already-pushed branch nothing removes it, and the summary below is what to do ` + |
| 548 | + `about that.`, |
512 | 549 | ); |
513 | 550 | } |
514 | 551 | return findings; |
@@ -658,8 +695,32 @@ export function judge(ctx) { |
658 | 695 | ); |
659 | 696 | for (const finding of commitFindings) lines.push(` ${finding}`, ''); |
660 | 697 | lines.push( |
661 | | - ' Pushing the reworded commits re-runs this check. ⛔ The repair is NOT a history rewrite:', |
662 | | - ' state the relation once in the PR body and take the squash message from that body at merge.', |
| 698 | + ' ⛔ The repair is NOT a history rewrite. Amend, rebase and force-push are forbidden in this', |
| 699 | + ' repository and this gate never asks for one. What it does ask for turns on one thing only:', |
| 700 | + '', |
| 701 | + ' BRANCH NOT PUSHED YET — reword the commit messages now and push once. Nothing published is', |
| 702 | + ' rewritten, the relation goes in the PR body where the contract puts it, and this check is green.', |
| 703 | + '', |
| 704 | + ' BRANCH ALREADY PUSHED — no author action clears this red, and that is expected rather than a', |
| 705 | + " problem to solve. This gate reads the PR's COMMIT LIST, so a new commit on top JOINS that list", |
| 706 | + ' and leaves the message above in it; the only thing that would remove it is the rewrite forbidden', |
| 707 | + ' above. Three measured facts, so this red can be READ rather than acted on:', |
| 708 | + '', |
| 709 | + ' 1. This check run is advisory at the branch-protection layer: it is absent from the', |
| 710 | + ' required-context registry, and its workflow subscribes to no `merge_group` event because a', |
| 711 | + ' queue build carries no PR body for it to judge.', |
| 712 | + ' 2. The squash message is assembled from the COMMIT messages, not from the PR body. Measured on', |
| 713 | + " 0a61db1f5, the squash of PR #16646: the landed message is the branch commit's, verbatim,", |
| 714 | + " carrying its `Refs #16624`; the body's `Fixes #16624` is nowhere in it.", |
| 715 | + ' 3. The card relation is safe either way. #16624 closed on that same merge although no commit', |
| 716 | + " message named a closing keyword for it — the PR BODY's keyword is what acts. Declaring the", |
| 717 | + ' relation once in the body is the whole contract, and it already works.', |
| 718 | + '', |
| 719 | + ' So the residue of landing this red is the trailer above sitting in the permanent history, and', |
| 720 | + ' what that costs depends on its spelling: `Part of` and `Refs` land as a reference and move no', |
| 721 | + " card, while a CLOSING keyword lands on the surface GitHub's parser reads. ⛔ Weigh that under the", |
| 722 | + ' landing rules that bind you — this gate found a real contradiction between your commits and your', |
| 723 | + ' body, and it does not decide whether the pull request merges.', |
663 | 724 | ); |
664 | 725 | } |
665 | 726 | return { exit: EXIT_CONTRADICTION, lines: [...lines, ...(unread.length ? ['', ...unread] : [])] }; |
@@ -881,6 +942,43 @@ function selfTest() { |
881 | 942 | named.includes(RELATION_CONTRACT), |
882 | 943 | true, |
883 | 944 | ); |
| 945 | + // The guidance text itself, pinned. This gate once told the author to push |
| 946 | + // reworded commits while also forbidding a rewrite — unsatisfiable on a |
| 947 | + // pushed branch, and a seat that read it instructed an amend and force-push. |
| 948 | + // These six hold the repaired shape: the forbidden action stays forbidden, |
| 949 | + // the reachable remedy is scoped to an UNPUSHED branch, the pushed case is |
| 950 | + // named as permanent, the measured squash fact replaces the false one, and |
| 951 | + // the gate still refuses to decide the merge. |
| 952 | + t( |
| 953 | + 'the guidance still forbids amend, rebase and force-push', |
| 954 | + /⛔ Do NOT amend, rebase or force-push/.test(named), |
| 955 | + true, |
| 956 | + ); |
| 957 | + t( |
| 958 | + 'the reachable remedy is scoped to a branch that is NOT pushed yet', |
| 959 | + named.includes('UNPUSHED') && named.includes('BRANCH NOT PUSHED YET'), |
| 960 | + true, |
| 961 | + ); |
| 962 | + t( |
| 963 | + 'the already-pushed case is named, and named as clearing for nobody', |
| 964 | + named.includes('BRANCH ALREADY PUSHED') && named.includes('no author action clears this red'), |
| 965 | + true, |
| 966 | + ); |
| 967 | + t( |
| 968 | + 'the guidance no longer claims the squash message comes from the PR body', |
| 969 | + /squash message from that body/.test(named), |
| 970 | + false, |
| 971 | + ); |
| 972 | + t( |
| 973 | + 'the guidance states the MEASURED squash fact instead', |
| 974 | + named.includes('assembled from the COMMIT messages, not from the PR body') && named.includes('0a61db1f5'), |
| 975 | + true, |
| 976 | + ); |
| 977 | + t( |
| 978 | + 'the guidance leaves the landing decision to the lander rather than ordering a merge', |
| 979 | + named.includes('does not decide whether the pull request merges'), |
| 980 | + true, |
| 981 | + ); |
884 | 982 | t( |
885 | 983 | 'an ordinary commit message with no card relation is clean', |
886 | 984 | commitVerdict('fix(cli): stop counting the walk instead of the tree\n\nBody prose about the change.\n').exit, |
|
0 commit comments