Skip to content

Commit f6480bc

Browse files
hotlongclaude
andauthored
fix(pm): let every delivery row state the evidence it rests on (#16798)
* 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 78bc4ad commit f6480bc

2 files changed

Lines changed: 349 additions & 16 deletions

File tree

‎scripts/pm/check-clause2-carriers.mjs‎

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ import {
338338
EXIT_PREREQUISITE_NOT_MET,
339339
PROXY_FLAG,
340340
SWEEP_REPO_SHAPE,
341+
deliveryEvidence,
342+
deliveryEvidenceNote,
341343
governingClaim,
342344
isGateSemanticLabel,
343345
labelNames,
@@ -643,7 +645,16 @@ export function c1CarrierSplit(pair) {
643645
const onPr = gated(pair?.prLabels);
644646
if (onCard === null || onPr === null) return null; // unreadable — accounted as UNJUDGED, not clean.
645647
if (onCard === onPr) return null;
646-
const draft = pair.draft ? ' (draft)' : '';
648+
// The PAIRING's own evidence (#16706). This row's dangerous half tells a
649+
// reader a live fail-open is in front of them, and the measured specimen that
650+
// produced one was a pair derived from an accounting sentence — so the row
651+
// states what the pairing rests on, in the same breath as the consequence.
652+
// A pair from a caller that predates the field carries no `evidence` at all;
653+
// it prints as it always did rather than claiming a reading nobody took.
654+
const bits = [];
655+
if (pair.draft) bits.push('draft');
656+
if (pair?.evidence !== undefined) bits.push(deliveryEvidenceNote(pair.evidence));
657+
const draft = bits.length > 0 ? ` (${bits.join(', ')})` : '';
647658
if (onCard && !onPr) {
648659
return (
649660
`\`${CONTRACT_REVIEW_LABEL}\` on card #${pair.card} while its delivering open PR ` +
@@ -1355,6 +1366,14 @@ export function pairUnjudged(pair) {
13551366
* its card, and every branch name carries the fallback too — 7 of 7, both
13561367
* channels. Pairing is a derivation here, not an assumption.
13571368
*
1369+
* Each pair carries the EVIDENCE its derivation rests on (#16706), because the
1370+
* derivation is exactly where a false pair enters this file: a body whose prose
1371+
* said 「part of #N already landed」 about another card derived a pair that
1372+
* never existed, and C1 then reported its dangerous half against it in the same
1373+
* words a real split gets. The kind rides on the pair so the row can say what
1374+
* it was built from; ⛔ it is never a filter here — a pair is derived exactly
1375+
* when `prDeliversCard` says so, as before.
1376+
*
13581377
* @param {object[]} openPrs
13591378
* @param {number[]} cardNumbers — the open cards the sweep holds.
13601379
*/
@@ -1363,7 +1382,15 @@ export function derivePairs(openPrs, cardNumbers) {
13631382
for (const pr of openPrs ?? []) {
13641383
if (!pr || pr.merged_at) continue;
13651384
for (const n of cardNumbers ?? []) {
1366-
if (prDeliversCard(pr, String(n))) pairs.push({ pr: pr.number, card: Number(n), draft: Boolean(pr.draft), prRow: pr });
1385+
if (prDeliversCard(pr, String(n))) {
1386+
pairs.push({
1387+
pr: pr.number,
1388+
card: Number(n),
1389+
draft: Boolean(pr.draft),
1390+
evidence: deliveryEvidence(pr, String(n)),
1391+
prRow: pr,
1392+
});
1393+
}
13671394
}
13681395
}
13691396
return pairs;
@@ -2274,6 +2301,40 @@ export function selfTest() {
22742301
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);
22752302
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);
22762303

2304+
// -- #16706: the pair carries the EVIDENCE it was derived from -------------
2305+
//
2306+
// ★ The measured specimen — objectui PR #8354, whose "Serial constraints"
2307+
// section said 「part of #7918 already landed …」 about a DIFFERENT card with
2308+
// its own separate PR. The pair below is the one this file derived from that
2309+
// sentence, and the C1 row it emitted told the reader a live fail-open was in
2310+
// front of them. The relation is unchanged, so the pair is still derived —
2311+
// what changed is that the row now says what it was built from.
2312+
const PROSE_8354 =
2313+
'Note that **part of #7918** already landed as `4f9f1ee` (PR #8226, memoising two of ' +
2314+
'the lazy getters); this PR does not touch the getters.';
2315+
const body8354 = `Fixes #7760\n\n## Serial constraints\n\n- ${PROSE_8354}\n`;
2316+
const pr8354 = { number: 8354, draft: true, labels: [], body: body8354, head: { ref: 'claude/issue-7760-lazy-mirror-input-type-args' } };
2317+
2318+
const inlinePair = derivePairs([pr8354], [7918]);
2319+
t('#16706: the prose sentence still derives the pair — the relation is NOT narrowed here', inlinePair.length === 1);
2320+
t('#16706: …and the pair records that the match was not at the declaration position', inlinePair[0]?.evidence === 'part-of-inline');
2321+
const keywordPair = derivePairs([pr8354], [7760]);
2322+
t('#16706: the real `Fixes` relation on the same body is graded as the strong channel', keywordPair[0]?.evidence === 'closing-keyword');
2323+
// CONTROL — the same body with that ONE sentence deleted derives nothing.
2324+
t('#16706 control: deleting the sentence removes the pair entirely', derivePairs([{ ...pr8354, body: 'Fixes #7760\n\n## Serial constraints\n\n' }], [7918]).length === 0);
2325+
2326+
// …and the C1 row PRINTS it — the row the card was filed about.
2327+
const c1Inline = c1CarrierSplit({ ...inlinePair[0], prLabels: [CONTRACT_REVIEW_LABEL], cardLabels: [], cardComments: [CLAIM('Clause-②: yes')] });
2328+
t('#16706: the C1 dangerous half still fires on the derived pair', typeof c1Inline === 'string');
2329+
t('#16706: …and now names the evidence beside the PR', String(c1Inline).includes('#8354 (draft, ⚠️ via `Part of` NOT at the declaration position'));
2330+
t('#16706: …while the consequence paragraph it always carried is untouched', String(c1Inline).includes('an ungated card is a card that was never'));
2331+
// A strong-channel pair prints the strong phrase, so the two are DISTINGUISHABLE
2332+
// in the row — which is the whole point of the card.
2333+
const c1Keyword = c1CarrierSplit({ ...keywordPair[0], prLabels: [CONTRACT_REVIEW_LABEL], cardLabels: [], cardComments: [CLAIM('Clause-②: yes')] });
2334+
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'));
2335+
// ⛔ A pair from a caller that predates the field claims no reading at all.
2336+
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)'));
2337+
22772338
// -- the three read paths ---------------------------------------------------
22782339
//
22792340
// The offline reader is exercised against the SAME predicates the live path

0 commit comments

Comments
 (0)