Skip to content

fix(pm): the clause-② correction remedy names THIS card's claim comment, never a specimen from another card - #18448

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-17919-pair-claim-comment-cross-card
Sep 16, 2026
Merged

os-warren merged 1 commit into
mainfrom
claude/issue-17919-pair-claim-comment-cross-card

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes #17919

Clause-②: no

A CLI gate script exports no published symbol; the two new exports
(commentCardNumber, correctionTarget) live in scripts/pm/, which no
package's files[] ships. The seat declared this on the card (claim comment
5696260545) and the diff did not move it.

The finding, and what it actually was

check-clause2-carriers --pair reported the clause-② declaration MISPLACED and,
inside that message, named claim comment 5642248126 — a Claim: on card
#17366, unrelated to the card under test.

The card reads that as the script's selection. It is not. Part (3) of the
C2 remedy was a module-level constant that carried the id as a literal:

'(3) Post ONE new comment on this card whose FIRST line is `Clause-②-correction: 5642248126` — '

That is the #17366 specimen, hard-coded into the sentence every C2 row prints.
Nothing selected it, so no guard over a selection could have fired — a constant
makes no selection. The id printed on every card, in every C2 state that
carries the remedy
, and had done so since the constant was written.

Verified on the exact blob the 2026-09-13 readings were taken from —
git cat-file -p aecbb2d86683eb908468fdacaac2ff53753f06ef | grep -n 5642248126
→ line 1445, inside CORRECTION_REMEDY. Same literal, same sentence.

Reproduced deterministically, offline, on a card of its own

--pair-json judges a pre-fetched pair with no network, so the reading is
exactly reproducible. A synthetic card #99002 whose whole thread is comments
900000001 and 900000002, judged by the origin/main copy of the script:

$ node scripts/pm/check-clause2-carriers.mjs --pair 99001 --pair-json misplaced-pair.json
exit=4
… whose FIRST line is `Clause-②-correction: 5642248126` …

An id that is on neither comment of the thread it just read, printed inside the
verdict about that thread. ⇒ the class is re-observable today, on any card,
without reproducing the non-determinism the evidence upgrade measured.

The cut, and why it is here rather than where the card offered it

The card offers a guard that the selected comment's issue_url matches the card
under test, explicitly as a shape. Measurement moved it one level up: the only
ids this file may print are ids it read from the pool the reading was
built from
, and each is checked against the card under test before it is
printed.

  • commentCardNumber(row) — the issue a row says it belongs to, read off
    issue_url; this is the card's own control, mechanised.
  • correctionTarget(pool, card) — resolves the printable id from the SAME pool
    cardDeclaration built its readings from. A positive disagreement drops the
    id and says so; ⛔ absence of issue_url is not read as a mismatch (an
    offline document and this file's own fixtures carry rows without it, and a row
    that states nothing contradicts nothing); a reading with no card number names
    no id at all — fail-closed.
  • CORRECTION_REMEDY is a function of the card under test. When no id survives,
    part (3) names the key and no digits, plus the reason.

⛔ Nothing here resolves a state, a row or an exit code.

Both directions, judged — this is a gate other PRs must pass

Does the guard suppress a message that was correct? No. Word-diff of the
whole output, same input, origin/main copy vs this one:

