|
355 | 355 | * readings rather than diagnosing one. Report-only like everything here: |
356 | 356 | * the remedy is a dispatch or a withdrawn claim, never a label written |
357 | 357 | * from this script. |
| 358 | + * H21 an OPEN PULL REQUEST whose body binds a closing keyword to a `#N` the |
| 359 | + * body never declared itself `Part of`, inside a SENTENCE that reads as |
| 360 | + * not closing it ("Filed, not fixed: #10240", "out of scope: closes |
| 361 | + * #N"). H7's own rationale covers this whole class while H7's predicate |
| 362 | + * covers one spelling of it — H7 is bound to a `Part of #N` declaration |
| 363 | + * and a body that declares `Part of` for nothing is silent by |
| 364 | + * construction, however plainly it says the card stays open. Measured |
| 365 | + * specimen (#10392): PR #10241 carried no `Part of` and the sentence |
| 366 | + * "Filed, not fixed: #10240"; #10240 closed `completed` two seconds |
| 367 | + * after that merge and read as finished until a human reopened it a day |
| 368 | + * later. ⛔ The trigger is the NEGATION WINDOW and never keyword |
| 369 | + * presence: 277 of the 300 most recently merged bodies carry a closing |
| 370 | + * keyword bound to a number (301 matches), so a presence rule would |
| 371 | + * report every correct PR in the corpus. The negation is the author's |
| 372 | + * own statement of intent contradicting the instruction beside it, which |
| 373 | + * is what makes accident separable from intent at all. The window is a |
| 374 | + * SENTENCE, measured: widening it to the whole body turns 0 false |
| 375 | + * positives into 13, all of them one legitimate fourteen-card close |
| 376 | + * (#10714). Disjoint from H7 by construction — a number already declared |
| 377 | + * `Part of` is H7's row and is skipped here. Report-only, and |
| 378 | + * deliberately not imported by the blocking gate that reuses H7's |
| 379 | + * predicate: widening the class must not silently widen a check that |
| 380 | + * fails builds. |
358 | 381 | * |
359 | 382 | * ## The close mechanism, measured (#8293) |
360 | 383 | * |
@@ -2919,6 +2942,190 @@ export function h20DispatchedNoBranchRef(issue, claim, refStates, nowMs = Date.n |
2919 | 2942 | ); |
2920 | 2943 | } |
2921 | 2944 |
|
| 2945 | +// --------------------------------------------------------------------------- |
| 2946 | +// H21 — a closing keyword bound to `#N` inside a sentence that NEGATES it |
| 2947 | +// (#10392). H7's rationale, minus H7's `Part of` precondition. |
| 2948 | +// |
| 2949 | +// ## The gap, and the card it cost |
| 2950 | +// |
| 2951 | +// H7's header states the rule in fully general terms — GitHub's parser "matches |
| 2952 | +// the keyword plus the number and ignores the surrounding prose entirely, |
| 2953 | +// negations and modals included, so the sentence an author writes to PREVENT an |
| 2954 | +// auto-close is exactly what performs it on merge". The PREDICATE behind that |
| 2955 | +// sentence is narrower than the sentence: it is bound to a `Part of #N` |
| 2956 | +// declaration and fires only when the same `#N` carries both. A body that |
| 2957 | +// declares `Part of` for nothing is silent by construction, however plainly it |
| 2958 | +// says it is not closing the card. |
| 2959 | +// |
| 2960 | +// Measured specimen — PR #10241, merged 2026-08-20T15:10:06Z. Its body carries |
| 2961 | +// no `Part of` anywhere and this sentence under `## Out of scope`: |
| 2962 | +// |
| 2963 | +// Filed, not fixed: #10240 — the same leak through the **delete** verb. |
| 2964 | +// |
| 2965 | +// Issue #10240 closed `completed` at 15:10:08Z — two seconds later — with a |
| 2966 | +// closing-link summary naming #10241 and nothing else. #10240 is a genuine |
| 2967 | +// unfixed defect (attachment tombstoning no-ops on a predicate delete); it read |
| 2968 | +// as finished until a human reopened it a day later. The author wrote the |
| 2969 | +// sentence to record that the card was deliberately LEFT OPEN, and the sentence |
| 2970 | +// closed it. H7 was silent, exactly as designed. |
| 2971 | +// |
| 2972 | +// ## Why this is a window and not "flag every closing keyword" |
| 2973 | +// |
| 2974 | +// The naive widening is not available and the corpus says so quantitatively: a |
| 2975 | +// PR cannot declare which cards it intends to close except BY using the keyword, |
| 2976 | +// so a rule over keyword-presence alone cannot separate intent from accident. |
| 2977 | +// Over the 300 most recently merged PR bodies (below), 277 carry a closing |
| 2978 | +// keyword bound to a number and there are 301 such matches — flagging keyword |
| 2979 | +// presence would produce 301 findings, every one of them a correct PR. |
| 2980 | +// |
| 2981 | +// The negation is what makes the two separable, because it is the AUTHOR'S OWN |
| 2982 | +// statement of intent sitting in the same sentence as the instruction that |
| 2983 | +// contradicts it. That is a contradiction internal to one sentence, which is |
| 2984 | +// the same shape H7 already reports across `Part of` and a keyword. |
| 2985 | +// |
| 2986 | +// ## The window bound is load-bearing — measured, not assumed |
| 2987 | +// |
| 2988 | +// Corpus (2026-08-21, this change's own stage-1 measurement): the 300 most |
| 2989 | +// recently merged PRs into `main`, bodies as the API returns them, |
| 2990 | +// 2026-08-19T15:13:23Z … 2026-08-21T19:11:30Z, 2,564,259 body characters, no |
| 2991 | +// empty bodies. Read through `stripMarkdownCode`, exactly as H7 reads. |
| 2992 | +// |
| 2993 | +// window scope flagged / 301 keyword matches |
| 2994 | +// sentence (this rule) 1 — PR #10241, the specimen. 0 false positives. |
| 2995 | +// paragraph 1 — same single hit |
| 2996 | +// whole body before match 14 — 13 false positives |
| 2997 | +// anywhere in body 301 — the naive shape; useless |
| 2998 | +// |
| 2999 | +// All 13 whole-body false positives are ONE PR (#10714) which legitimately |
| 3000 | +// closes fourteen cards with a wall of `Fixes #N` lines and merely contains a |
| 3001 | +// negation word somewhere earlier in a long body. A body-scoped negation check |
| 3002 | +// would red-flag the most correct multi-close PR in the corpus thirteen times. |
| 3003 | +// That is why the window is a sentence and why it is stated in code rather than |
| 3004 | +// left to a reviewer's judgement. |
| 3005 | +// |
| 3006 | +// The result is not clean for lack of opportunity, which is the failure mode a |
| 3007 | +// 0-of-301 number invites: the same corpus carries 116 sentences in exactly the |
| 3008 | +// deliberate-non-closure register this rule reads — `## Out of scope`, |
| 3009 | +// "filed, not fixed here", "#N is not addressed here", "#N remains open". The |
| 3010 | +// register is everywhere; only one author put a closing keyword next to the |
| 3011 | +// number. Two near-misses are worth naming because they are the specimen's |
| 3012 | +// wording almost exactly and are correctly clean: #10876's "## Out of scope — |
| 3013 | +// filed, not repaired here" and #10851's "filed, not fixed here" — neither |
| 3014 | +// binds a keyword to a number. |
| 3015 | +// |
| 3016 | +// Second corpus, a different surface and the same answer: all 1,418 squash |
| 3017 | +// commit messages on `main` (2026-08-11 … 2026-08-21) — a surface GitHub's |
| 3018 | +// closing-keyword parser also reads — carry 228 keyword+`#N` matches across 197 |
| 3019 | +// commits and produce 0 flags. That arm contributes no true positive either: |
| 3020 | +// commit messages here do not carry the `## Out of scope` register at all. |
| 3021 | +// |
| 3022 | +// ## Report-only, and deliberately NOT wired to the blocking gate |
| 3023 | +// |
| 3024 | +// `scripts/check-partof-closing-keyword.mjs` imports `h7PartOfWithClosingKeyword` |
| 3025 | +// and FAILS a PR on it. This predicate is deliberately a separate function that |
| 3026 | +// that gate does not import, so widening the class cannot silently widen a |
| 3027 | +// blocking check. Report-only first is the commissioned order (#10392 triage, |
| 3028 | +// 2026-08-21): measure, ship the row, and let a promotion to blocking be its own |
| 3029 | +// decision with these numbers in hand. Nothing about this file's report-only |
| 3030 | +// contract changes, and the existing patrol workflow already calls the sweep, so |
| 3031 | +// no workflow edit is part of this. |
| 3032 | +// |
| 3033 | +// ## Disjoint from H7 by construction |
| 3034 | +// |
| 3035 | +// A number already declared `Part of #N` is H7's row and is skipped here, so the |
| 3036 | +// two never double-report one number. H7 keeps that class whether or not the |
| 3037 | +// sentence is negated (its own self-test pins a negated `Part of` body as an H7 |
| 3038 | +// finding); this rule takes the class H7 cannot see — a keyword bound to a |
| 3039 | +// number the body never declared itself part of. |
| 3040 | +// --------------------------------------------------------------------------- |
| 3041 | + |
| 3042 | +/** |
| 3043 | + * The negation / filing markers, EXACTLY as measured above. |
| 3044 | + * |
| 3045 | + * The set is pinned to the measured one on purpose: a marker added later |
| 3046 | + * without re-running the corpus would inherit a "0 false positives" number it |
| 3047 | + * was never measured under. Each was also measured ALONE against both corpora |
| 3048 | + * and each is independently clean; `not` and `filed` are the two that fire on |
| 3049 | + * the specimen. |
| 3050 | + * |
| 3051 | + * The bare noun `file`/`files` is deliberately NOT a marker although it too |
| 3052 | + * measured clean. It carries no negation or filing sense — "this file fixes |
| 3053 | + * #123" is a normal, correct close — and it is one of the most common nouns in |
| 3054 | + * this repo's prose, so it is the marker most likely to turn into a false |
| 3055 | + * positive on a corpus this one did not sample. |
| 3056 | + */ |
| 3057 | +const NEGATED_CLOSE_MARKER_RE = |
| 3058 | + /\b(?:not|cannot|never|no longer|filed|filing|out of scope|rather than|instead of|without|remains open)\b|\bn't\b/i; |
| 3059 | + |
| 3060 | +/** |
| 3061 | + * The start offset of the sentence containing `idx`. |
| 3062 | + * |
| 3063 | + * Boundaries are sentence-ending punctuation, a blank line (paragraph break), |
| 3064 | + * and a markdown structural line start (heading, list item, table row, block |
| 3065 | + * quote). A PLAIN single newline is deliberately NOT a boundary: PR bodies are |
| 3066 | + * soft-wrapped but commit messages in this repo are hard-wrapped at ~72 |
| 3067 | + * columns, so prose sentences routinely span lines there, and treating every |
| 3068 | + * newline as a break would blind the window on exactly the second corpus. |
| 3069 | + * |
| 3070 | + * Exported for the self-test: the window is the whole design, so it is pinned |
| 3071 | + * directly rather than only through the predicate's verdict. |
| 3072 | + */ |
| 3073 | +export function sentenceStartOffset(text, idx) { |
| 3074 | + const head = String(text ?? '').slice(0, idx); |
| 3075 | + let best = 0; |
| 3076 | + for (const re of [ |
| 3077 | + /[.!?][)\]"'`]*[ \t\n]/g, |
| 3078 | + /\n[ \t]*\n/g, |
| 3079 | + /\n[ \t]*(?:#{1,6}\s|[-*+]\s|\d+\.\s|\||>)/g, |
| 3080 | + ]) { |
| 3081 | + let m; |
| 3082 | + while ((m = re.exec(head)) !== null) { |
| 3083 | + const end = m.index + m[0].length; |
| 3084 | + if (end > best) best = end; |
| 3085 | + } |
| 3086 | + } |
| 3087 | + return best; |
| 3088 | +} |
| 3089 | + |
| 3090 | +/** |
| 3091 | + * H21 — null when clean, else the finding sentence. |
| 3092 | + * |
| 3093 | + * Reads the same code-stripped text H7 reads, so a keyword quoted in backticks |
| 3094 | + * or parked in a fence is not a finding here either — the measured reason is in |
| 3095 | + * `stripMarkdownCode`. |
| 3096 | + */ |
| 3097 | +export function h21NegatedClosingKeyword(pr) { |
| 3098 | + const body = pr?.body ?? ''; |
| 3099 | + const text = stripMarkdownCode(body); |
| 3100 | + const declared = partOfTargets(body); |
| 3101 | + const hits = []; |
| 3102 | + for (const m of text.matchAll(closingKeywordRe())) { |
| 3103 | + const [full, keyword, number] = m; |
| 3104 | + if (declared.has(number)) continue; // H7 owns that number |
| 3105 | + const start = sentenceStartOffset(text, m.index); |
| 3106 | + if (!NEGATED_CLOSE_MARKER_RE.test(text.slice(start, m.index))) continue; |
| 3107 | + hits.push({ |
| 3108 | + keyword, |
| 3109 | + number, |
| 3110 | + sentence: text.slice(start, m.index + full.length).replace(/\s+/g, ' ').trim(), |
| 3111 | + }); |
| 3112 | + } |
| 3113 | + if (hits.length === 0) return null; |
| 3114 | + return hits |
| 3115 | + .map( |
| 3116 | + (h) => |
| 3117 | + `body carries \`${h.keyword} #${h.number}\` in a sentence that reads as NOT closing it — ` + |
| 3118 | + `"${h.sentence}". GitHub's closing-keyword parser matches the keyword plus the number and ` + |
| 3119 | + `ignores the surrounding prose entirely, negations included, so merging this closes ` + |
| 3120 | + `#${h.number} and the sentence written to prevent that is what performs it. The measured ` + |
| 3121 | + `specimen (PR #10241, "Filed, not fixed: #10240") closed a genuine unfixed card two seconds ` + |
| 3122 | + `after merge, and a closed card reads as finished. Reword so no closing keyword sits next ` + |
| 3123 | + `to that number — "#${h.number} is not addressed here" / "out of scope: #${h.number}" / ` + |
| 3124 | + `"#${h.number} remains open" — or put the keyword in backticks.`, |
| 3125 | + ) |
| 3126 | + .join('; '); |
| 3127 | +} |
| 3128 | + |
2922 | 3129 | // --------------------------------------------------------------------------- |
2923 | 3130 | // Report rendering — pure over (findings, counts), so `--self-test` pins both |
2924 | 3131 | // media offline. The live sweep below picks a renderer and prints it; nothing |
@@ -4054,15 +4261,19 @@ async function sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped, stat |
4054 | 4261 | throw lastHoldError; |
4055 | 4262 | } |
4056 | 4263 |
|
4057 | | - // H7 + H12 — the PR side. Listed straight from `/pulls` rather than filtered |
4058 | | - // out of the label pages above: PRs carry no `pm:*` label, so the issue sweep |
4059 | | - // cannot see them (it discards them explicitly). Drafts are INCLUDED for H7 — |
4060 | | - // a draft is exactly where that is still cheap to fix — and excluded by |
4061 | | - // H12's own predicate (drafts are parked deliberately). |
| 4264 | + // H7 + H12 + H21 — the PR side. Listed straight from `/pulls` rather than |
| 4265 | + // filtered out of the label pages above: PRs carry no `pm:*` label, so the |
| 4266 | + // issue sweep cannot see them (it discards them explicitly). Drafts are |
| 4267 | + // INCLUDED for H7 and H21 — a draft is exactly where that is still cheap to |
| 4268 | + // fix, and both rows are only fixable while the PR is open, because the |
| 4269 | + // damage is done by the merge — and excluded by H12's own predicate (drafts |
| 4270 | + // are parked deliberately). |
4062 | 4271 | for (const pr of await listOpenPullRequests()) { |
4063 | 4272 | seenPrs.set(pr.number, pr); |
4064 | 4273 | const contradiction = h7PartOfWithClosingKeyword(pr); |
4065 | 4274 | if (contradiction) findings.push([pr, 'H7', contradiction]); |
| 4275 | + const negated = h21NegatedClosingKeyword(pr); |
| 4276 | + if (negated) findings.push([pr, 'H21', negated]); |
4066 | 4277 | const orphan = h12OrphanLanding(pr); |
4067 | 4278 | if (orphan) findings.push([pr, 'H12', orphan]); |
4068 | 4279 | } |
@@ -4642,6 +4853,113 @@ function selfTest() { |
4642 | 4853 | ); |
4643 | 4854 | t('H7: a fenced-only keyword is not a finding', h7PartOfWithClosingKeyword(pr('Part of #5\n\n```\nFixes #5\n```')), null); |
4644 | 4855 |
|
| 4856 | + // -- H21: a closing keyword inside a sentence that negates it (#10392) ----- |
| 4857 | + // The positive fixture is the REAL specimen sentence, byte-for-byte from PR |
| 4858 | + // #10241's body, and the negative fixtures are real sentences from the same |
| 4859 | + // 300-body corpus the stage-1 measurement read — including the two that are |
| 4860 | + // the specimen's wording almost exactly and must stay clean. |
| 4861 | + |
| 4862 | + // ★ Specimen — PR #10241 (merged 2026-08-20T15:10:06Z). No `Part of` |
| 4863 | + // anywhere in the body; #10240 closed `completed` two seconds later. |
| 4864 | + const pr10241 = pr( |
| 4865 | + '## Out of scope\n\n' + |
| 4866 | + 'Filed, not fixed: #10240 — the same leak through the **delete** verb. ' + |
| 4867 | + '`beforeDelete`→`afterDelete` hands ids over on the context stash, which the ' + |
| 4868 | + 'measurement above shows is lost on the predicate path.', |
| 4869 | + ); |
| 4870 | + const fired21 = h21NegatedClosingKeyword(pr10241); |
| 4871 | + t('H21: the #10241 specimen FIRES', typeof fired21, 'string'); |
| 4872 | + t('H21: …and names the card it will close', fired21.includes('`fixed #10240`'), true); |
| 4873 | + t('H21: …and quotes the offending sentence back', fired21.includes('Filed, not fixed: #10240'), true); |
| 4874 | + t('H21: …and says the parser ignores the negation', fired21.includes('negations included'), true); |
| 4875 | + t('H21: …and offers the safe rewordings', fired21.includes('#10240 is not addressed here'), true); |
| 4876 | + // H7 is silent on this body — the gap that made the row necessary. If this |
| 4877 | + // ever inverts, H21 is redundant rather than merely quiet. |
| 4878 | + t('H21: …and H7 is silent on it (the gap this row exists for)', h7PartOfWithClosingKeyword(pr10241), null); |
| 4879 | + |
| 4880 | + // The window. `not` and `filed` both fire on the specimen; each marker was |
| 4881 | + // measured alone against both corpora. |
| 4882 | + t('H21: a bare negated close fires', typeof h21NegatedClosingKeyword(pr('This does not fix #77.')), 'string'); |
| 4883 | + t('H21: "out of scope" fires', typeof h21NegatedClosingKeyword(pr('Out of scope: closes #77.')), 'string'); |
| 4884 | + t('H21: "no longer" fires', typeof h21NegatedClosingKeyword(pr('#77 is no longer in scope, so this closes #77 only on paper.')), 'string'); |
| 4885 | + |
| 4886 | + // ⛔ The rule is the negation window, never keyword presence. 277 of 300 |
| 4887 | + // measured bodies carry a plain closing keyword and every one is correct. |
| 4888 | + t('H21: a plain `Fixes #N` is clean', h21NegatedClosingKeyword(pr('Fixes #10171')), null); |
| 4889 | + t( |
| 4890 | + 'H21: a fourteen-card close list is clean (the #10714 shape)', |
| 4891 | + h21NegatedClosingKeyword(pr('Fixes #10581\nFixes #10582\nFixes #10583')), |
| 4892 | + null, |
| 4893 | + ); |
| 4894 | + // …and it stays clean even when the body says "not" somewhere ELSE. This is |
| 4895 | + // the measured 13-false-positive case a body-scoped window produces. |
| 4896 | + t( |
| 4897 | + 'H21: a negation elsewhere in the body does not reach the keyword', |
| 4898 | + h21NegatedClosingKeyword(pr('This does not touch the loader.\n\n### Closing lines\n\nFixes #10581')), |
| 4899 | + null, |
| 4900 | + ); |
| 4901 | + |
| 4902 | + // Real corpus near-misses — the specimen's register, no keyword bound to a |
| 4903 | + // number. These are the 116-sentence population the row must not report. |
| 4904 | + t( |
| 4905 | + 'H21: "#N is not addressed here" is clean (the advised spelling)', |
| 4906 | + h21NegatedClosingKeyword(pr('#10526 is not addressed here — spec-side, another lane.')), |
| 4907 | + null, |
| 4908 | + ); |
| 4909 | + t( |
| 4910 | + 'H21: "#N remains open" is clean', |
| 4911 | + h21NegatedClosingKeyword(pr('out of scope for it — #10368 remains open and untouched.')), |
| 4912 | + null, |
| 4913 | + ); |
| 4914 | + t( |
| 4915 | + 'H21: the #10876 near-miss ("filed, not repaired here") is clean', |
| 4916 | + h21NegatedClosingKeyword(pr('## Out of scope — filed, not repaired here\n\nThe loader half stays open.')), |
| 4917 | + null, |
| 4918 | + ); |
| 4919 | + t( |
| 4920 | + 'H21: the #10851 near-miss ("filed, not fixed here") is clean', |
| 4921 | + h21NegatedClosingKeyword(pr('## The divergence the suite found — filed, not fixed here')), |
| 4922 | + null, |
| 4923 | + ); |
| 4924 | + |
| 4925 | + // Disjoint from H7: a number already declared `Part of` is H7's row. |
| 4926 | + t( |
| 4927 | + 'H21: a `Part of #N` body is H7\'s row, not this one', |
| 4928 | + h21NegatedClosingKeyword(pr('Part of #77\n\nThis does not fix #77.')), |
| 4929 | + null, |
| 4930 | + ); |
| 4931 | + t( |
| 4932 | + 'H21: …while a DIFFERENT number in the same body is still this row\'s', |
| 4933 | + typeof h21NegatedClosingKeyword(pr('Part of #77\n\nFiled, not fixed: #88.')), |
| 4934 | + 'string', |
| 4935 | + ); |
| 4936 | + |
| 4937 | + // Code stripping — inherited from H7, so a quoted keyword is not a finding. |
| 4938 | + t('H21: a keyword in backticks is clean', h21NegatedClosingKeyword(pr('Filed, not `fixed #10240`.')), null); |
| 4939 | + t('H21: a fenced keyword is clean', h21NegatedClosingKeyword(pr('not fixed:\n\n```\nFixes #10240\n```')), null); |
| 4940 | + t('H21: gerunds are not closing keywords', h21NegatedClosingKeyword(pr('This is not fixing #77.')), null); |
| 4941 | + t('H21: empty / missing body', h21NegatedClosingKeyword(pr(undefined)), null); |
| 4942 | + |
| 4943 | + // The window itself, pinned directly — it is the whole design. Asserted as |
| 4944 | + // the TEXT the marker scan actually sees, not as an offset: the offset is an |
| 4945 | + // implementation detail, while "the previous sentence is not in the window" |
| 4946 | + // is the property the 13-false-positive measurement turns on. |
| 4947 | + const win = (text, idx) => text.slice(sentenceStartOffset(text, idx), idx); |
| 4948 | + t('H21 window: a sentence break bounds it', win('No. Fixes #1', 4), ''); |
| 4949 | + t('H21 window: a blank line bounds it', win('not here\n\nFixes #1', 10), ''); |
| 4950 | + // The structural boundary lands after the markdown marker, so the window is |
| 4951 | + // the heading's / item's own text — and the sentence BEFORE it is excluded. |
| 4952 | + t('H21 window: a heading line bounds it', win('not here\n## H\nFixes #1', 14), 'H\n'); |
| 4953 | + t('H21 window: a list item bounds it', win('not here\n- item\nFixes #1', 16), 'item\n'); |
| 4954 | + // ⛔ A plain newline is NOT a boundary: commit messages here are hard-wrapped |
| 4955 | + // at ~72 columns, so a sentence routinely spans lines on that corpus. |
| 4956 | + t('H21 window: a soft-wrapped newline does NOT bound it', sentenceStartOffset('not\nhere', 8), 0); |
| 4957 | + t( |
| 4958 | + 'H21: …so a hard-wrapped negated close still fires', |
| 4959 | + typeof h21NegatedClosingKeyword(pr('Filed, not\nfixed: #10240')), |
| 4960 | + 'string', |
| 4961 | + ); |
| 4962 | + |
4645 | 4963 | // -- H8: delivering PR merged, card still `pm:dispatched` (#8683) ---------- |
4646 | 4964 | // Fixtures reuse H7's extractor pins, so the stripping and per-number- |
4647 | 4965 | // binding measurements carry over rather than being re-proved. |
|
0 commit comments