You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1400918
Browse filesBrowse the repository at this point in the historyBrowse files
test(pm): pin the second-Claim: reading per direction, with a control each
A new self-test battery (51 cases) registered in the roster, floor 31 -> 32:
same seat twice with no retraction ⇒ named at exit 4 with both ids and both
repairs; different authors ⇒ supersession as today; a re-claim after a
`Release:` or an id-naming retraction ⇒ RETRACTED as today, wording
byte-unchanged; a `Clause-②-correction:` row ⇒ silent; a decorated second claim
⇒ counted through the sibling's one reading; an unparsed `Branch:` ⇒ still
named; three claims ⇒ ONE refusal naming three; an unattributable row ⇒ fail
closed; a later comment that merely quotes the word ⇒ silent. Every direction
carries a non-vacuity control, and the five measured instances (2026-09-18T00:05Z)
are replayed from their real rows with #18559's correction as the control.
Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
// one #17915 adds, by the one #17959 adds, by the one #18042 adds, and by the
801
802
// one #18174 adds, and by the one #18141 adds, and by the one #17919 adds, and
802
803
// by the one #16833 adds, and by the one #18456 adds, and by the one #18719
803
-
// adds, and by the one #18683 adds, and by the one #18764 adds.
804
-
const SELF_TEST_BATTERY_FLOOR = 31;
804
+
// adds, and by the one #18683 adds, and by the one #18764 adds, and by the one
805
+
// #18828 adds.
806
+
const SELF_TEST_BATTERY_FLOOR = 32;
805
807
806
808
// The key an assertion is filed under when no battery is open. It is not a
807
809
// declared battery, so it reds by the same set difference rather than silently
@@ -8482,6 +8484,154 @@ export async function selfTest() {
8482
8484
t('⭐ the RETRACTION path still reads the sigil-led #18373 line — narrowing it to the shared reading would unland #18719', claimRetractions(RTX_18373).has(RTX_18373.find((r) => r.id === RTX_WITHDRAWN)));
8483
8485
t('⭐ …while the SHARED reading does not strip a leading sigil, so the two paths are ⛔ NOT interchangeable', markerMatches(CLAIM_COMMENT_MARKER, '🚨 Claim: PM loop round 1') === false);
8484
8486
8487
+
// -- #18828: a SECOND `Claim:` by ONE seat, NAMED -------------------------
8488
+
//
8489
+
// The rule is this file's own (#17366 block, upstairs): the claim protocol
8490
+
// forbids a second `Claim:`. What the reader did with one was RANK it —
8491
+
// `rejected: 1 … a SUPERSEDED claim`, exit 0 — so a writer-side prohibition
8492
+
// had no enforcing reader, and the record used the word for a transition the
8493
+
// protocol DESIGNED about a line it says must not be written.
8494
+
//
8495
+
// ⭐ The pins below are per DIRECTION, and each one carries its own
8496
+
// non-vacuity control: an assertion that some thread is silent proves nothing
8497
+
// unless the SAME thread, with one thing changed, is named.
8498
+
battery('#18828: a SECOND `Claim:` by ONE seat — the writer-side prohibition, finally READ');
// (a) the shape the card measured five times over.
8517
+
t('⭐ (a) two LIVE claims by ONE seat, no retraction between them — ONE named group carrying BOTH ids', R28_IDS([R28_A, R28_B]) === '7100000001+7100000002', R28_IDS([R28_A, R28_B]));
8518
+
t('…and `--pair` earns a C8 FINDING, which is its exit 4', R28_CODES([R28_A, R28_B]).includes('C8'), R28_CODES([R28_A, R28_B]).join());
8519
+
t('⛔ …a VERDICT and never a NOTE: the code lives in `pairRows`, and `pairNotes` does not carry it', pairNotes(R28_PAIR([R28_A, R28_B])).every((n) => n.code !== 'C8') && EXIT_PAIR_ADVERSE === 4 && EXIT_PAIR_ADVERSE !== EXIT_OK);
8520
+
t('…the ROW names the author and BOTH comment ids', says(R28_ROW([R28_A, R28_B]), R28_SEAT) && says(R28_ROW([R28_A, R28_B]), '7100000001') && says(R28_ROW([R28_A, R28_B]), '7100000002'));
8521
+
t('…and it names BOTH sanctioned repairs — the correction key and the `Release:`', says(R28_ROW([R28_A, R28_B]), 'Clause-②-correction') && says(R28_ROW([R28_A, R28_B]), '`Release:`'));
8522
+
t('…and the input record READS the same two ids, beside the carrier it selected', says(R28_FIELD([R28_A, R28_B], 'claim.repeat'), '7100000001') && says(R28_FIELD([R28_A, R28_B], 'claim.repeat'), '7100000002'));
8523
+
t('⛔ CONTROL: ONE claim by that same seat is silent — no group, no row, and the record says so', R28_IDS([R28_A]) === '' && !R28_CODES([R28_A]).includes('C8') && says(R28_FIELD([R28_A], 'claim.repeat'), 'none — no author holds'));
8524
+
t('⛔ CONTROL: the SELECTOR did not move — the newest still governs and the older is still listed SUPERSEDED', claimCarrierSelection([R28_A, R28_B]).pool.map((r) => r.id).join() === '7100000002' && says(R28_FIELD([R28_A, R28_B], 'claim.rejected'), 'a SUPERSEDED claim'));
8525
+
t('⛔ …and the refusal is an ADDITIONAL reading, ⛔ not a new selector: same pool, same rejected count as before', claimCarrierSelection([R28_A, R28_B]).rejected.length === 1 && claimCarrierSelection([R28_A, R28_B]).claims.length === 2 && CLAIM_SELECTION_RULE.includes('the GOVERNING claim'));
8526
+
8527
+
// (b) the p1 shape — REPORTED in the filing round, ⛔ not refused here.
t('⭐ (b) two live claims by DIFFERENT authors ⇒ ⛔ NOT this state — an ownership transfer is a separate reading', R28_IDS([R28_A, R28_OTHER]) === '' && !R28_CODES([R28_A, R28_OTHER]).includes('C8'));
8530
+
t('…and that thread still reads exactly as it did: the newer governs, the older is SUPERSEDED, exit 0', claimCarrierSelection([R28_A, R28_OTHER]).pool.map((r) => r.id).join() === '7100000003' && says(R28_FIELD([R28_A, R28_OTHER], 'claim.rejected'), 'a SUPERSEDED claim'));
8531
+
t('⛔ CONTROL: make those two authors ONE and the same thread is named — the author test is what decides', R28_IDS([R28_A, { ...R28_OTHER, user: { login: R28_SEAT } }]) === '7100000001+7100000003');
8532
+
t('…and the rule PRINTED with the row says so in as many words, so a reader is not left to infer it', CLAIM_REPEAT_RULE.includes('DIFFERENT authors are not this state'));
8533
+
8534
+
// (c) MEMBERSHIP first — a re-claim after a release is the protocol working.
t('⭐ (c) a fresh claim AFTER a `Release:` of the first ⇒ silent — a retracted claim does not stand', R28_IDS([R28_A, R28_RELEASE, R28_B]) === '' && !R28_CODES([R28_A, R28_RELEASE, R28_B]).includes('C8'));
8538
+
t('…and the first is still listed RETRACTED, with the 「⛔ NOT superseded」 wording byte-unchanged', says(R28_FIELD([R28_A, R28_RELEASE, R28_B], 'claim.rejected'), 'RETRACTED') && says(R28_FIELD([R28_A, R28_RELEASE, R28_B], 'claim.rejected'), '⛔ NOT superseded: a withdrawn claim is not a candidate for governance at all, whatever its date'));
8539
+
t('…and the id-naming retraction channel reads the same way — ⛔ not a `Release:`-only rule', R28_IDS([R28_A, R28_NAMED_RETRACTION, R28_B]) === '');
8540
+
t('⛔ CONTROL: drop the retraction and those same two claims are named — the release is what clears it', R28_IDS([R28_A, R28_B]) === '7100000001+7100000002');
8541
+
t('⭐ the repair is PERFORMABLE after the fact: `Release:` then ONE fresh claim leaves exactly one standing', R28_IDS([R28_A, R28_B, R28(7100000012, '2026-09-18T03:00:00Z', R28_SEAT, 'Release: session `x`, cause: 修复本席的双认领, 去向: `pm:queue`'), R28_CLAIM(7100000013, '2026-09-18T04:00:00Z', R28_SEAT, { round: 3 })]) === '');
8542
+
8543
+
// (d) the sanctioned exit stays the sanctioned exit.
t('⭐ (d) a `Clause-②-correction:` as the LATER row is not a claim — silent, and the pool never saw it', R28_IDS([R28_A, R28_CORRECTION]) === '' && !R28_CODES([R28_A, R28_CORRECTION]).includes('C8') && markerMatches(CLAIM_COMMENT_MARKER, R28_CORRECTION.body) === false);
8550
+
t('…and the #17366 exit still WORKS: the declaration is read off the correction, untouched by this row', cardDeclaration([R28_A, R28_CORRECTION]).state === 'declared' && cardDeclaration([R28_A, R28_CORRECTION]).correctionNote !== undefined);
8551
+
t('⛔ CONTROL: write that same correction as a SECOND `Claim:` instead and it is named — which is the whole card', R28_IDS([R28_A, R28_B]) !== '' && says(R28_ROW([R28_A, R28_B]), 'never enters the pool'));
8552
+
8553
+
// (e) ONE reading of the marker, and it is the sibling's (#18764).
t('⭐ (e) a DECORATED second claim (`**Claim:**`) is counted exactly as a bare one', R28_IDS([R28_A, R28_BOLD]) === '7100000001+7100000030');
8557
+
t('…and the backticked spelling with it', R28_IDS([R28_A, R28_TICK]) === '7100000001+7100000031');
8558
+
t('⛔ CONTROL: the raw constant REFUSES both bodies — the counting is the sibling\'s ONE reading, ⛔ not a second reader here', CLAIM_COMMENT_MARKER.test(R28_BOLD.body) === false && CLAIM_COMMENT_MARKER.test(R28_TICK.body) === false && markerMatches(CLAIM_COMMENT_MARKER, R28_BOLD.body) === true);
8559
+
t('⛔ CONTROL: a markdown LIST ITEM is not a claim, so it is not a SECOND one either — the refusals are the sibling\'s', R28_IDS([R28_A, R28(7100000032, '2026-09-18T02:00:00Z', R28_SEAT, '- Claim: PM loop round 2\nBranch: `claude/issue-4242-second`')]) === '');
8560
+
8561
+
// (f) the prohibition is on the WRITING, ⛔ not on the parse.
t('⭐ (f) a second claim whose `Branch:` parses to ZERO branches is still a second claim', R28_IDS([R28_A, R28_UNPARSED]) === '7100000001+7100000040');
8564
+
t('…and it is named even though governance itself is unresolvable on that thread', cardDeclaration([R28_A, R28_UNPARSED]).state === 'claim-branch-unparsed' && R28_CODES([R28_A, R28_UNPARSED]).includes('C8'));
8565
+
t('⛔ CONTROL: give that same row a parseable branch and the two are named just the same — the parse fires nothing', R28_IDS([R28_A, { ...R28_UNPARSED, body: R28_B.body }]) === '7100000001+7100000040');
8566
+
8567
+
// (g) one refusal per seat, ⛔ never one per pair.
t('⭐ (g) THREE claims by one seat ⇒ ONE refusal naming all three', claimRepeats([R28_A, R28_B, R28_C]).length === 1 && R28_IDS([R28_A, R28_B, R28_C]) === '7100000001+7100000002+7100000050');
8570
+
t('…and exactly ONE C8 row on the pair — ⛔ not one per ordered pair, which would have been three', R28_CODES([R28_A, R28_B, R28_C]).filter((c) => c === 'C8').length === 1);
8571
+
t('…with all three ids in the sentence a reader is handed', says(R28_ROW([R28_A, R28_B, R28_C]), '7100000001') && says(R28_ROW([R28_A, R28_B, R28_C]), '7100000002') && says(R28_ROW([R28_A, R28_B, R28_C]), '7100000050'));
8572
+
t('⛔ CONTROL: retract two of the three and one standing claim is left — silent', R28_IDS([R28_A, R28_B, R28(7100000051, '2026-09-18T02:30:00Z', R28_SEAT, 'Release: session `x`, cause: y, 去向: `pm:queue`'), R28_C]) === '');
8573
+
8574
+
// (h) fail closed on an unattributable row — the way `claimRetractions` does.
t('⭐ (h) an unattributable LATER row is never counted — ⛔ `null` is a refusal, never a wildcard', R28_IDS([R28_A, R28_NO_USER]) === '' && !R28_CODES([R28_A, R28_NO_USER]).includes('C8'));
8577
+
t('…and an unattributable FIRST row is not counted either — both sides fail closed', R28_IDS([{ id: 7100000061, created_at: '2026-09-18T01:00:00Z', body: R28_A.body }, R28_B]) === '');
8578
+
t('…and the direction is STATED in the rule the row prints, so silence here is readable rather than inferred', CLAIM_REPEAT_RULE.includes('unattributable row is never counted'));
8579
+
t('⛔ CONTROL: give that same row a login and it is named — the attribution is what was missing', R28_IDS([R28_A, { ...R28_NO_USER, user: { login: R28_SEAT } }]) === '7100000001+7100000060');
8580
+
8581
+
// (i) the seat's own shape: a later comment that QUOTES or DISCUSSES the word.
t('⭐ (i) a later same-author comment that QUOTES the word is not a claim — the marker is read at LINE START', R28_IDS([R28_A, R28_QUOTE]) === '' && !R28_CODES([R28_A, R28_QUOTE]).includes('C8'));
8591
+
t('…and a round report mentioning the word mid-line is not one either', R28_IDS([R28_A, R28_ROUND_REPORT]) === '');
8592
+
t('…both of them together with the claim still leave exactly one standing claim on the thread', claimCarrierSelection([R28_A, R28_QUOTE, R28_ROUND_REPORT]).live.length === 1);
8593
+
t('⛔ CONTROL: move that same word to the OPENING of a line and it IS a second claim', R28_IDS([R28_A, R28(7100000072, '2026-09-18T02:00:00Z', R28_SEAT, 'Claim: 本席重新认领\nBranch: `claude/issue-4242-second`')]) === '7100000001+7100000072');
8594
+
8595
+
// The unread and empty halves — ⛔ never a clean reading.
t('…and the record SAYS unread rather than reporting a clean thread', says(R28_FIELD(null, 'claim.repeat'), 'UNREAD'));
8598
+
t('⛔ an EMPTY thread is silent, and says the other sentence', claimRepeats([]).length === 0 && says(R28_FIELD([], 'claim.repeat'), 'none — no author holds'));
8599
+
8600
+
// ⭐ The LIVE census, replayed from the real rows the filing card named.
8601
+
// Population: both boards this gate reads, at 2026-09-18T00:05Z — 533 open
8602
+
// cards in `objectstack-ai/objectstack`, 416 in `objectstack-ai/objectui`,
8603
+
// 167 of them carrying at least one claim comment. The five below are the
8604
+
// measured instances on the objectstack board, all by one seat; #18559 is the
8605
+
// same seat on the same shift doing it the sanctioned way.
t('⭐ the five measured instances (2026-09-18T00:05Z) are ALL named — ⛔ five green readings before this row', R28_LIVE_CARDS.every(([, a, b]) => claimRepeats([a, b]).length === 1));
8621
+
t('…each naming its own two comment ids and the one seat that wrote them', R28_LIVE_CARDS.every(([, a, b]) => R28_IDS([a, b]) === `${a.id}+${b.id}` && claimRepeats([a, b])[0].author === R28_SEAT), R28_LIVE_CARDS.map(([n, a, b]) => `${n}:${R28_IDS([a, b])}`).join(' '));
8622
+
t('⛔ …and every one of them read GREEN before, as a SUPERSESSION — the measurement this row answers', R28_LIVE_CARDS.every(([, a, b]) => says(R28_FIELD([a, b], 'claim.rejected'), 'a SUPERSEDED claim')));
8623
+
t('⛔ CONTROL #18559 — the same seat, same shift, the SANCTIONED shape: one claim plus a correction ⇒ silent', (() => {
t('⭐ …and that control IS the repair this row prints, which is why the row prints it', says(R28_ROW([R28_A, R28_B]), 'Clause-②-correction: <claim comment id>'));
8629
+
8630
+
// ONE derivation — the record and the verdict cannot disagree.
8631
+
t('the repeat field is DECLARED in the pair roster, so it renders on every block, filled or not', INPUT_RECORD_PAIR_FIELDS.includes('claim.repeat'));
8632
+
t('⭐ ONE derivation: the ids the ROW names are the ids the RECORD names', claimRepeats([R28_A, R28_B])[0].ids.every((id) => says(R28_ROW([R28_A, R28_B]), id) && says(R28_FIELD([R28_A, R28_B], 'claim.repeat'), id)));
8633
+
t('⛔ the row is not in the sweep\'s NOTE family and the record carries no verdict word for it', !says(R28_FIELD([R28_A, R28_B], 'claim.repeat'), 'exit 4') && says(R28_FIELD([R28_A, R28_B], 'claim.repeat'), 'row C8 is the verdict'));
8634
+
8485
8635
// -- The floor: every declared battery RAN, and ran its cases (#13489) -----
8486
8636
//
8487
8637
// Evaluated after every battery has had its chance and BEFORE the verdict, so
@@ -8552,7 +8702,9 @@ export async function selfTest() {
8552
8702
+ 'and on a refusal — with the selected carrier, its body fingerprint and the rejected '
8553
8703
+ 'candidates each stated, the DECORATED claim that enters the pool and governs through '
8554
8704
+ 'the sibling\'s one reading — the constant unwidened, and the file\'s two undecoration '
8555
-
+ 'paths pinned as the two jobs they are — and the exit register).',
8705
+
+ 'paths pinned as the two jobs they are, the SECOND `Claim:` by one seat named as the state '
8706
+
+ 'the protocol forbids writing rather than ranked as a supersession — per direction, with a '
8707
+
+ 'non-vacuity control each and the five measured instances replayed — and the exit register).',
0 commit comments