Skip to content

Commit 84a7e9e

Browse files
os-steveclaude
andauthored
fix(pm): the clause-2 correction note READS the claim comment's two stamps instead of vouching it was never edited (#19298)
Fixes #18892 Clause-②: no The `--pair` green line and the `C2-CORRECTION` note under it both testified that the claim comment 「is NOT edited and still reads as it was written」. Nothing in this file had ever read an edit. `created_at` and `updated_at` both arrive on the `/issues/comments` row `applicableCorrection` already holds, so the reading cost no request — it simply was not taken. Triage's grading (5725446014) chose option **A** over the card's B and C: read the two fields, say it in the message, ⛔ never fail on it. ## What changed — `scripts/pm/check-clause2-carriers.mjs` only - **`claimEditReading(row)`** — module-local, ⛔ no new `export` and no registration (Clause-② declared `no`). It turns the claim comment's own two stamps into **three** readings, never two: - `updated_at` differs from `created_at` ⇒ **WAS EDITED**, naming the instant so a reader can open that comment's edit history; - the two are equal ⇒ a **measured UNEDITED**, naming both stamps it compared; - a row carrying neither stamp ⇒ **NOT READ**, said as a gap — ⛔ never as "unedited", which is the sentence this card was filed against. - **The correction note states that reading** in place of the assertion. **Report-only**, per the ruling: no exit moves, and an edited claim still reads `declared` at the seat's own value. An edit is a legitimate act; the silence was the defect. - **The green line's parenthetical** now describes what the reading above actually states, and names the stream that reading prints on. ## The pin was proven red BEFORE the fix existed Two commits, in that order, exactly as the card required (「⛔ 先让 pin 红」). `0d29892` — the pin alone, against the unfixed message: ``` $ node scripts/pm/check-clause2-carriers.mjs --self-test # exit 1 ✗ ⭐ the EDITED specimen is REPORTED in the note, in as many words ✗ …naming the edit instant beside the creation one, so a reader can open that comment's history ✗ ⭐ an UNEDITED claim reads as a MEASURED unedited, naming both stamps it compared ✗ ⭐ a row carrying NO `updated_at` reads NOT READ — ⛔ never "unedited", which is this defect one room over ✗ ⭐ the three readings are three DIFFERENT sentences — one sentence for all three was the defect ✗ ⭐ the green line points at that reading and says what it STATES, ⛔ never testifying to the edit itself ✗ ⛔ …and the clause that asserted an unread fact is gone from the printed line ✗ ⛔ NEGATIVE, on the SOURCE: no branch of this file asserts the unread 「NOT edited」 any more ✗ ⛔ CONTROL — the same source read is not empty or misdirected: it reaches the reader this card added ✗ check-clause2-carriers self-test: 9 of 1071 case(s) failed. ``` `0128cbb` — the reader: ``` $ node scripts/pm/check-clause2-carriers.mjs --self-test # exit 0 ✓ check-clause2-carriers self-test: 1071 cases pass (…) ``` The unedited control and the three report-only cases pass on **both** commits, so the battery is not a one-sided pin. The source-scan negative and its control are assembled at runtime (the #16770 idiom), because a literal would have made the scan hit itself — the first draft did exactly that and passed vacuously; it is fixed and the control now proves the read reaches the file. ## Measured on the specimen the card names — offline, via `--pair-json` objectui's claim `5724909959` on card #9764: `created_at` `2026-09-18T03:54:37Z`, `updated_at` `2026-09-18T04:23:23Z`. Replayed with the real API payloads, thread truncated to the instant the card measured: **Before** (`adf4b18`): ``` ℹ️ C2-CORRECTION — … And it supersedes claim comment 5724909959's own declaration, which is NOT edited and still reads as it was written. ``` **After** (`0128cbb`): ``` ℹ️ C2-CORRECTION — … And it supersedes claim comment 5724909959's own declaration, and ⚠️ it WAS EDITED at `2026-09-18T04:23:23Z` (`created_at` `2026-09-18T03:54:37Z`) — REPORTED and ⛔ never a failure: read its edit history before taking the declaration under it for the one the seat first wrote. ``` And the sharper half — two `--pair-json` documents differing **only** in `updated_at`, both reaching exit 0: | | before | after | |---|---|---| | edited claim | exit 0, note says `NOT edited` | exit 0, note says `WAS EDITED at …` | | unedited claim | exit 0, note says `NOT edited` | exit 0, note says `reads UNEDITED — … both …` | Before this PR the two runs were **byte-identical on every stream**. That identity is now impossible, and the exit register did not move in either row. ## Acceptance notes - **The PM's mechanism assumption 3 is REFUTED, and measured so.** The pass path does **not** print three lines with no per-item readings: `renderPair`'s `rows.length === 0` branch prints every note (`ℹ️ C2-CORRECTION — …`) immediately above the green line. So 「the reading above」 resolves today and was ⛔ not dropped. What is true is that the **note goes to stderr while the green line goes to stdout**, so a seat capturing stdout alone holds the pointer without its referent — which is the likeliest reading of the card's 「只打三行」. The parenthetical now names the stream, which is the cheapest thing that makes the pointer executable. No reading was deleted. - **A `--pair-json` document that omits `updated_at` changes reading**, from a silent "unedited" to an explicit NOT READ. That is the intended direction (absence must be loud) and affects hand-written fixture documents only; the live `/issues/comments` rows always carry both fields, unprojected — `readCardComments` hands the raw rows through. - **noted, not filed** — the *plain* branch of the green line (declaration read from the claim comment itself) states nothing about editing, so nothing false lives there and no reading was added to it. A claim comment could still have been edited into carrying its own declaration; the gate says nothing about that in either direction today. Extending the reading to that branch is a capability decision this card did not rule on, and adding it would have meant a new note on every corrected-free pair. Succeeding reader: whoever takes the escalation condition triage recorded on this card (a pair whose claim was edited *into* carrying the declaration ⇒ p1). - **noted, not filed** — the shallow checkout (`git rev-parse --is-shallow-repository` ⇒ `true`, 116 commits; this file reads as `+9132` insertions at the boundary commit) makes `git log -S` unable to answer whether the pass path ever failed to print its notes. Stated rather than answered; nothing in this PR rests on it. - **Net +39 lines** (9,925 → 9,964) against the card's +40 ceiling. One file. No changeset: `scripts/pm/**` publishes nothing, hence `skip-changeset`. - `scripts/pm/**` is not a governed path, so no 维护者速读 section is owed. ## Gates Derived from the worktree after the last commit, no paths passed: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `0128cbb` ⇒ **34 commands**, matching the dispatch-time derivation. Each is run with its exit code captured **before** any pipe; the full table, its `--ran` reconciliation and the head sha it was taken on are in this card's `os-dev-report` comment, which is the machine-read record. --- _Generated by [Claude Code](https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 596090e commit 84a7e9e

