@@ -2155,6 +2155,13 @@ export function branchNameTarget(ref) {
21552155 * not narrow it here to serve H8: that would make the live half invisible to
21562156 * the rows that exist to see it.
21572157 *
2158+ * ⭐ And ⛔ not to serve H31 either (#18229). That row needs a stronger question
2159+ * — "does this PR CLOSE the card", because the gate's clearing stroke rides the
2160+ * landing — and it asks it of `bindingClosesCard` as a filter over the
2161+ * population THIS relation hands it. The narrowing is the READER's and lives
2162+ * beside the row that needs it; the relation stays wide, so H8, H31, H35 and
2163+ * H53 still cannot disagree about which PR delivers which card.
2164+ *
21582165 * ## The boolean is DERIVED from `deliveryEvidence`, and stays byte-identical
21592166 *
21602167 * The verdict is now one `!== null` over `deliveryEvidence` below rather than a
@@ -3014,12 +3021,24 @@ export const PM_STATE_LABELS = [
30143021 'finding',
30153022];
30163023
3024+ /**
3025+ * The ruling-anchor state: a card that stays OPEN by design, so nothing that
3026+ * fires on a CLOSURE ever fires on it. H9's header carries the measurement
3027+ * (`Restart-when: closed …#5499` parked a card forever because #5499 is one of
3028+ * these), and H31 reads it for the same structural reason — a gate hung on a
3029+ * card nothing closes is never cleared by the stroke that clears gates.
3030+ *
3031+ * One spelling, two readers: a second literal here is exactly the drift that
3032+ * lets one reader move and the other keep answering the old way.
3033+ */
3034+ export const TRACKING_ANCHOR_LABEL = 'tracking';
3035+
30173036/**
30183037 * Labels whose NORMAL shape is domain-without-pm-state, excluded by the
30193038 * sweep's own protocol text (SKILL.md, Backlog sweep): flagging them would
30203039 * report the protocol's design as a defect.
30213040 */
3022- export const H13_EXEMPT_LABELS = ['tracking' , 'status:parked', 'qa-run'];
3041+ export const H13_EXEMPT_LABELS = [TRACKING_ANCHOR_LABEL , 'status:parked', 'qa-run'];
30233042
30243043/**
30253044 * H13 threshold — "one sweep cycle": the triage Routine fires HOURLY and its
@@ -6045,6 +6064,44 @@ export function h30QueueRotting(issue, nowMs = Date.now()) {
60456064// governs enqueue and landing while the PR is open, and a merged carrier is a
60466065// closed-out stroke rather than a live half-write.
60476066//
6067+ // ## Which binding makes a CARRIER PAIR (#18229)
6068+ //
6069+ // The comparison needs two carriers of ONE gate, and the gate is cleared by the
6070+ // stroke that lands the increment. So the binding that puts a card into it is
6071+ // the one that makes that landing reach the card: a CLOSING KEYWORD. A
6072+ // `Part of #N` line is the opposite declaration — it says the PR is a MEMBER of
6073+ // what #N tracks — and an epic tracker is by construction a card no PR closes.
6074+ //
6075+ // Measured, anchor #9857's 2026-09-14T19:45Z sweep: card #14122 (`tracking`, the
6076+ // one-artifact/N-packages epic) drew a row saying the gate was missing from the
6077+ // card half of a dual carrier, while the two PRs the row named — #18212 and
6078+ // #18213 — each carried `Fixes` for their OWN card (#18202, #18204), each of
6079+ // which carried the gate, and each said in the same line the predicate read that
6080+ // #14122 is a tracker that stays open. Both real dual carriers were intact. The
6081+ // row's own remedy text is action-shaped, so acting on it means hanging
6082+ // `needs:contract-review` on a card nothing closes: the clearing stroke never
6083+ // arrives and the gate sits there as a permanent false blocker. This false
6084+ // positive costs a WRITE nothing later removes, and every correctly gated
6085+ // sub-PR re-manufactures it on every sweep, forever.
6086+ //
6087+ // ⛔ The shared relation is NOT narrowed — `prDeliversCard`'s docblock forbids
6088+ // that, and H8's open side, H35's sibling resolver, `claimDelivery` and the
6089+ // pairing `check-clause2-carriers` derives all still read it wide. The narrowing
6090+ // is H31's OWN, one filter over the population THIS row judges, so the rows
6091+ // still agree about which PR delivers which card and differ only about which
6092+ // binding makes a carrier pair — which is a question only this row asks.
6093+ //
6094+ // ## …and every other binding is DECLINED, never silently dropped
6095+ //
6096+ // A silent drop would be #4690 in this row's own uniform: a split that was never
6097+ // judged would render exactly like a board whose two carriers agree, and this is
6098+ // the one row that can tell 「被剥」 from 「从未挂过」. So a weak-bound delivering PR
6099+ // whose carrier DISAGREES with the card's still produces a row — one that names
6100+ // both carriers and the binding it read, says the comparison is not answerable
6101+ // from that binding, and prescribes NOTHING. A weak-bound PR whose carrier
6102+ // AGREES produces nothing, exactly as before: there is no split to report, and a
6103+ // standing row per tracker per sweep is the disease above, not its cure.
6104+ //
60486105// Report-only, and emphatically: this is a GATE. ⛔ Never a label written from
60496106// this script — a sweeper that hung or cleared a review gate would be issuing
60506107// the review verdict, and the one thing the whole clause-② chain forbids is
@@ -6054,9 +6111,43 @@ export function h30QueueRotting(issue, nowMs = Date.now()) {
60546111/** The clause-② gate label — one constant, both carriers. */
60556112export const CONTRACT_REVIEW_LABEL = 'needs:contract-review';
60566113
6114+ /**
6115+ * Does this PR's binding to card `n` make GitHub CLOSE the card on merge — the
6116+ * binding H31's carrier comparison needs (#18229)?
6117+ *
6118+ * One read of `deliveryEvidence`'s grading, ⛔ never a second keyword parser:
6119+ * that function grades the closing keyword FIRST and never lets position
6120+ * downgrade it, so a body carrying both `Fixes #N` and `Part of #N` answers
6121+ * `true` here — which is exactly what GitHub does on merge. The three weaker
6122+ * kinds answer `false`: `part-of` and `part-of-inline` declare MEMBERSHIP, and
6123+ * `branch-name` is a body that declared nothing at all, so neither says the
6124+ * landing reaches this card.
6125+ *
6126+ * ⚠️ It answers "does this PR CLOSE #n", never "does this PR deliver #n" —
6127+ * `prDeliversCard` owns the second question and stays wide for the readers that
6128+ * need a half in flight to be visible. ⛔ Do not substitute one for the other.
6129+ *
6130+ * Exported because the sibling shape is live one file over: the clause-② dual
6131+ * carrier that `check-clause2-carriers --pair` demands on an epic tracker
6132+ * reached through a `Part of` line (#18214) is the same question, and that fix
6133+ * wants this predicate rather than a second copy of it.
6134+ */
6135+ export function bindingClosesCard(pr, n) {
6136+ return deliveryEvidence(pr, n) === 'closing-keyword';
6137+ }
6138+
60576139/**
60586140 * H31 — null when the two carriers agree (or the comparison is not yet
6059- * possible), else the finding sentence.
6141+ * possible), else the finding sentence, else — for a delivering PR bound to
6142+ * this card by something other than a closing keyword — the DECLINED sentence.
6143+ *
6144+ * Three outcomes, never two (#18229). A closing-keyword binding is adjudicated
6145+ * exactly as before, byte for byte. A weaker binding is never adjudicated and
6146+ * never silently dropped: it produces a row only when the two carriers actually
6147+ * differ, and that row states that it declined and prescribes no write. The
6148+ * declined row is deliberately NOT marked `UNJUDGED_MARKER`: that marker buys
6149+ * trim priority ahead of judged rows, and a decline must never sort ahead of a
6150+ * real carrier split in the same `gate` band.
60606151 *
60616152 * A PR row whose `labels` is not an array is one this sweep could not read, and
60626153 * it is EXCLUDED from the comparison rather than counted as unlabelled: reading
@@ -6076,8 +6167,15 @@ export function h31ContractReviewCarrierSplit(issue, openPrs) {
60766167 );
60776168 if (delivering.length === 0) return null; // card-side-first is legal — see the header note.
60786169 const cardGated = labelNames(issue ?? {}).includes(CONTRACT_REVIEW_LABEL);
6079- const gatedPrs = delivering.filter((pr) => labelNames(pr).includes(CONTRACT_REVIEW_LABEL));
6080- const barePrs = delivering.filter((pr) => !labelNames(pr).includes(CONTRACT_REVIEW_LABEL));
6170+ // The population split the header argues for (#18229): only a closing keyword
6171+ // makes a landing that reaches THIS card, so only those PRs are a carrier pair
6172+ // with it. The rest are reported below, never judged, and never dropped.
6173+ const judged = delivering.filter((pr) => bindingClosesCard(pr, n));
6174+ const declined = delivering.filter((pr) => !bindingClosesCard(pr, n));
6175+ const gatedIn = (prs) => prs.filter((pr) => labelNames(pr).includes(CONTRACT_REVIEW_LABEL));
6176+ const bareIn = (prs) => prs.filter((pr) => !labelNames(pr).includes(CONTRACT_REVIEW_LABEL));
6177+ const gatedPrs = gatedIn(judged);
6178+ const barePrs = bareIn(judged);
60816179 // With its evidence (#16706) — this row names a PR as DELIVERING the card,
60826180 // and a reader clearing a gate off it needs to know whether that rests on a
60836181 // closing keyword or on a `Part of` that sat mid-line.
@@ -6107,7 +6205,33 @@ export function h31ContractReviewCarrierSplit(issue, openPrs) {
61076205 `demonstrably still live one carrier over. ${contract}`
61086206 );
61096207 }
6110- return null;
6208+ // Nothing adjudicable, or the adjudicable pair agrees. What is left is the
6209+ // weak-bound half: a PR this sweep believes delivers the card on evidence
6210+ // that does not close it. A DIFFERENCE there is still reportable — silence
6211+ // would render it identically to agreement, which is the one confusion this
6212+ // row exists to end — but it is not adjudicable, so the row says exactly that
6213+ // and asks for no write. Agreement here stays silent, as it was before.
6214+ const unjudgeable = cardGated ? bareIn(declined) : gatedIn(declined);
6215+ if (unjudgeable.length === 0) return null;
6216+ const anchorClause = labelNames(issue ?? {}).includes(TRACKING_ANCHOR_LABEL)
6217+ ? ` The card carries \`${TRACKING_ANCHOR_LABEL}\` — a ruling-anchor state that stays OPEN by design, the ` +
6218+ 'same property that keeps a `Restart-when: closed …#N` exit from ever firing on one — so no PR will ' +
6219+ 'close it, a gate hung here would never be reached by the stroke that clears gates, and it would sit ' +
6220+ 'as a permanent blocker on a card whose sub-PRs are gated correctly one level down. '
6221+ : ' ';
6222+ const direction = cardGated
6223+ ? `the CARD carries \`${CONTRACT_REVIEW_LABEL}\` while the open PR ${list(unjudgeable)} does NOT`
6224+ : `\`${CONTRACT_REVIEW_LABEL}\` is on the open PR ${list(unjudgeable)} while the CARD does NOT carry it`;
6225+ return (
6226+ `${direction} — and this row DECLINES to judge that pair, which is NOT the same as reporting it ` +
6227+ 'clean. The binding between the two is the one printed beside the PR number, and it is not a closing ' +
6228+ 'keyword: a `Part of` line declares MEMBERSHIP in what this card tracks and a branch-name fallback ' +
6229+ 'declares nothing at all, so neither says this PR\'s landing closes this card — and the gate\'s clearing ' +
6230+ `stroke rides that landing.${anchorClause}⛔ The row prescribes NOTHING: do not hang the gate on the ` +
6231+ 'card and do not clear it off the PR on the strength of it. What is owed first is a reading of the PR ' +
6232+ 'body — decide which card that PR actually delivers; only if a real half-write is behind it does anyone ' +
6233+ `write a carrier, and then it is the review's owner writing both in one stroke. ${contract}`
6234+ );
61116235}
61126236
61136237// ---------------------------------------------------------------------------
0 commit comments