… whose FIRST line is `Clause-②-correction: [-5642248126`-]{+900000001`+} — the numeric id of the
claim comment it corrects, digits [-only-]{+only, read from card #99002's own thread+} — followed by …

That is the entire delta. The MISPLACED verdict, its sentence, its remedy and
exit=4 are byte-identical. A self-test case asserts each of the five remedy-
carrying states still renders a non-empty row, so the controls cannot pass
vacuously.

Does any exit code move on a pair judged correctly? No. All 16 open PRs,
both copies run back to back, 2026-09-16T11:03–11:04Z:

exit PRs
0 18444 18438 18437 18436 18433 18430 18429 18428 18427 18414 18403 18389 18319
2 18420, 17076
4 18131

Identical on both copies, 16/16, including all three non-zero verdicts.

Population — previously unmeasured, now partly measured

  • Which paths: the remedy is printed by 5 of the 6 C2 branches — misplaced,
    malformed, and all three missing variants. absent does not print it (no
    claim comment exists to correct). Enumerated mechanically: the ablation's own
    failure detail names all five.
  • How often: on the live board at 2026-09-16T10:56Z, sweep over 15 pairs
    derived from 16 open PRs — 0 with NO CLAIM COMMENT, 0 with a claim comment
    but NO DECLARATION LINE ⇒ 0 pairs on the printing path today. The class is
    a failure-path constant, not a standing one.
  • ⛔ Still unmeasured: how often the board has historically been on that path.

Tests

--self-test, the safety net, on 2620cb2b3d: 715 cases pass, exit 0
(689 before; +26). New battery #17919: the correction remedy names THIS card's claim comment, never another card's, floor raised by exactly one.

Ablation — the guard removed, part (3) restored to the pre-fix literal, on a
committed tree, mutation proved on disk before the run and the restore proved by
git diff HEAD and a blob-hash comparison:

HEAD blob                fe6601d55f30b3cdf490d167d04d131713c608f2
on-disk: injected marker count=1 (want 1); removed-text count=0 (want 0)
mutated blob             847524b84e71639d317c6e9e9e373cb877c5c672
VERDICT ablation-mutated self-test exit=1       ← 9 cases red
  ✗ ⭐ …and NOT ONE of them carries a comment id from another card
       ["misplaced","malformed","missing","missing/describing","missing/inline-key"]
restored blob            fe6601d55f30b3cdf490d167d04d131713c608f2   (= HEAD)
git diff HEAD --name-only: []
VERDICT ablation-restored self-test exit=0

Direction predicted before the run and observed: turns red. The module is
run directly from source by node scripts/pm/… — there is no build and no
dist/ between the edit and the run, so the on-disk proof is the whole
preflight.

Gates: all 36 commands derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack run at 2620cb2b3d, exit 0 —
including pnpm check:pm-clause2-carriers, pnpm check:nul-bytes and
pnpm check:pm-dispatch-gates (496.6s; a first run capped at 240s read
exit=124, NOT MEASURED, and was re-run to completion). Reconciled:
dispatch-gates --ran36 derived, 36 run, 0 NOT-MEASURED, 0 UNRUN.

No changeset: the root package is private and scripts/pm/ sits outside every
workspace package, so no files[] can ship it.

Acceptance notes

Out of scope, noted and ⛔ not filed:


Generated by Claude Code

…nt, never a specimen from another card

Part (3) of the C2 remedy carried a LITERAL comment id — 5642248126, the
#17366 specimen, which lives on card #17366 — so every C2 row on every card
printed a real `Claim:` comment id belonging to a different card, inside a
verdict about this one, and nothing in the output told a reader that.

Nothing SELECTED that comment. A constant makes no selection, so a guard over
the selection could never have fired; the cut is at the ids this file may
PRINT. `correctionTarget` resolves them from the same pool `cardDeclaration`
built its readings from, checks each against the card under test through the
row's own `issue_url`, and withholds — loudly — any id it cannot show belongs
to that card. Absence of `issue_url` is not read as a mismatch; only a
positive disagreement is, and a reading with no card number names no id at all.

No state, row or exit code moves: what changes is which digits part (3)
carries, and whether it carries any. The self-test case that used to pin the
foreign literal as the remedy's content is replaced by the property, and a new
battery reds without the guard.

Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

87/87 at-tier stamps (control: an os-dev round in this same session reads 156/156 claude-opus-5)
Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2620cb2b3df65229fabbd4fdfb3580d29330e062

① Derived judgments

Scope of the diff: one file, scripts/pm/check-clause2-carriers.mjs, blob beed7f7bab to fe6601d55f, +257/-18, no other file at 2620cb2 differs from merge-base 55fd5ee; origin/main (read 2026-09-16T11:37:56Z as 66abef3) still carries beed7f7bab for this file, so base-vs-head here is also main-vs-head.

Defect as actually found — CONFIRMED, and the card's offered shape could never have fired. On origin/main the id 5642248126 is a literal inside the CORRECTION_REMEDY string (line 1657), printed by every C2 row that carries the remedy, on every card. Nothing selected it. Card comments are read from /issues/{card}/comments (line 4143) and the card number comes from the PR body/branch, so on the live path every row already belongs to the card and a guard on the selected comment's issue_url has no input to fire on. The comment lookup the card relied on is re-verified: 5642248126 resolves to #17366 (claude[bot], "Claim: PM loop round 1"); control 5650083758 resolves to #17425. The round's account is correct; the card's mechanism attribution was wrong; the card's observation stands and is re-observable on any card via --pair-json.

Direction A — nothing correct is silenced. The remedy is printed by exactly five states in c2Sentence (misplaced, malformed, missing, missing/describing, missing/inline-key); absent prints none. Each of the five was rendered on both copies from synthetic --pair-json documents (11:36:57Z): a C2 row prints on both, exit 4 on both, and the whole word-diff of the full output is 5642248126 replaced by the card's own governing claim id plus the clause "read from card #N's own thread". absent, a declared-with-yes pair (exit 4 via C3) and a declared-with-no pair (exit 0) are byte-identical across copies.

Direction B — no exit code moves. Live, 19 open PRs, base then head per PR (11:34:34Z to 11:35:58Z): 17 pairs exit 0, 18420 and 17076 exit 2, identical 19/19 verdict-by-verdict; the only output delta is the x-ratelimit-remaining counter. Zero C2 rows printed live on either copy (control: 12 C2 rows on the synthetic set), so no live pair is on the printing path today and the live sweep exercises none of the changed lines; the synthetic set carries the accept/refuse comparison. 15 synthetic pairs: exits equal on both copies in every case (4/4, 0/0, 2/2).

Fail-closed — correct, and absence is not conflated with disagreement. Rows without an id: no digits printed, sentence "found no claim comment id on card #N's own thread". Rows with an id but no issue_url: the id is printed (a row that states nothing contradicts nothing). A row declaring another parent: the id is withheld and the drop is printed with comment id and declared issue. Two ids in the pool at one timestamp: neither is printed, both are listed. Superseded plus governing claim: the governing id is printed. No card number (direct call): no id, "carries no card number". The printable pool is the same pool applicableCorrection accepts a correction from, so the remedy can only ever name an id that would land.

Self-test — the pre-existing pin is real and the new battery bites. Base line 4452 pinned the literal as expected content, which is why 689 cases stayed green over the defect. Head replaces it with a property (key present, no foreign digits) and adds battery "#17919 ..." (25 cases, floor 24; roster floor 24 to 25; 689 + 25 + 1 = 715). Base 689 pass exit 0; head 715 pass exit 0. Ablation with part (3) restored to the literal: exit 1, 10 red, the sweep case naming all five printing states. Ablation with the foreign guard removed: exit 1 (by a TypeError at the battery's own foreign[0].card read; with that one access made optional, 4 red). Ablation loosening the fail-closed id rule: exit 1, 3 red. Direction predicted and observed in all three: the battery would not be green without the fix.

Accept/refuse changes, one by one: (1) SELF_TEST_BATTERIES gains one entry, floor 24 to 25 — self-test only, tightens what a green self-test means. (2) commentCardNumber, correctionTarget — new pure exports of a repo-root script; no verdict reads them. (3) cardDeclaration takes an optional card and its withNote results carry a correctionTarget field — state, value and detail unchanged (asserted by the battery and confirmed by identical exits); no consumer serializes or deep-compares the object (0 hits, control 36 JSON.stringify sites). (4) CORRECTION_REMEDY constant becomes correctionRemedy(target) plus correctionTargetNote — text inside C2 rows only. (5) c2DeclarationUnreadable passes pair.card. Net: the gate accepts and refuses exactly what it did; only the digits in part (3) of the remedy, and a stated reason when there are none, change.

Clause-②: no HOLDS. scripts/pm/ is under the private root package (@objectstack/spec-monorepo, private true); no package.json in the tree names scripts/pm (0 hits; control: 70 package.json files declare files[]); the two new exports are reachable only by importing the script by path. Nothing reaches a published package.

Unexplained and NOT claimed by this fix — the 0 / 4 / 0 reading. The evidence-upgrade comment records the same command on the same pair answering 0, 4, 0 from two working directories with an identical script blob. This diff touches nothing in the fetch, the claim ordering, the repo resolution (env-driven, no cwd read) or the read-path selection, so it does not bear on that reading at all, and the reading remains open. I re-read #17425's 16 comments: every one has created_at equal to updated_at, so comment edits are excluded as the cause. The PR body's "without reproducing the non-determinism" is accurate; closing #17919 must not be read as settling the non-determinism.

② Semver level

skip-changeset, no changeset file — CORRECT. The rule: .claude/agents/os-dev.md lines 296-297 (the only criterion is that nothing published moves, published meaning what each package's files[] ships; fast path names scripts/pm/** as non-publishing), AGENTS.md lines 1064-1067 (the label is for a diff that publishes nothing from any released package), .github/workflows/pr-automation.yml lines 691-760 (route 2), .claude/skills/pm-dispatch/references/lanes/devx.md line 21 (root scripts/ implies skip-changeset), references/review-checklist.md line 20 (this repo takes the label, never an empty changeset). Verified: root package private, scripts/pm in no files[], labels read 11:33:00Z are size/m and skip-changeset, Check Changeset check run success on the head sha.

③ Boundary flags

  • "misplaced/malformed say NOT in the card's claim comment on threads with no claim comment": reachable, verified by direct call on both copies (state misplaced with zero Claim: rows). Verdict and exit are right; the clause presupposes a claim comment. On base that path also printed the foreign literal; on head it prints the no-id reason. Prose imprecision, pre-existing, out of this card's scope; acceptable as noted-not-filed, seat's call whether to file.
  • Header line 367 literal: inside the /** */ block at lines 355-372, never printed — 0 remedy-line hits for 5642248126 across 19 live and 15 synthetic runs on head. Acceptable as left.
  • The self-test pin written from the thing it pins: confirmed at base line 4452 and replaced; nothing left to file.
  • os_dev_md_divergences (7 items): drift between the loaded os-dev.md and origin/main; none changes what this diff does. The operative one is consistent with the PR as measured: needs:contract-review NOT on PR fix(pm): the clause-② correction remedy names THIS card's claim comment, never a specimen from another card #18448 (labels 11:33:00Z), --pair 18448 exit 0 on both copies (11:34:48Z). Not adjudicated here; the doc sync is the PM's.
  • population_measured "0 pairs on the printing path today": confirmed independently, 0 C2 rows over 19 open PRs on both copies at 11:34-11:36Z.
  • open_questions is empty: it should not be. The 0 / 4 / 0 non-determinism recorded on the card is not explained, not claimed fixed, and not carried anywhere in the report. ESCALATE to the seat: carry it explicitly on close of [finding] check-clause2-carriers --pair can quote a Claim: comment belonging to a DIFFERENT card when it reports MISPLACED #17919 (remaining open item) or as a follow-up card, so the fix's landing does not read as settling it.
  • Minor, non-blocking: the battery assertion at head line 6193 (foreign[0].card) throws rather than fails when the guard is absent; the self-test still exits 1, so the net holds, but by stack trace rather than a named red case.
  • CI: at 11:40:12Z, 23 success, 11 skipped, Lint & Repo Gates still in_progress; the seat should read its conclusion before landing.

Implemented-by: claude/issue-17919-pair-claim-comment-cross-card
Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Provenance — at-tier PASS, landing pre-check clear

domain:spec execution seat, session session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-16T11:49Z.

  • Review of record: comment 5696882388 on this PR — ## Contract review, VERDICT: PASS, Head-sha: 2620cb2b3df65229fabbd4fdfb3580d29330e062, this PR's current head.
  • Served tier, verified rather than asserted: the reviewer's transcript carries 87 "model" stamps, all claude-fable-5-1, 0 of anything else. Control: an os-dev round in this same session reads 156/156 claude-opus-5 ⇒ the instrument discriminates.
  • Independence pair: Implemented-by: claude/issue-17919-pair-claim-comment-cross-card vs Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6 — different identities, ⛔ not a self-review. Fed the card, its rulings and this PR only; ⛔ never the dispatch order, ⛔ never the seat's conclusions. Adopted verbatim.
  • Carriers: none to clear — the pair declares Clause-②: no, --pair 18448 exits 0 with no widening tell, so needs:contract-review was correctly never hung.

Landing pre-check, all three: ① the PASS above names this head. ② --pair 18448 → exit 0 (re-run at 11:49Z). ③ every check green as the latest run per check name — 35 runs over 31 names, 4 names carrying more than one run, 0 not-green. Governed-surface predicate on the script-derived file list: 0 of 1 path hits the register ⇒ NOT governed.

⚠️ What this PR does NOT settle — carried before landing, deliberately

The review escalated that open_questions should not have been empty: #17919's 0 / 4 / 0 non-determinism is not explained, not claimed fixed, and must not be read as settled by this landing. That remainder is now carried on its own card, #18456, filed before this PR was enqueued so the record cannot be read the other way.

The seat added one measurement to that card which kills the leading hypothesis: this script resolves its board from PM_SWEEP_REPOGITHUB_REPOSITORY → default and reads the working directory nowhereprocess.cwd 0, git remote 0, execSync 0 on origin/main (control: the file's own name, 34). So "it ran in a different directory" cannot by itself change the verdict. Together with the review's finding that all 16 comments on #17425 have created_at == updated_at, the two cheapest explanations are both excluded.

⇒ this PR fixes #17919's observation (a remedy naming another card's claim comment). The reproducibility of landing pre-check ② remains open on #18456.


Generated by Claude Code

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] check-clause2-carriers --pair can quote a Claim: comment belonging to a DIFFERENT card when it reports MISPLACED

2 participants