Skip to content

fix(pm): the clause-2 correction note READS the claim comment's two stamps instead of vouching it was never edited - #19298

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-18892-clause2-pair-edit-reading
Sep 20, 2026
Merged

os-steve merged 2 commits into
mainfrom
claude/issue-18892-clause2-pair-edit-reading

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

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

…ers — RED against the current message

The `--pair` green line and the C2-CORRECTION note under it both testify that
the claim comment 「is NOT edited and still reads as it was written」, with no
instrument behind either sentence. This commit lands the pin ALONE, so the red
run is on the record before the fix exists (#18892's filing discipline: a pin
that was never shown to fail is not evidence).

Measured specimen replayed as the fixture: objectui#9764's claim 5724909959,
`created_at` 2026-09-18T03:54:37Z vs `updated_at` 2026-09-18T04:23:23Z.

`node scripts/pm/check-clause2-carriers.mjs --self-test` → exit 1,
9 of 1071 cases fail.

Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
Co-authored-by: Claude <noreply@anthropic.com>
…tamps instead of vouching that it was never edited

`applicableCorrection`'s note ended with an integrity assertion the gate had no
instrument for — 「which is NOT edited and still reads as it was written」 —
printed unconditionally under every correction. `created_at` and `updated_at`
both arrive on the `/issues/comments` row the reader already holds, so the
reading cost no request; it simply was not taken.

`claimEditReading` takes it, in three readings and never two: EDITED names the
instant so a reader can open the comment's edit history, equal stamps are a
measured UNEDITED, and a row carrying neither stamp is NOT READ — never
reported as unedited. Report-only, per the #18892 ruling: the exit register does
not move, because an edit is a legitimate act and the silence was the defect.

The `--pair` green line's parenthetical follows the note it points at, and now
names the stream that note prints on.

`node scripts/pm/check-clause2-carriers.mjs --self-test` → exit 0, 1071 cases.
Net +39 lines against the card's +40 ceiling.

Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
Co-authored-by: Claude <noreply@anthropic.com>
@os-steve os-steve added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 20, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0128cbbe1223f3071a091351aac5355396278a39

① Derived judgments

  • Accept set: unchanged. The --pair exit register does not move on an edited claim (the head's own case 「REPORT-ONLY: an edited claim raises NO C2 finding」 passes), and the declaration reads declared at the seat's value on the edited and the unedited fixture alike. Judged correct — triage's option A (5725446014) is report-only by its own words.
  • Public surface: unchanged. 0 new export in the diff (grep over the diff), check-widening-tells --declaration no exit 0; claimEditReading is module-local. Judged correct.
  • Printed contract: the corrected branch of the green line no longer asserts an unread fact; it names the stderr stream the ℹ️ note prints on and says the note states the claim's own created_at / updated_at reading — three readings (EDITED with the instant · UNEDITED with both stamps · NOT READ on a missing pair), one sentence each. Judged correct: a missing stamp reads as a gap, ⛔ never as unedited.
  • Self-test battery: a #18892 battery pinned at 10 with 11 cases; SELF_TEST_BATTERY_FLOOR 35 → 36; the retired sentence is assembled at runtime so the source-negative pin cannot hit itself. Seat-run on this head: node scripts/pm/check-clause2-carriers.mjs --self-test exit 0, 1071 cases (origin/main at 5636641: 1060).

② Semver level

None — scripts/pm/** publishes nothing; skip-changeset on the PR is the correct declaration; no changeset file. Judged correct.

③ Boundary flags

Dev flags: open_questions empty. Two noted, not filed items read and accepted as filed: (a) the PLAIN branch of the green line still says nothing about editing — correct to leave, the p1 escalation condition triage recorded is its successor; (b) a hand-written --pair-json bag lacking updated_at now reads NOT READ — the intended loud direction, fixture-only. The dev's refutation of the PM's assumption 3 (the notes already print, on stderr; the pointer now names the stream) is accepted as the better reading of 「补上读数或删掉指代」.

Implemented-by: claude/issue-18892-clause2-pair-edit-reading
Reviewed-by: session_017ETYWqMQD4qMtZzAGovWNi

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants