Skip to content

Commit fdd09cd

Browse files
committed
test(pm): pin the retraction reading — three shapes, their controls, and the #18373 replay
The battery replays the #18373 thread offline from its six real comment ids, with each body carrying the load-bearing lines of the real comment rather than a retyped paraphrase. Its BEFORE leg is produced by removing the retraction comment, which is the reading main takes. The measured trap is pinned too: the seat's own report line carries a retraction word and its own claim id, so an `includes` anchor would have read the comment that filed this defect as a withdrawal of the claim it defends. The anchor opens the line instead. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
1 parent 930bada commit fdd09cd

1 file changed

Lines changed: 147 additions & 2 deletions

File tree

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

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
771771
'#16833: an UNJUDGED refusal names the CHANNEL that answered, what it answered, and which carrier': 30,
772772
'#18456: the `--pair` input record — the same block on every exit, so two runs that disagree can be diffed': 38,
773773
'#18701: ONE thread set -- what the template STATES is what the queue guard READS': 16,
774+
'#18719: a RETRACTED claim leaves the pool — a withdrawn claim never governs': 36,
774775
});
775776

776777
// DELETING an entry silences that battery's floor exactly as effectively as
@@ -781,8 +782,9 @@ const SELF_TEST_BATTERIES = Object.freeze({
781782
// effect, and once more by the one #17149 adds, by the one #17098 adds, by the
782783
// one #17915 adds, by the one #17959 adds, by the one #18042 adds, and by the
783784
// one #18174 adds, and by the one #18141 adds, and by the one #17919 adds, and
784-
// by the one #16833 adds, and by the one #18456 adds.
785-
const SELF_TEST_BATTERY_FLOOR = 28;
785+
// by the one #16833 adds, and by the one #18456 adds, and by the one #18719
786+
// adds.
787+
const SELF_TEST_BATTERY_FLOOR = 29;
786788

787789
// The key an assertion is filed under when no battery is open. It is not a
788790
// declared battery, so it reds by the same set difference rather than silently
@@ -7729,6 +7731,149 @@ export async function selfTest() {
77297731
t('⛔ CONTROL: the ranking belongs to `deliveryEvidence` -- every grade it answers is ranked here, none invented', DELIVERY_EVIDENCE_PRECEDENCE.every((kind) => deliveryEvidenceNote(kind) !== 'evidence unread') && new Set(DELIVERY_EVIDENCE_PRECEDENCE).size === DELIVERY_EVIDENCE_PRECEDENCE.length);
77307732
t('…and it is ranked in the order that function applies, measured pair by pair', DELIVERY_EVIDENCE_PRECEDENCE.indexOf(deliveryEvidence(PIN_PULL(`Fixes #1\nPart of #2`), '1')) < DELIVERY_EVIDENCE_PRECEDENCE.indexOf(deliveryEvidence(PIN_PULL(`Fixes #1\nPart of #2`), '2')) && DELIVERY_EVIDENCE_PRECEDENCE.indexOf('part-of') < DELIVERY_EVIDENCE_PRECEDENCE.indexOf('branch-name'));
77317733

7734+
// -- #18719: a RETRACTED claim leaves the pool -----------------------------
7735+
//
7736+
// The pool used to be closed under addition: a `Claim:` comment entered it
7737+
// and nothing ever took one out, so the withdrawal that the protocol calls an
7738+
// explicit act was the one act the arbiter could not see. These cases pin the
7739+
// MEMBERSHIP rule and its three shapes, each against the control that makes
7740+
// the shape a reading rather than a coincidence.
7741+
battery('#18719: a RETRACTED claim leaves the pool — a withdrawn claim never governs');
7742+
7743+
// The #18373 thread, replayed OFFLINE and ⛔ never re-graded: that card is
7744+
// `needs-user-decision` and another seat's. Ids, stamps and logins are the
7745+
// REAL ones; each body carries the load-bearing LINES of the real comment,
7746+
// extracted from the REST rows rather than retyped — so a case here fails
7747+
// when the reader changes, ⛔ never when a transcription slipped.
7748+
const RTX_18373 = [
7749+
{
7750+
// the triage comment — no `Claim:` line, so never a pool candidate
7751+
id: 5716318188,
7752+
created_at: '2026-09-17T14:45:28Z',
7753+
user: { login: 'os-sam' },
7754+
body: '**Triage:`pm:queue` · `priority:p1` · `domain:spec` · 类型 Bug · 摘 `finding`** · 2026-09-17T14:45Z · 分诊席 #6015 · R+275',
7755+
},
7756+
{
7757+
// `os-litant` — the claim the card is actually being worked on, and the card's only assignee
7758+
id: 5717143021,
7759+
created_at: '2026-09-17T15:40:44Z',
7760+
user: { login: 'os-litant' },
7761+
body: [
7762+
'Claim: PM loop round 2026-09-17 R1',
7763+
'Session: `session_01LvwGppdonww4zGLWZo5rho`',
7764+
'Branch: `claude/issue-18373-type-source-resolution-bare-dir-include`',
7765+
'Clause-②: no',
7766+
].join('\n'),
7767+
},
7768+
{
7769+
// `os-bill` — claimed 13 minutes later, and withdrawn 84 seconds after that
7770+
id: 5717315121,
7771+
created_at: '2026-09-17T15:53:24Z',
7772+
user: { login: 'os-bill' },
7773+
body: [
7774+
'Claim: PM loop round 8',
7775+
'Session: `session_01JbZnqu8bt6YqfJsr9vaFb3`',
7776+
'Branch: `claude/issue-18373-include-bare-directory-provenance`',
7777+
'Clause-②: no',
7778+
].join('\n'),
7779+
},
7780+
{
7781+
// THE RETRACTION — no `Claim:` line of its own, which is why the pool could not see it
7782+
id: 5717333576,
7783+
created_at: '2026-09-17T15:54:48Z',
7784+
user: { login: 'os-bill' },
7785+
body: '🚨 **撤回上一条认领(`5717315121`)—— 本卡已由 `os-litant` 在先认领,本席晚了 13 分钟。** `domain:spec` seat 2(`session_01JbZnqu8bt6YqfJsr9vaFb3`,座位贴 #18549)。⏱️ 本条读数取自同一动作:2026-09-17T15:54Z。',
7786+
},
7787+
{
7788+
// `os-litant`'s report line: an anchor and its OWN claim id, mid-line — the `includes` trap
7789+
id: 5717738051,
7790+
created_at: '2026-09-17T16:22:40Z',
7791+
user: { login: 'os-litant' },
7792+
body: ' "question": "The governing-claim instrument now names a retracted claim. check-clause2-carriers --pair 18708 (exit 0) selects comment 5717315121 as governing and marks my dispatch\'s 5717143021 as SUPERSEDED, because the retraction 5717333576 carries no `Claim:` line and so is not in the pool. Is that worth a rule change?",',
7793+
},
7794+
{
7795+
// `os-litant` describing the retraction: an anchor, another seat's claim id — shape (3)
7796+
id: 5717775707,
7797+
created_at: '2026-09-17T16:25:37Z',
7798+
user: { login: 'os-litant' },
7799+
body: '- ⚠️ 但它暴露了一个**工具缺陷**:`check-clause2-carriers --pair 18708` 仍机械地把**那条已撤回的** `5717315121` 选为 governing claim —— 因为撤回评论不带 `Claim:` 行,不在候选池里。**本席另行立卡**,⛔ 不在本卡处理。',
7800+
},
7801+
];
7802+
const RTX_LIVE_CLAIM = 5717143021;
7803+
const RTX_WITHDRAWN = 5717315121;
7804+
const RTX_RETRACTION = 5717333576;
7805+
const RTX_LIVE_BRANCH = 'claude/issue-18373-type-source-resolution-bare-dir-include';
7806+
const RTX_GHOST_BRANCH = 'claude/issue-18373-include-bare-directory-provenance';
7807+
const RTX_AFTER = claimCarrierSelection(RTX_18373);
7808+
// ⛔ The BEFORE leg is the reading `main` takes, produced by REMOVING the
7809+
// retraction rather than by describing it: the same rows, minus the one
7810+
// comment the old pool could not see.
7811+
const RTX_BEFORE = claimCarrierSelection(RTX_18373.filter((r) => r.id !== RTX_RETRACTION));
7812+
const RTX_REASON = (sel, id) => sel.rejected.find((r) => r.row.id === id)?.reason ?? '';
7813+
const RTX_RECORD = (rows) => {
7814+
const rec = pairInputRecord({ pr: 18708, card: 18373, cardComments: rows, headSha: 'offline' });
7815+
const flat = (v) => (Array.isArray(v) ? v.join('\n') : String(v ?? ''));
7816+
return { selected: flat(rec['claim.selected']), rejected: flat(rec['claim.rejected']), rule: flat(rec['claim.rule']) };
7817+
};
7818+
7819+
t('⭐ the #18373 counterfactual, AFTER: the pool is the LIVE claimant\'s claim alone', RTX_AFTER.pool.length === 1 && RTX_AFTER.pool[0].id === RTX_LIVE_CLAIM, JSON.stringify(RTX_AFTER.pool.map((r) => r.id)));
7820+
t('…and the branch it names is the ONE ref origin actually has', (RTX_AFTER.governing?.branches ?? []).join() === RTX_LIVE_BRANCH);
7821+
t('…while the branch the withdrawn claim named — absent from origin — governs nothing', !(RTX_AFTER.governing?.branches ?? []).includes(RTX_GHOST_BRANCH));
7822+
t('the withdrawn claim is listed RETRACTED, naming the comment that took it back', RTX_REASON(RTX_AFTER, RTX_WITHDRAWN).startsWith('RETRACTED') && RTX_REASON(RTX_AFTER, RTX_WITHDRAWN).includes(String(RTX_RETRACTION)));
7823+
t('⛔ …never as SUPERSEDED: a withdrawn record and an out-ranked one are two different facts', !/a SUPERSEDED claim/.test(RTX_REASON(RTX_AFTER, RTX_WITHDRAWN)));
7824+
t('⛔ …and never DROPPED: the full claim listing still carries both records', RTX_AFTER.claims.length === 2 && RTX_AFTER.claims.some((r) => r.id === RTX_WITHDRAWN));
7825+
t('…and the LIVE claim is no longer rejected at all', RTX_REASON(RTX_AFTER, RTX_LIVE_CLAIM) === '');
7826+
t('⛔ CONTROL — the #18373 counterfactual, BEFORE: drop the retraction and the withdrawn claim governs again', RTX_BEFORE.pool.length === 1 && RTX_BEFORE.pool[0].id === RTX_WITHDRAWN && (RTX_BEFORE.governing?.branches ?? []).join() === RTX_GHOST_BRANCH);
7827+
t('⛔ …and in that reading the real claimant is the one marked SUPERSEDED — the defect, replayed', /a SUPERSEDED claim/.test(RTX_REASON(RTX_BEFORE, RTX_LIVE_CLAIM)));
7828+
t('⭐ the two faces side by side: the record NAMES the author the pool selected', RTX_RECORD(RTX_18373).selected.includes('os-litant') && !RTX_RECORD(RTX_18373).selected.includes('os-bill'));
7829+
t('⛔ CONTROL: the BEFORE record named the other seat, which is how the contradiction went unread', RTX_RECORD(RTX_18373.filter((r) => r.id !== RTX_RETRACTION)).selected.includes('os-bill'));
7830+
t('the printed RULE carries the membership half, so two runs are comparable on it', RTX_RECORD(RTX_18373).rule.includes(CLAIM_RETRACTION_RULE) && CLAIM_SELECTION_RULE.includes(CLAIM_RETRACTION_RULE));
7831+
7832+
// ⚠️ The measured trap, and the reason the anchor OPENS a line instead of
7833+
// being searched for inside one. Both lines below are real bytes off this
7834+
// thread; the first is the report that FILED this defect.
7835+
const RTX_OWN_REPORT = RTX_18373.find((r) => r.id === 5717738051);
7836+
const RTX_DESCRIPTION = RTX_18373.find((r) => r.id === 5717775707);
7837+
t('⛔ a seat REPORTING the defect does not commit it — an anchor and its own claim id, mid-line', !claimRetractions(RTX_18373).has(RTX_18373.find((r) => r.id === RTX_LIVE_CLAIM)));
7838+
t('⛔ …and the author test alone would NOT have saved it: that line is the claim\'s OWN author', RTX_OWN_REPORT.user.login === RTX_18373.find((r) => r.id === RTX_LIVE_CLAIM).user.login && /retract/i.test(RTX_OWN_REPORT.body) && RTX_OWN_REPORT.body.includes(String(RTX_LIVE_CLAIM)));
7839+
t('⛔ …and a THIRD seat describing the retraction retracts nothing either — anchor, id, wrong author', RTX_DESCRIPTION.body.includes('撤回') && RTX_DESCRIPTION.body.includes(String(RTX_WITHDRAWN)) && RTX_DESCRIPTION.user.login !== RTX_18373.find((r) => r.id === RTX_WITHDRAWN).user.login);
7840+
7841+
// The three shapes, synthetic so each one varies exactly one thing.
7842+
const RTX_ROW = (id, at, login, lines) => ({ id, created_at: at, user: { login }, body: [].concat(lines).join('\n') });
7843+
const RTX_CLAIM = (id, at, login, value = 'no') => RTX_ROW(id, at, login, ['Claim: round 1', 'Branch: `claude/issue-4242-x`', `Clause-②: ${value}`]);
7844+
const RTX_A = RTX_CLAIM(6000000011, '2026-09-17T10:00:00Z', 'seat-a');
7845+
const RTX_B = RTX_CLAIM(6000000012, '2026-09-17T11:00:00Z', 'seat-b');
7846+
const RTX_POOL_IDS = (rows) => claimCarrierSelection(rows).pool.map((r) => r.id).join();
7847+
7848+
t('shape (1) — a retraction NAMING the claim by comment id takes it out of the pool', RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回本席的认领 `6000000012` —— 先到者是 seat-a')]) === '6000000011');
7849+
t('⛔ shape (1) CONTROL: the SAME words from a DIFFERENT author retract nothing', RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-a', '撤回本席的认领 `6000000012` —— 先到者是 seat-a')]) === '6000000012');
7850+
t('shape (2) — a `Release:` line from the claim\'s own author, no id at all, takes it out', RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', 'Release: session X, cause: 先到者是 seat-a, 去向: back to the queue')]) === '6000000011');
7851+
t('⛔ shape (2) CONTROL: the same `Release:` posted BEFORE the claim retracts nothing', RTX_POOL_IDS([RTX_A, RTX_ROW(6000000013, '2026-09-17T10:30:00Z', 'seat-b', 'Release: session X, cause: y, 去向: queue'), RTX_B]) === '6000000012');
7852+
t('shape (3) — a `Release:` from a DIFFERENT author retracts nobody else\'s claim', RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-c', 'Release: session Z, cause: y, 去向: queue')]) === '6000000012');
7853+
t('⛔ shape (3) CONTROL: the live claimant\'s claim still GOVERNS, it is not merely un-rejected', (() => { const sel = claimCarrierSelection([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-c', 'Release: session Z')]); return sel.governing?.createdAt === RTX_B.created_at && sel.rejected.every((r) => !r.reason.startsWith('RETRACTED')); })());
7854+
t('⛔ the anchor OPENS the line: the same act and id buried mid-sentence retract nothing', RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '本席读到工具把 `6000000012` 当作已撤回的认领,记一笔')]) === '6000000012');
7855+
t('…while decoration in FRONT of the act is not a difference — 「- ⚠️ **撤回** …」 reads', RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '- ⚠️ **撤回**了 `6000000012`,本席让行')]) === '6000000011');
7856+
t('⛔ a retraction naming SOME OTHER comment id retracts nothing here', RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000009999` —— 另一张卡上的认领')]) === '6000000012');
7857+
t('⛔ an unreadable author on the RETRACTOR retracts nothing — fail closed, never a wildcard', RTX_POOL_IDS([RTX_A, RTX_B, { id: 6000000013, created_at: '2026-09-17T12:00:00Z', body: '撤回 `6000000012`' }]) === '6000000012');
7858+
t('⛔ an unreadable author on the CLAIM is not retractable either', RTX_POOL_IDS([RTX_A, { id: 6000000012, created_at: '2026-09-17T11:00:00Z', body: ['Claim: r', 'Branch: `claude/issue-4242-x`'].join('\n') }, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000000012`')]) === '6000000012');
7859+
t('⛔ a RETRACTED claim\'s declaration is not the one read — the LIVE carrier\'s line is', cardDeclaration([RTX_CLAIM(6000000011, '2026-09-17T10:00:00Z', 'seat-a', 'no'), RTX_CLAIM(6000000012, '2026-09-17T11:00:00Z', 'seat-b', 'yes'), RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000000012`')]).value === 'no');
7860+
// ⭐ Every claim retracted ⇒ a state this file ALREADY has, ⛔ never a
7861+
// fabricated carrier and ⛔ never the withdrawn record's own value. WHICH
7862+
// existing state depends on what is left on the thread, and both are pinned
7863+
// because collapsing them would describe neither: a withdrawn claim that
7864+
// carried a declaration leaves that line ON the thread with no carrier under
7865+
// it (`misplaced`, a C2 row, the value ⛔ not accepted), and one that carried
7866+
// none leaves nothing to read at all (`absent`).
7867+
t('⭐ every claim retracted, declaration left on the thread ⇒ `misplaced` — a finding, ⛔ not a reading', (() => { const rows = [RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000000012`')]; const d = cardDeclaration(rows); return claimCarrierSelection(rows).pool.length === 0 && d.state === 'misplaced'; })());
7868+
t('⛔ …and the withdrawn record\'s OWN value is never handed back as the card\'s declaration', (() => { const rows = [RTX_CLAIM(6000000012, '2026-09-17T11:00:00Z', 'seat-b', 'yes'), RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000000012`')]; return cardDeclaration(rows).state !== 'declared'; })());
7869+
t('⭐ …and with nothing left to read the state is `absent` — the carrier is owed, ⛔ not invented', (() => { const rows = [RTX_ROW(6000000012, '2026-09-17T11:00:00Z', 'seat-b', ['Claim: r', 'Branch: `claude/issue-4242-x`']), RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000000012`')]; return cardDeclaration(rows).state === 'absent'; })());
7870+
t('⛔ CONTROL: without the retraction that same thread reads `missing` — the claim IS the carrier', cardDeclaration([RTX_ROW(6000000012, '2026-09-17T11:00:00Z', 'seat-b', ['Claim: r', 'Branch: `claude/issue-4242-x`'])]).state === 'missing');
7871+
t('…and the record SAYS that, instead of reporting a thread nobody claimed', RTX_RECORD([RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000000012`')]).selected.includes('RETRACTED'));
7872+
t('⛔ CONTROL: a thread with no claim at all still reads as the OTHER sentence', RTX_RECORD([RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', 'no claim here')]).selected.includes('no comment on this thread carries'));
7873+
t('ONE derivation: the map the selection rejects from is the map `claimRetractions` returns', (() => { const rows = [RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', '撤回 `6000000012`')]; const sel = claimCarrierSelection(rows); return sel.retracted.size === 1 && sel.retracted.get(RTX_B)?.id === '6000000013' && claimRetractions(rows).get(RTX_B)?.id === '6000000013'; })());
7874+
t('⛔ an unreadable thread retracts nothing and carries the empty halves', claimRetractions(null).size === 0 && claimCarrierSelection(null).live.length === 0 && claimCarrierSelection(null).retracted.size === 0);
7875+
t('the anchor roster is CLOSED, and bare `release` is not on it — a version release is not a retraction', RETRACTION_PROSE_ANCHORS.length === 5 && !RETRACTION_PROSE_ANCHORS.includes('release') && RTX_POOL_IDS([RTX_A, RTX_B, RTX_ROW(6000000013, '2026-09-17T12:00:00Z', 'seat-b', 'release 阻塞在 `6000000012` 上,等维护者')]) === '6000000012');
7876+
77327877
// -- The floor: every declared battery RAN, and ran its cases (#13489) -----
77337878
//
77347879
// Evaluated after every battery has had its chance and BEFORE the verdict, so

0 commit comments

Comments
 (0)