From 608d6b25a6cd4ba7e2bc7a5d03754b02b720105b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 05:17:11 +0000 Subject: [PATCH 1/2] fix(pm): let every delivery row state the evidence it rests on `prDeliversCard` answers one bit, and five readers print rows from it -- H8's open side, H31's carrier comparison, `claimDelivery`, the pairing `check-clause2-carriers` derives, and `prFullyDeliversCard`. A `true` sourced from a closing keyword and a `true` sourced from the two words "part of" landing in an accounting sentence printed identically, so a row manufactured by the second was indistinguishable from a real finding. Add `deliveryEvidence` beside the boolean, returning the source kind, and thread it into what each of the five readers prints. `prDeliversCard` now derives its boolean from it, so the two can never be edited apart; every input answers exactly what it answered before. The relation is NOT narrowed -- the position is reported, never rejected. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY --- scripts/pm/check-clause2-carriers.mjs | 31 +++- scripts/pm/check-half-states.mjs | 195 ++++++++++++++++++++++++-- 2 files changed, 213 insertions(+), 13 deletions(-) diff --git a/scripts/pm/check-clause2-carriers.mjs b/scripts/pm/check-clause2-carriers.mjs index a7dfc0e225..99bb14fe57 100644 --- a/scripts/pm/check-clause2-carriers.mjs +++ b/scripts/pm/check-clause2-carriers.mjs @@ -338,6 +338,8 @@ import { EXIT_PREREQUISITE_NOT_MET, PROXY_FLAG, SWEEP_REPO_SHAPE, + deliveryEvidence, + deliveryEvidenceNote, governingClaim, isGateSemanticLabel, labelNames, @@ -643,7 +645,16 @@ export function c1CarrierSplit(pair) { const onPr = gated(pair?.prLabels); if (onCard === null || onPr === null) return null; // unreadable — accounted as UNJUDGED, not clean. if (onCard === onPr) return null; - const draft = pair.draft ? ' (draft)' : ''; + // The PAIRING's own evidence (#16706). This row's dangerous half tells a + // reader a live fail-open is in front of them, and the measured specimen that + // produced one was a pair derived from an accounting sentence — so the row + // states what the pairing rests on, in the same breath as the consequence. + // A pair from a caller that predates the field carries no `evidence` at all; + // it prints as it always did rather than claiming a reading nobody took. + const bits = []; + if (pair.draft) bits.push('draft'); + if (pair?.evidence !== undefined) bits.push(deliveryEvidenceNote(pair.evidence)); + const draft = bits.length > 0 ? ` (${bits.join(', ')})` : ''; if (onCard && !onPr) { return ( `\`${CONTRACT_REVIEW_LABEL}\` on card #${pair.card} while its delivering open PR ` + @@ -1355,6 +1366,14 @@ export function pairUnjudged(pair) { * its card, and every branch name carries the fallback too — 7 of 7, both * channels. Pairing is a derivation here, not an assumption. * + * Each pair carries the EVIDENCE its derivation rests on (#16706), because the + * derivation is exactly where a false pair enters this file: a body whose prose + * said 「part of #N already landed」 about another card derived a pair that + * never existed, and C1 then reported its dangerous half against it in the same + * words a real split gets. The kind rides on the pair so the row can say what + * it was built from; ⛔ it is never a filter here — a pair is derived exactly + * when `prDeliversCard` says so, as before. + * * @param {object[]} openPrs * @param {number[]} cardNumbers — the open cards the sweep holds. */ @@ -1363,7 +1382,15 @@ export function derivePairs(openPrs, cardNumbers) { for (const pr of openPrs ?? []) { if (!pr || pr.merged_at) continue; for (const n of cardNumbers ?? []) { - if (prDeliversCard(pr, String(n))) pairs.push({ pr: pr.number, card: Number(n), draft: Boolean(pr.draft), prRow: pr }); + if (prDeliversCard(pr, String(n))) { + pairs.push({ + pr: pr.number, + card: Number(n), + draft: Boolean(pr.draft), + evidence: deliveryEvidence(pr, String(n)), + prRow: pr, + }); + } } } return pairs; diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index 0ecccb5b17..a1e225b9fb 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -1526,6 +1526,49 @@ function partOfRe() { return /\bPart of\s+#(\d+)\b/gi; } +/** + * The same relation, restricted to the DECLARATION POSITION — `Part of #N` + * alone on a line or at the start of one, after the markers a list item or a + * blockquote puts in front of it. + * + * ⚠️ This is a LABEL, never a filter. `partOfTargets` above is unchanged and + * every reader still sees exactly the set it saw before; this predicate only + * answers *where* a match sat, so a row can say what its belief rests on. The + * distinction it draws is POSITIONAL and mechanical — "at the declaration + * position" / "elsewhere on a line" — and deliberately not a judgement about + * whether the surrounding words are prose: a semantic "mid-sentence" test is + * the start of a second dialect, and the corpus below shows the position alone + * does not settle authorship either. + * + * ## Why the relation is NOT narrowed to this position (the measured reason) + * + * Narrowing `partOfRe` to the declaration position was the obvious remedy and + * it is REFUSED on a corpus reading, not on taste. Measured over the 299 merged + * PR bodies of `objectstack-ai/objectstack` reachable in three pages of 100 + * (2026-09-08) and, on the commit surface, over the complete 512-commit window + * of this checkout: + * + * surface `Part of #N` at the declaration position / elsewhere + * body 29 / 1 + * commit 34 / 0 + * + * The single body-surface match elsewhere is a REAL declaration, not prose — + * PR #16543's opening line reads + * + * Refs #15858 (item 1) · Part of #15858 (item 1 of the two the card names). + * + * where `Part of` is second on the line only because the author put the two + * declarations side by side. That body carries no closing keyword, so a + * narrowed extractor would empty its `partOf` set and drop `prDeliversCard` + * through to the branch-name fallback — the channel its own docblock reserves + * for bodies that declare NOTHING. One real declaration lost is the cost the + * ⛔ in `prDeliversCard`'s docblock names, so the relation stays wide and the + * position is reported instead. + */ +function partOfDeclarationRe() { + return /^[ \t]*(?:>[ \t]*)*(?:(?:[-*+]|\d{1,9}[.)])[ \t]+)?[*_]{0,3}Part of\s+#(\d+)\b/gim; +} + /** * Blank out markdown code — fenced blocks and inline spans — so the scan sees * only the text GitHub's own reference parser acts on. @@ -1620,6 +1663,17 @@ export function partOfTargets(body, { markdown = true } = {}) { return new Set([...text.matchAll(partOfRe())].map((m) => m[1])); } +/** + * The subset of `partOfTargets` whose match sat at the DECLARATION POSITION. + * A strict subset by construction — same surface handling, same word, a + * stricter prefix — so a caller can only ever use it to grade a match that + * `partOfTargets` already returned, never to find one it did not. + */ +export function partOfDeclarationTargets(body, { markdown = true } = {}) { + const text = markdown ? stripMarkdownCode(body) : String(body ?? ''); + return new Set([...text.matchAll(partOfDeclarationRe())].map((m) => m[1])); +} + /** * The third relation a PR body can declare to a card, after `Part of #N` and * a closing keyword: `Refs #N` — the spelling the partial-dispatch rule makes a @@ -1752,17 +1806,110 @@ export function branchNameTarget(ref) { * derives — wants this wide one, because a half in flight is live work. ⛔ Do * not narrow it here to serve H8: that would make the live half invisible to * the rows that exist to see it. + * + * ## The boolean is DERIVED from `deliveryEvidence`, and stays byte-identical + * + * The verdict is now one `!== null` over `deliveryEvidence` below rather than a + * second copy of the same precedence. That is anti-drift, not a change: the + * evidence function reproduces this function's three steps in the same order, + * so every input answers exactly what it answered before, and the two can never + * be edited apart — the failure mode a sibling predicate invites. The ⛔ above + * still binds: the relation is not narrowed, only ATTRIBUTED. */ export function prDeliversCard(pr, n) { + return deliveryEvidence(pr, n) !== null; +} + +/** + * WHY this PR is believed to deliver card `n` — the evidence kind behind + * `prDeliversCard`'s boolean, or `null` when it does not deliver at all. + * + * ## The defect this exists for (#16706) + * + * `prDeliversCard` answers one bit, and five readers print rows from it — H8's + * open side, H31's carrier comparison, `claimDelivery`, the pairing + * `check-clause2-carriers` derives, and `prFullyDeliversCard`, which calls it + * first. A `true` sourced from a closing keyword and a `true` sourced from the + * two words `part of` landing in an accounting sentence printed IDENTICALLY, so + * a row manufactured by the second was indistinguishable from a real finding — + * measured two-sidedly on objectui PR #8354, whose "Serial constraints" section + * said 「part of #7918 already landed …」 about a *different* card with its own + * separate PR, and whose C1 row then told a reader that a live fail-open was in + * front of them. Deleting that one sentence flipped the reading back. + * + * This adds INFORMATION and narrows nothing — the distinction the card's own + * remedy 3 asks for. The relation `prDeliversCard` reports is untouched, which + * is what keeps it clear of the ⛔ in that function's docblock: that ⛔ governs + * *which declarations count*, and every declaration that counted still counts. + * + * ## The kinds, in the precedence `prDeliversCard` already used + * + * `closing-keyword` a closing keyword bound to `#N` — GitHub closes the card + * on merge, the strongest evidence there is + * `part-of` `Part of #N` at the declaration position + * `part-of-inline` `Part of #N` elsewhere on a line — a real declaration + * written beside another one, or the accounting sentence + * above. ⚠️ POSITION, not authorship: a reader owes this + * one a look, and the corpus at `partOfDeclarationRe` + * records why it is not simply rejected. + * `branch-name` no declaration in the body at all; the card comes from + * `claude/issue--`, the fallback whose cost is + * argued at `prDeliversCard` + * + * ⛔ The closing keyword is graded FIRST and the position never downgrades it: + * a body carrying `Fixes #N` delivers `#N` whatever else it says about it, so a + * stray inline `part of #N` beside a real keyword must not make the row read as + * unattributed. + */ +export function deliveryEvidence(pr, n) { const target = String(n); const body = pr?.body ?? ''; const partOf = partOfTargets(body); const closing = closingKeywordTargets(body); - if (partOf.has(target) || closing.has(target)) return true; + if (closing.has(target)) return 'closing-keyword'; + if (partOf.has(target)) { + return partOfDeclarationTargets(body).has(target) ? 'part-of' : 'part-of-inline'; + } // The body spoke — about some OTHER card. A stale branch name does not // overrule it (the re-scope case above). - if (partOf.size > 0 || closing.size > 0) return false; - return branchNameTarget(pr?.head?.ref) === target; + if (partOf.size > 0 || closing.size > 0) return null; + return branchNameTarget(pr?.head?.ref) === target ? 'branch-name' : null; +} + +/** + * The phrase a finding row prints beside a PR number, so the row states its own + * evidence instead of leaving every `true` looking alike. + * + * Kept short on purpose: these render INSIDE sentences that already carry a + * contract paragraph, and a row nobody finishes reading reports nothing. The + * two weak channels are the ones that get the ⚠️, because they are the two a + * reader must verify before acting. + */ +export function deliveryEvidenceNote(kind) { + switch (kind) { + case 'closing-keyword': + return 'via a closing keyword'; + case 'part-of': + return 'via a `Part of` declaration'; + case 'part-of-inline': + return '⚠️ via `Part of` NOT at the declaration position — verify it declares a delivery rather than mentioning the card in passing'; + case 'branch-name': + return '⚠️ via the branch-name fallback, the body declaring nothing'; + default: + return 'evidence unread'; + } +} + +/** + * `#8354 (draft, ⚠️ via …)` — one PR reference with its evidence, for the rows. + * + * One renderer, so the five readers cannot drift into five spellings of the + * same fact; the `draft` marker keeps the position it already had in every row + * that carried one. + */ +export function deliveryRef(pr, n) { + const draft = pr?.draft ? 'draft, ' : ''; + return `#${pr?.number} (${draft}${deliveryEvidenceNote(deliveryEvidence(pr, n))})`; } /** @@ -1845,8 +1992,15 @@ export function h8MergedPrStillDispatched(issue, mergedPrs, openPrs) { if (prFullyDeliversCard(pr, n)) delivering.push(pr); } if (delivering.length === 0) return null; + // Each row states the evidence its delivery reading rests on (#16706): a + // `true` from a closing keyword and a `true` from a `Part of` that sat + // mid-line printed identically, and this row prescribes a DESTRUCTIVE write. const list = delivering - .map((p) => `#${p.number} (merged ${String(p.merged_at).slice(0, 10)})`) + .map( + (p) => + `#${p.number} (merged ${String(p.merged_at).slice(0, 10)}, ` + + `${deliveryEvidenceNote(deliveryEvidence(p, n))})`, + ) .join(', '); const stillOpen = []; @@ -1857,9 +2011,7 @@ export function h8MergedPrStillDispatched(issue, mergedPrs, openPrs) { if (prDeliversCard(pr, n)) stillOpen.push(pr); } if (stillOpen.length > 0) { - const openList = stillOpen - .map((p) => `#${p.number}${p.draft ? ' (draft)' : ''}`) - .join(', '); + const openList = stillOpen.map((p) => deliveryRef(p, n)).join(', '); const total = delivering.length + stillOpen.length; return ( `delivered IN PART — ${delivering.length} of ${total} delivering PR(s) merged ` + @@ -5007,7 +5159,10 @@ export function h31ContractReviewCarrierSplit(issue, openPrs) { const cardGated = labelNames(issue ?? {}).includes(CONTRACT_REVIEW_LABEL); const gatedPrs = delivering.filter((pr) => labelNames(pr).includes(CONTRACT_REVIEW_LABEL)); const barePrs = delivering.filter((pr) => !labelNames(pr).includes(CONTRACT_REVIEW_LABEL)); - const list = (prs) => prs.map((p) => `#${p.number}${p.draft ? ' (draft)' : ''}`).join(', '); + // With its evidence (#16706) — this row names a PR as DELIVERING the card, + // and a reader clearing a gate off it needs to know whether that rests on a + // closing keyword or on a `Part of` that sat mid-line. + const list = (prs) => prs.map((p) => deliveryRef(p, n)).join(', '); const contract = 'The gate is a DUAL carrier — 「两边都挂好」, hung in one stroke and cleared in one ' + 'stroke, each carrier written read-modify-write with a READ-BACK ' + @@ -5267,12 +5422,30 @@ export function h27NeedsClaimLivenessRead(issue, claim, nowMs = Date.now()) { * is a bounded recency window (`MERGED_WINDOW_DAYS`). That asymmetry is what * the finding sentence has to disclose, so it is preserved here rather than * collapsed into a boolean. + * + * ## `evidence` — the one reader with no row of its own (#16706) + * + * H27, this function's only consumer, fires only when BOTH counts are zero, so + * a delivery it counted never reaches a printed sentence: the count's whole + * effect is to SUPPRESS the row. That makes the suppression the thing worth + * attributing — a card held silent by a delivery whose evidence is an inline + * `Part of` is a card nobody is told about, on the strength of a match this + * card measured as unreliable. So the kinds ride along on the return shape, + * additively (both counts keep their spelling and their meaning), for a reader + * or a report that asks why a dispatch was judged live. */ export function claimDelivery(n, openPrs, mergedPrs) { const target = String(n); - const open = (openPrs ?? []).filter((pr) => prDeliversCard(pr, target)).length; - const merged = (mergedPrs ?? []).filter((pr) => pr?.merged_at && prDeliversCard(pr, target)).length; - return { open, merged }; + const openRows = (openPrs ?? []).filter((pr) => prDeliversCard(pr, target)); + const mergedRows = (mergedPrs ?? []).filter((pr) => pr?.merged_at && prDeliversCard(pr, target)); + return { + open: openRows.length, + merged: mergedRows.length, + evidence: [...openRows, ...mergedRows].map((pr) => ({ + pr: pr?.number, + kind: deliveryEvidence(pr, target), + })), + }; } /** From 08c0dd8d680badea6c1ea300c84331f9f8132415 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 05:19:50 +0000 Subject: [PATCH 2/2] test(pm): pin the two-sided reading and the evidence each reader prints Both sides of the specimen are the reading: one side alone cannot tell "fixed" from "changed to never report". The relation is deliberately unchanged -- the prose match still reports a delivery -- so what the two sides pin is the evidence the row now prints, plus the control that deleting the one sentence still removes the pair entirely. Five readers, five readings: H8's open side, H8's merged side through `prFullyDeliversCard`, H31's carrier comparison, `claimDelivery` (whose only consumer fires on ZERO delivery, so the kinds ride on the return shape -- its only surface), and the pairing `check-clause2-carriers` derives, pinned in that file's own battery together with its C1 row. Three existing pins move one spelling on: the row parenthetical now carries the evidence beside the draft marker, so `(draft)` is pinned as `(draft,`. Their subject is unchanged. The `Refs` axis gets the reading the ruling asked for: the hole is open there too, reported rather than narrowed. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY --- scripts/pm/check-clause2-carriers.mjs | 34 +++++++++ scripts/pm/check-half-states.mjs | 105 +++++++++++++++++++++++++- 2 files changed, 136 insertions(+), 3 deletions(-) diff --git a/scripts/pm/check-clause2-carriers.mjs b/scripts/pm/check-clause2-carriers.mjs index 99bb14fe57..234f0fd275 100644 --- a/scripts/pm/check-clause2-carriers.mjs +++ b/scripts/pm/check-clause2-carriers.mjs @@ -2301,6 +2301,40 @@ export function selfTest() { t('a PR body naming another card does NOT pair on a stale branch name', derivePairs([prRow(13910, 9999, 'claude/issue-13476-x')], [13476]).length === 0); t('a branch name is the fallback when the body says nothing', derivePairs([{ number: 1, labels: [], body: 'no refs here', head: { ref: 'claude/issue-13476-x' } }], [13476]).length === 1); + // -- #16706: the pair carries the EVIDENCE it was derived from ------------- + // + // ★ The measured specimen — objectui PR #8354, whose "Serial constraints" + // section said 「part of #7918 already landed …」 about a DIFFERENT card with + // its own separate PR. The pair below is the one this file derived from that + // sentence, and the C1 row it emitted told the reader a live fail-open was in + // front of them. The relation is unchanged, so the pair is still derived — + // what changed is that the row now says what it was built from. + const PROSE_8354 = + 'Note that **part of #7918** already landed as `4f9f1ee` (PR #8226, memoising two of ' + + 'the lazy getters); this PR does not touch the getters.'; + const body8354 = `Fixes #7760\n\n## Serial constraints\n\n- ${PROSE_8354}\n`; + const pr8354 = { number: 8354, draft: true, labels: [], body: body8354, head: { ref: 'claude/issue-7760-lazy-mirror-input-type-args' } }; + + const inlinePair = derivePairs([pr8354], [7918]); + t('#16706: the prose sentence still derives the pair — the relation is NOT narrowed here', inlinePair.length === 1); + t('#16706: …and the pair records that the match was not at the declaration position', inlinePair[0]?.evidence === 'part-of-inline'); + const keywordPair = derivePairs([pr8354], [7760]); + t('#16706: the real `Fixes` relation on the same body is graded as the strong channel', keywordPair[0]?.evidence === 'closing-keyword'); + // CONTROL — the same body with that ONE sentence deleted derives nothing. + t('#16706 control: deleting the sentence removes the pair entirely', derivePairs([{ ...pr8354, body: 'Fixes #7760\n\n## Serial constraints\n\n' }], [7918]).length === 0); + + // …and the C1 row PRINTS it — the row the card was filed about. + const c1Inline = c1CarrierSplit({ ...inlinePair[0], prLabels: [CONTRACT_REVIEW_LABEL], cardLabels: [], cardComments: [CLAIM('Clause-②: yes')] }); + t('#16706: the C1 dangerous half still fires on the derived pair', typeof c1Inline === 'string'); + t('#16706: …and now names the evidence beside the PR', String(c1Inline).includes('#8354 (draft, ⚠️ via `Part of` NOT at the declaration position')); + t('#16706: …while the consequence paragraph it always carried is untouched', String(c1Inline).includes('an ungated card is a card that was never')); + // A strong-channel pair prints the strong phrase, so the two are DISTINGUISHABLE + // in the row — which is the whole point of the card. + const c1Keyword = c1CarrierSplit({ ...keywordPair[0], prLabels: [CONTRACT_REVIEW_LABEL], cardLabels: [], cardComments: [CLAIM('Clause-②: yes')] }); + t('#16706: a keyword-sourced row reads differently from an inline-sourced one', String(c1Keyword).includes('via a closing keyword') && !String(c1Keyword).includes('NOT at the declaration position')); + // ⛔ A pair from a caller that predates the field claims no reading at all. + t('#16706: a pair with no evidence field prints exactly as it always did', String(c1CarrierSplit({ pr: 13910, card: 13476, draft: true, prLabels: [CONTRACT_REVIEW_LABEL], cardLabels: [], cardComments: [] })).includes('#13910 (draft)')); + // -- the three read paths --------------------------------------------------- // // The offline reader is exercised against the SAME predicates the live path diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index a1e225b9fb..a455bce347 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -15554,7 +15554,10 @@ async function selfTest() { t('H8 open: …and the merged half too', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('#10004'), true); t('H8 open: …and counts them, N of M', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('1 of 2'), true); // A draft open half is the specimen's own shape — never filtered out. - t('H8 open: …and marks the open half as a draft', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('(draft)'), true); + // The parenthetical carries the delivery EVIDENCE beside the draft marker + // since #16706, so the marker is pinned with its trailing comma rather than + // its old closing paren — still "is this row marked a draft", one spelling on. + t('H8 open: …and marks the open half as a draft', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('(draft,'), true); t('H8 open: a NON-draft open half counts identically', typeof halves([openHalf(10226, 'Part of #9834', false)]), 'string'); // …and the row it replaces is unchanged whenever every deliverer HAS merged — @@ -15597,6 +15600,102 @@ async function selfTest() { true, ); + // -- #16706: every delivery row states the EVIDENCE it rests on ----------- + // + // ★ Specimen — objectui PR #8354, whose body opened `Fixes #7760` and whose + // "Serial constraints" section carried the accounting sentence below about a + // DIFFERENT card (#7918, which had its own separate PR). `partOfTargets` + // matched the token sequence, so five readers were told #8354 delivered + // #7918, and `check-clause2-carriers` emitted a C1 row whose text tells the + // reader a live fail-open is in front of them. + // + // ⚖️ Two-sided, and BOTH sides are the reading: one side alone cannot tell + // "fixed" from "changed to never report". The relation is deliberately + // UNCHANGED — the prose match still reports a delivery — so what the two + // sides pin here is the EVIDENCE the row now prints, plus the control that + // deleting the sentence still removes the pair entirely. + const PROSE_8354 = + 'Note that **part of #7918** already landed as `4f9f1ee` (PR #8226, memoising two of ' + + 'the lazy getters); this PR does not touch the getters.'; + const body8354 = `Fixes #7760\n\n## Serial constraints\n\n- ${PROSE_8354}\n`; + const clean8354 = 'Fixes #7760\n\n## Serial constraints\n\n'; + const pr8354 = (body) => ({ number: 8354, body, draft: true, merged_at: null, head: { ref: 'claude/issue-7760-lazy-mirror-input-type-args' } }); + + t('#16706 specimen: the prose sentence still reports a delivery — the relation is NOT narrowed', prDeliversCard(pr8354(body8354), '7918'), true); + t('#16706 specimen: …and the row can now say the match was not at the declaration position', deliveryEvidence(pr8354(body8354), '7918'), 'part-of-inline'); + // CONTROL — same body, that ONE sentence deleted, nothing else changed. + t('#16706 control: deleting the sentence removes the pair entirely', prDeliversCard(pr8354(clean8354), '7918'), false); + t('#16706 control: …and there is no evidence to report either', deliveryEvidence(pr8354(clean8354), '7918'), null); + // NEGATIVE — the real relation on the same body, in the same run, unmoved. + t('#16706 negative: `Fixes #7760` still parses on the specimen body', prDeliversCard(pr8354(body8354), '7760'), true); + t('#16706 negative: …and is graded as the strongest evidence there is', deliveryEvidence(pr8354(body8354), '7760'), 'closing-keyword'); + t('#16706 negative: …in the control body too', deliveryEvidence(pr8354(clean8354), '7760'), 'closing-keyword'); + // NEGATIVE — a real line-leading declaration keeps delivering, and is graded + // as the declaration it is. + t('#16706 negative: a line-leading `Part of #7918` still delivers', prDeliversCard({ number: 1, body: 'Part of #7918' }, '7918'), true); + t('#16706 negative: …and is graded `part-of`, not the inline kind', deliveryEvidence({ number: 1, body: 'Part of #7918' }, '7918'), 'part-of'); + t('#16706 negative: a list-item declaration is the declaration position too', deliveryEvidence({ number: 1, body: '- Part of #7918' }, '7918'), 'part-of'); + t('#16706 negative: …and a blockquoted one', deliveryEvidence({ number: 1, body: '> Part of #7918' }, '7918'), 'part-of'); + t('#16706 negative: …and a bold-wrapped one', deliveryEvidence({ number: 1, body: '**Part of #7918**' }, '7918'), 'part-of'); + // ⛔ A closing keyword is graded FIRST: a stray inline `part of #N` beside a + // real `Fixes #N` must not make the row read as unattributed. + t('#16706: a closing keyword outranks an inline `Part of` for the SAME card', deliveryEvidence({ number: 1, body: 'Fixes #7918\n\nsee part of #7918 above' }, '7918'), 'closing-keyword'); + // The branch-name fallback is named as the weak channel it is. + t('#16706: the branch-name fallback is graded as such', deliveryEvidence({ number: 1, body: 'no declaration here', head: { ref: 'claude/issue-9834-x' } }, '9834'), 'branch-name'); + t('#16706: a body that spoke about ANOTHER card still overrules the branch name', deliveryEvidence({ number: 1, body: 'Part of #9999', head: { ref: 'claude/issue-9834-x' } }, '9834'), null); + + // ⭐ The anti-drift invariant: the boolean IS the evidence being non-null, so + // the two can never be edited apart. Driven over every fixture above. + for (const [label, row, card] of [ + ['specimen prose', pr8354(body8354), '7918'], + ['specimen control', pr8354(clean8354), '7918'], + ['specimen closing keyword', pr8354(body8354), '7760'], + ['line-leading declaration', { number: 1, body: 'Part of #7918' }, '7918'], + ['branch fallback', { number: 1, body: '', head: { ref: 'claude/issue-9834-x' } }, '9834'], + ['re-scoped branch', { number: 1, body: 'Part of #9999', head: { ref: 'claude/issue-9834-x' } }, '9834'], + ]) { + t(`#16706 invariant (${label}): prDeliversCard === (deliveryEvidence !== null)`, prDeliversCard(row, card), deliveryEvidence(row, card) !== null); + } + + // -- #16706: the kind reaches each of the FIVE readers' printed rows ------- + // The docblock on `prDeliversCard` enumerates them; every one gets a reading. + + // READER 1 — H8's OPEN side. + const openInline = { number: 8354, body: body8354, draft: true, merged_at: null }; + const h8Inline = String(h8MergedPrStillDispatched(dispatched(7918), [mergedPr(1, 'Part of #7918')], [openInline]) ?? ''); + t('#16706 reader H8-open: an inline-sourced open half says so in the row', h8Inline.includes('#8354 (draft, ⚠️ via `Part of` NOT at the declaration position'), true); + // READER 2 — H8's MERGED side, through `prFullyDeliversCard`. + const h8Merged = String(h8MergedPrStillDispatched(dispatched(7918), [mergedPr(8354, body8354)], []) ?? ''); + t('#16706 reader prFullyDeliversCard: the merged side still fires on the inline match', h8Merged.includes('#8354'), true); + t('#16706 reader prFullyDeliversCard: …and the merged row states the evidence beside the date', h8Merged.includes('⚠️ via `Part of` NOT at the declaration position'), true); + t('#16706 reader prFullyDeliversCard: …while a closing keyword prints the strong phrase', String(h8MergedPrStillDispatched(dispatched(7760), [mergedPr(8354, body8354)], []) ?? '').includes('via a closing keyword'), true); + // READER 3 — H31's carrier comparison. + const h31Card = { ...issue([]), number: 7918, state: 'open' }; + const h31Pr = { number: 8354, body: body8354, draft: true, merged_at: null, labels: [{ name: CONTRACT_REVIEW_LABEL }] }; + const h31Row = String(h31ContractReviewCarrierSplit(h31Card, [h31Pr]) ?? ''); + t('#16706 reader H31: the split row still fires', h31Row.length > 0, true); + t('#16706 reader H31: …and names the evidence the pairing rests on', h31Row.includes('#8354 (draft, ⚠️ via `Part of` NOT at the declaration position'), true); + // READER 4 — `claimDelivery`. Its only consumer (H27) fires on ZERO delivery, + // so the count's whole effect is to SUPPRESS a row; the kinds ride on the + // return shape, which is the only surface it has. + const cd = claimDelivery(7918, [openInline], []); + t('#16706 reader claimDelivery: the count is unchanged', cd.open, 1); + t('#16706 reader claimDelivery: …and the suppression is attributable', cd.evidence[0].kind, 'part-of-inline'); + t('#16706 reader claimDelivery: …naming the PR it came from', cd.evidence[0].pr, 8354); + t('#16706 reader claimDelivery: a merged delivery is attributed too', claimDelivery(7760, [], [mergedPr(8354, body8354)]).evidence[0].kind, 'closing-keyword'); + // READER 5 is `derivePairs`/C1, which lives in `check-clause2-carriers.mjs` + // and is pinned in that file's own self-test. + + // -- #16706: the `Refs` axis — the reading the ruling asked for ----------- + // The hole is OPEN on this axis too: `refsRe` is documented as "same + // strictness as `partOfRe`" and shares its shape, so an ordinary sentence + // reaches `refsTargets` exactly as the accounting sentence reached + // `partOfTargets`. It is reported rather than narrowed — same corpus + // discipline, and narrowing one of the two would break the symmetry their + // docblocks assert. + t('#16706 Refs axis: a prose `refs #N` is matched, exactly as `part of #N` is', refsTargets('The cleanup that refs #9999 already landed upstream.').has('9999'), true); + t('#16706 Refs axis: …and the protocol declaration still reads its item', refsTargets('Refs #9999 (item 2)').get('9999'), 'item 2'); + // -- H22: a CLOSED card still carrying a `pm:*` state label (#10688) ------- const closedCard = (labels, state_reason = 'completed') => ({ ...issue(labels), @@ -18447,7 +18546,7 @@ async function selfTest() { const bare = gatePr(11844, ['documentation', 'size/l', 'tests']); const gated = gatePr(11844, ['documentation', 'size/l', CONTRACT_REVIEW_LABEL]); t('H31: gated card + a bare delivering PR -> finding', typeof h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL, 'pm:dispatched']), [bare]), 'string'); - t('H31: …and it names the PR that is missing the carrier', says(h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [bare]), '#11844 (draft)'), true); + t('H31: …and it names the PR that is missing the carrier', says(h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [bare]), '#11844 (draft,'), true); t('H31: …and names both failure routes (hang never reached / PASS stopped half way)', says(h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [bare]), 'already passed'), true); t('H31: bare card + a gated delivering PR -> finding', typeof h31ContractReviewCarrierSplit(gateCard(['pm:dispatched']), [gated]), 'string'); t('H31: …and calls that the more dangerous half', says(h31ContractReviewCarrierSplit(gateCard([]), [gated]), 'more dangerous half'), true); @@ -18487,7 +18586,7 @@ async function selfTest() { labels: ['documentation', 'size/l', 'dependencies', 'tests', 'tooling'].map((name) => ({ name })), }; t('H31 live: #11427 gated while its delivering PR #11844 is not -> finding', typeof h31ContractReviewCarrierSplit(live11427, [live11844]), 'string'); - t('H31 live: …and the row names the PR', says(h31ContractReviewCarrierSplit(live11427, [live11844]), '#11844 (draft)'), true); + t('H31 live: …and the row names the PR', says(h31ContractReviewCarrierSplit(live11427, [live11844]), '#11844 (draft,'), true); // …and #10025, the other live carrier: gated, `pm:blocked`, no open PR at // all — the shape this row deliberately does NOT report. t('H31 live: #10025 (gated, no PR carrier yet) -> clean', h31ContractReviewCarrierSplit({ ...gateCard(['domain:services', 'pm:blocked', CONTRACT_REVIEW_LABEL]), number: 10025 }, [live11844]), null);