1 file changed

Lines changed: 51 additions & 12 deletions

File tree

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

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
838838
'#18536: the lane-keyed owed population — spec and skills owe the record on EVERY round, other lanes owe none, a `yes` outside them is spec-lane work': 30,
839839
'#18862: cross-author LIVE claims with no `Release:` between — the hand-over the protocol never wrote, named; judged only after its effective instant': 52,
840840
'#16770: the exit-0 line says which carriers agreed — LABEL carriers — and that the PR body was not read': 14,
841+
'#18892: the claim comment\'s EDIT reading — taken from the two stamps already in hand, reported and never failed': 10,
841842
});
842843

843844
// DELETING an entry silences that battery's floor exactly as effectively as
@@ -851,8 +852,8 @@ const SELF_TEST_BATTERIES = Object.freeze({
851852
// by the one #16833 adds, and by the one #18456 adds, and by the one #18719
852853
// adds, and by the one #18683 adds, and by the one #18764 adds, and by the one
853854
// #18828 adds, and by the one #18536 adds, and by the one #18862 adds, and by
854-
// the one #16770 adds.
855-
const SELF_TEST_BATTERY_FLOOR = 35;
855+
// the one #16770 adds, and by the one #18892 adds.
856+
const SELF_TEST_BATTERY_FLOOR = 36;
856857

857858
// The key an assertion is filed under when no battery is open. It is not a
858859
// declared battery, so it reds by the same set difference rather than silently
@@ -1458,6 +1459,20 @@ function newestFirst(a, b) {
14581459
return b.index - a.index;
14591460
}
14601461

1462+
/**
1463+
* The claim comment's own EDIT reading, from the two stamps ALREADY on the row
1464+
* this reader holds — ⛔ never asserted, which is what #18892 measured: the note
1465+
* below testified 「NOT edited」 about objectui#9764's claim 5724909959, whose
1466+
* `updated_at` is 29 min past its `created_at`. THREE readings, ⛔ never two — a
1467+
* missing stamp is a GAP, ⛔ not an unedited; ⛔ report-only per that ruling, an
1468+
* edit is legitimate and moves no exit. The untaken testimony was the defect. */
1469+
function claimEditReading(row) {
1470+
const [c, u] = [row?.created_at, row?.updated_at].map((v) => (typeof v === 'string' ? v : null));
1471+
if (c === null || u === null) return 'whether it has been EDITED is NOT READ — its row carries no `created_at`/`updated_at` pair to compare, so ⛔ read that as a gap and never as "unedited"';
1472+
if (c === u) return `it reads UNEDITED — its \`created_at\` and \`updated_at\` are both \`${c}\``;
1473+
return `⚠️ it WAS EDITED at \`${u}\` (\`created_at\` \`${c}\`) — REPORTED and ⛔ never a failure: read its edit history before taking the declaration under it for the one the seat first wrote`;
1474+
}
1475+
14611476
/**
14621477
* Which correction, if any, governs this card's declaration — and when none
14631478
* does, WHY, in a sentence that names an act the claiming seat can perform.
@@ -1467,8 +1482,8 @@ function newestFirst(a, b) {
14671482
* here that a seat cannot answer would rebuild that door one room over, so the
14681483
* note always names what to post next.
14691484
*
1470-
* @param {{ id?: number|string, body?: string, created_at?: string }[]} commentRows
1471-
* @param {{ id?: number|string, body?: string }[]} pool — the governing claim rows.
1485+
* @param {{ id?: number|string, body?: string, created_at?: string, updated_at?: string }[]} commentRows
1486+
* @param {{ id?: number|string, body?: string, created_at?: string, updated_at?: string }[]} pool — the governing claim rows.
14721487
* @returns {{ state: 'none' }
14731488
* | { state: 'applies', value: 'yes'|'no', detail?: string, note: string }
14741489
* | { state: 'ignored', note: string }}
@@ -1520,8 +1535,8 @@ function applicableCorrection(commentRows, pool) {
15201535
const claimRow = claimById.get(chosen.claimId);
15211536
const claimSession = readSessionId(claimRow?.body);
15221537
const shared =
1523-
`it supersedes claim comment ${chosen.claimId}'s own declaration, which is NOT edited and ` +
1524-
'still reads as it was written. ⛔ Nothing was filled in on the seat\'s behalf: the value is ' +
1538+
`it supersedes claim comment ${chosen.claimId}'s own declaration, and ${claimEditReading(claimRow)}. ` +
1539+
'⛔ Nothing was filled in on the seat\'s behalf: the value is ' +
15251540
'the seat\'s, in the fixed spelling, and this script still writes nothing.';
15261541

15271542
if (claimSession === null) {
@@ -4796,8 +4811,8 @@ export function pairRows(pair, pairs = null) {
47964811
* a pair that answers 0 because a CORRECTION comment carries its declaration is
47974812
* answering about a carrier the claim comment does not hold, and a seat reading
47984813
* only `$?` must be able to find out from the run which comment was read. The
4799-
* row also states, in as many words, that the claim comment was NOT edited —
4800-
* because the whole value of the shape is that nothing had to be.
4814+
* shape's whole value is that nothing had to be EDITED, so the row states that
4815+
* as a reading it takes from the claim's two stamps, ⛔ never as one it assumes.
48014816
*/
48024817
export function c2CorrectionNote(pair) {
48034818
const d = cardDeclaration(pair?.cardComments ?? null);
@@ -6490,9 +6505,9 @@ export function greenPairLine({ pr, card, sibling = false, corrected = false, re
64906505
? 'readable in the fixed spelling on a SIBLING card this same PR delivers rather than on ' +
64916506
`this card (the reading above names which, and what it says), ${LABEL_CARRIERS_AGREE}`
64926507
: corrected
6493-
? 'readable in the fixed spelling on a CORRECTION comment superseding the claim\'s own ' +
6494-
'line (the reading above names which comment, and says the claim was not edited), ' +
6495-
LABEL_CARRIERS_AGREE
6508+
? 'readable in the fixed spelling on a CORRECTION comment superseding the claim\'s own line ' +
6509+
'(the ℹ️ reading printed above on stderr names which comment, and states what that claim ' +
6510+
'comment\'s own `created_at`/`updated_at` say about whether it was edited), ' + LABEL_CARRIERS_AGREE
64966511
: `readable in the fixed spelling, ${LABEL_CARRIERS_AGREE}`) +
64976512
(record
64986513
? ', and a review of record names this head (the note above says which comment it is, and ' +
@@ -7944,6 +7959,8 @@ export async function selfTest() {
79447959
const CLAIMED = (extra, o = {}) => ({
79457960
id: o.id ?? C_CLAIM_ID,
79467961
created_at: o.created_at ?? '2026-09-12T00:41:08Z',
7962+
// ⭐ EQUAL on a comment nobody edited: the default is the unedited control, and an edited fixture names `updated_at` alone (#18892).
7963+
updated_at: o.updated_at ?? o.created_at ?? '2026-09-12T00:41:08Z',
79477964
body:
79487965
`Claim: PM loop round 1\n` +
79497966
(o.session === null ? '' : `Session: \`${o.session ?? C_SESSION}\`\n`) +
@@ -8027,7 +8044,7 @@ export async function selfTest() {
80278044
t('⛔ …so the governing claim does not move, and the card is not re-claimed', governingClaim(REPAIRED_THREAD)?.createdAt === governingClaim(BROKEN_THREAD)?.createdAt);
80288045
t('⛔ nor is the correction read as a MISPLACED declaration — it is the designated second carrier', cardDeclaration(REPAIRED_THREAD).state !== 'misplaced');
80298046
t('the reading PRINTS: a note names the correction rather than answering 0 in silence', typeof c2CorrectionNote(repairedPair) === 'string');
8030-
t('…and says the claim comment was NOT edited', says(c2CorrectionNote(repairedPair), 'NOT edited'));
8047+
t('…and STATES the claim comment\'s edit reading, measured from its own two stamps (#18892)', says(c2CorrectionNote(repairedPair), 'UNEDITED'));
80318048
t('…and names the comment id it corrects, so a reader can find it', says(c2CorrectionNote(repairedPair), String(C_CLAIM_ID)));
80328049
t('…and states the attribution ceiling rather than claiming a verification', says(c2CorrectionNote(repairedPair), 'DECLARED identity, never a verified one'));
80338050
t('…and it rides as a NOTE, never as a finding — pairNotes carries it, pairRows does not', pairNotes(repairedPair).some((n) => n.code === 'C2-CORRECTION') && pairRows(repairedPair).every((r) => r.code !== 'C2-CORRECTION'));
@@ -9422,6 +9439,28 @@ export async function selfTest() {
94229439
t('⛔ CONTROL: the label the line names is the constant C1 compares, ⛔ not a second spelling of it', says(LABEL_CARRIERS_AGREE, CONTRACT_REVIEW_LABEL) && CONTRACT_REVIEW_LABEL === 'needs:contract-review');
94239440
t('the pair is still identified in the line, in the spelling the round reports paste', says(G_BRANCHES[0][1], '✓ check-clause2-carriers: PR #16761 / card #16568 — the clause-② declaration is readable in the fixed spelling'));
94249441

9442+
// -- #18892: the EDIT reading, taken rather than asserted. ⭐ Measured specimen:
9443+
// objectui#9764's claim 5724909959 was EDITED (`created_at` 2026-09-18T03:54:37Z
9444+
// vs `updated_at` 04:23:23Z) and the note testified 「NOT edited」 anyway. The
9445+
// retired sentence is ASSEMBLED below, or the source pin hits itself (#16770).
9446+
battery('#18892: the claim comment\'s EDIT reading — taken from the two stamps already in hand, reported and never failed');
9447+
const E_EDITED = [CLAIMED(MEASURED_PROSE[1], { updated_at: '2026-09-18T04:23:23Z' }), FIXED_CORRECTION('no')];
9448+
const E_UNEDITED = [CLAIMED(MEASURED_PROSE[1]), FIXED_CORRECTION('no')];
9449+
const E_NOSTAMP = [{ ...CLAIMED(MEASURED_PROSE[1]), updated_at: undefined }, FIXED_CORRECTION('no')];
9450+
const E_NOTE = (thread) => c2CorrectionNote(pair({ cardComments: thread }));
9451+
const E_SRC = readFileSync(SELF_PATH, 'utf8');
9452+
t('⭐ the EDITED specimen is REPORTED in the note, in as many words', says(E_NOTE(E_EDITED), 'WAS EDITED'));
9453+
t('…naming the edit instant beside the creation one, so a reader can open that comment\'s history', says(E_NOTE(E_EDITED), '2026-09-18T04:23:23Z') && says(E_NOTE(E_EDITED), '2026-09-12T00:41:08Z'));
9454+
t('⭐ an UNEDITED claim reads as a MEASURED unedited, naming both stamps it compared', says(E_NOTE(E_UNEDITED), 'UNEDITED') && says(E_NOTE(E_UNEDITED), 'created_at') && says(E_NOTE(E_UNEDITED), 'updated_at'));
9455+
t('⭐ a row carrying NO `updated_at` reads NOT READ — ⛔ never "unedited", which is this defect one room over', says(E_NOTE(E_NOSTAMP), 'NOT READ') && says(E_NOTE(E_NOSTAMP), 'never as "unedited"'));
9456+
t('⭐ the three readings are three DIFFERENT sentences — one sentence for all three was the defect', new Set([E_NOTE(E_EDITED), E_NOTE(E_UNEDITED), E_NOTE(E_NOSTAMP)]).size === 3);
9457+
t('⛔ REPORT-ONLY: an edited claim raises NO C2 finding, so the exit register does not move', pairRows(pair({ cardComments: E_EDITED })).every((r) => r.code !== 'C2'));
9458+
t('⛔ …and reads DECLARED at the seat\'s own value, exactly as the unedited thread does', cardDeclaration(E_EDITED).state === cardDeclaration(E_UNEDITED).state && cardDeclaration(E_EDITED).value === 'no');
9459+
t('⭐ the green line points at that reading and says what it STATES, ⛔ never testifying to the edit itself', says(greenPairLine({ pr: 1, card: 2, corrected: true }), '`created_at`/`updated_at`'));
9460+
t('⛔ …and the clause that asserted an unread fact is gone from the printed line', !says(greenPairLine({ pr: 1, card: 2, corrected: true }), 'says the claim was not edited'));
9461+
t('⛔ NEGATIVE, on the SOURCE: no branch of this file asserts the unread 「NOT edited」 any more', !E_SRC.includes(['still reads as', 'it was written'].join(' ')));
9462+
t('⛔ CONTROL — the same source read is not empty or misdirected: it reaches the reader this card added', E_SRC.includes(['function claimEdit', 'Reading('].join('')));
9463+
94259464
// -- The floor: every declared battery RAN, and ran its cases (#13489) -----
94269465
//
94279466
// Evaluated after every battery has had its chance and BEFORE the verdict, so

0 commit comments

Comments
 (0)