Skip to content

fix(pm): let every delivery row state the evidence it rests on - #16798

Merged
hotlong merged 3 commits into
mainfrom
claude/issue-16706-partof-prose-match
Sep 8, 2026
Merged

fix(pm): let every delivery row state the evidence it rests on#16798
hotlong merged 3 commits into
mainfrom
claude/issue-16706-partof-prose-match

Conversation

@hotlong

@hotlong hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #16706

prDeliversCard answers one bit, and five readers print rows from it. A true sourced from a closing keyword and a true sourced from the two words "part of" landing in an accounting sentence printed identically, so a row manufactured by the second was indistinguishable from a real finding.

This adds deliveryEvidence beside the boolean and threads the kind into what each of the five readers prints. The relation is not narrowed — the position is reported, never rejected.

What landed, and what did not

The dispatch carried two remedies. Only one of them survived its own gate.

remedy disposition
3 — every row states the evidence it rests on landed
1 — require the declaration position in partOfRe / refsRe NOT landed — the corpus gate refused it (回翻条款 below)

The corpus gate, and why remedy 1 was refused

The PM's ruling conditioned remedy 1 on a corpus measurement: any match that is a real declaration outside the declaration position ⇒ stop. Measured over the 299 merged PR bodies reachable in three pages of 100 (2026-09-08), and — because partOfTargets is also read on the commit surface — over the complete 512-commit window of this checkout:

surface Part of #N declaration-position / elsewhere Refs #N declaration-position / elsewhere
PR body (299 merged) 29 / 1 16 / 0
commit message (512) 34 / 0 8 / 1

The single body-surface match elsewhere is a real declaration. PR #16543's opening line reads:

Refs #15858 (item 1) · Part of #15858 (item 1 of the two the card names).

Part of sits second on the line only because the author put the two declarations side by side. That body carries no closing keyword, so a narrowed extractor empties its partOf set and drops prDeliversCard through to the branch-name fallback — the channel its own docblock reserves for bodies that declare nothing. Measured directly:

TODAY   partOfTargets         : ["15858"]
TODAY   closingKeywordTargets : []
TODAY   prDeliversCard(15858) : true
REMEDY1 partOf decl-position  : []          <- the declaration is gone

One real declaration lost is exactly the cost the ⛔ in prDeliversCard's docblock names, so the relation stays wide. The one commit-surface Refs match elsewhere (* wip: changeset + ledger refs #15728) is prose, not a declaration.

The Refs axis alone would have passed its gate (16/0 on bodies). It is still not narrowed here, for a stated reason: refsRe's docblock asserts "Same strictness as partOfRe, on purpose", and moving one of the two would break the symmetry the two docblocks assert and start the second dialect the ruling warns against. Reported for a separate decision rather than taken unilaterally.

The mechanism

deliveryEvidence(pr, n) returns the source kind in the precedence prDeliversCard already used, or null:

kind meaning
closing-keyword a closing keyword bound to the card — the strongest evidence there is
part-of Part of #N at the declaration position
part-of-inline Part of #N elsewhere on a line — ⚠️ position, not authorship
branch-name no declaration at all; the card comes from the branch name

prDeliversCard now derives its boolean as deliveryEvidence(pr, n) !== null. That is anti-drift, not a behaviour change: every input answers exactly what it answered before, and the two can never be edited apart.

⛔ The closing keyword is graded first, so a stray inline part of beside a real keyword cannot make a row read as unattributed.

验收备注

The ruling's six acceptance items, one by one.

1. 两侧对照必须都在测试里. Both sides are pinned, on the specimen sentence quoted verbatim from the card. With the sentence, the pair is still reported (the relation is unchanged) and is now graded part-of-inline; with that one sentence deleted and nothing else changed, prDeliversCard is false and there is no evidence to report. Pinned in both files' batteries — check-half-states.mjs (#16706 specimen / #16706 control) and check-clause2-carriers.mjs (derivePairs and its C1 row).

2. 阴性对照. A line-leading Part of #7918 still delivers and is graded part-of (list-item, blockquote and bold-wrapped forms too). Fixes #7760 still parses in the same run, on the same body, and is graded closing-keyword.

3. 五个读者逐一验证. Every name on the docblock's list has a reading:

reader reading
H8 open side row prints #8354 (draft, ⚠️ via ...)
H8 merged side, via prFullyDeliversCard evidence printed beside the merge date
H31 carrier comparison row names the evidence the pairing rests on
claimDelivery no printed row of its own — its only consumer fires on zero delivery, so the count's whole effect is to SUPPRESS a row. The kinds ride on the return shape, its only surface, and are pinned there
check-clause2-carriers pairing / C1 pair carries evidence; the C1 row prints it

4. Refs 侧读数. The hole is open on that axis too: a prose refs #9999 already landed reaches refsTargets exactly as the accounting sentence reached partOfTargets. Pinned as a reading, and reported rather than narrowed — see the corpus section.

5. ⛔ 不要在 prDeliversCard 内部收窄关系. Nothing narrows anywhere. prDeliversCard's verdict is byte-identical on every input, and the position judgement is a label attached beside it, never a filter. The one structural change is that the boolean is now derived from the evidence function so the two cannot drift.

6. objectui PR #8354 的正文 ⛔ 不要去改. Not touched. ⚠️ Reported rather than absorbed: that body had already been respelled by its own author before this work began, and the PR merged 2026-09-08T00:48:57Z. Its "Serial constraints" section now carries zero verbs beside the card number and names this card. So the ruling's acceptance signal — "修好之后它应当自然不再产生 C1 行" — can no longer be read off that PR: it stopped producing the row for the author's reason, not for this change's. The specimen is therefore pinned from the card's verbatim quote of the original body instead, which is why both sides are in the battery.

Verification

Exit codes captured by redirect before any pipe.

  • 37 of 37 derived gate families run, 0 NOT-MEASURED, on the merged head. Re-derived in the worktree with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack and reconciled with --ran:
    ✓ dispatch-gates --ran: 37 derived famil(ies) accounted for — 37 run, 0 NOT-MEASURED.
    The derivation adds one family the dispatch's list did not carry — pnpm check:pm-clause2-carriers — because this diff now touches that file. It is run and green.
  • ✓ check-half-states self-test: 2749 cases pass.
  • ✓ check-clause2-carriers self-test: 260 cases pass (...)
  • ✓ check-partof-closing-keyword self-test: 89 cases pass.
  • check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8289 tracked file(s), all registered).
  • ✓ dispatch-gates self-test: 1552 cases pass.
  • pnpm lint (repo-wide, eslint . --no-inline-config) — exit 0 over the full 6351-file population, so no narrowing argument is needed.
  • Not a governed surface, on the final two-file list: ✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.
  • skip-changeset: the root package is private and no published files[] ships a scripts directory, so nothing published moves.

Ablation

Remedy 1 did not land, so the prescribed ablation target changed with it: what is ablated is what did land — the declaration-position classifier. Stripping its anchoring makes it identical to partOfRe, so part-of-inline becomes unreachable.

leg check-half-states.mjs blob self-test
this branch a455bce3479780918dddf9f23472c447f4d1218a exit 0 — 2749 pass
anchoring stripped on disk b47367eb375f1ef297f8e2b2346b7f403c4b5ac8 exit 1 — 5 failed
restored, git checkout HEAD -- a455bce3479780918dddf9f23472c447f4d1218a exit 0 — 2749 pass

On-disk proof of the mutation, not the editor's exit code: anchored-regex lines went 2 to 1 and the injected unanchored form counted 1. The two blobs differ, so the middle leg is a real change and not a no-op. Restoration is proven by state — the blob matches HEAD again and git diff HEAD is empty — never by an exit code. The mutation ran under a trap ... EXIT INT TERM.

The five reds are the point: one grading assertion plus four of the five readers' printed rows, which is the reader-reaching requirement demonstrated adversarially.

✗ #16706 specimen: …and the row can now say the match was not at the declaration position (got "part-of", want "part-of-inline")
✗ #16706 reader H8-open: an inline-sourced open half says so in the row
✗ #16706 reader prFullyDeliversCard: …and the merged row states the evidence beside the date
✗ #16706 reader H31: …and names the evidence the pairing rests on
✗ #16706 reader claimDelivery: …and the suppression is attributable

Three existing pins moved one spelling on

The row parenthetical now carries the evidence beside the draft marker, so (draft) is pinned as (draft,. Their subject — "is this row marked a draft", "does it name the PR" — is unchanged. A pair from a caller that predates the evidence field prints exactly as it always did, which is pinned too.


Generated by Claude Code

`prDeliversCard` answers one bit, and five readers print rows from it --
H8's open side, H31's carrier comparison, `claimDelivery`, the pairing
`check-clause2-carriers` derives, and `prFullyDeliversCard`. A `true`
sourced from a closing keyword and a `true` sourced from the two words
"part of" landing in an accounting sentence printed identically, so a row
manufactured by the second was indistinguishable from a real finding.

Add `deliveryEvidence` beside the boolean, returning the source kind, and
thread it into what each of the five readers prints. `prDeliversCard` now
derives its boolean from it, so the two can never be edited apart; every
input answers exactly what it answered before.

The relation is NOT narrowed -- the position is reported, never rejected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY
Both sides of the specimen are the reading: one side alone cannot tell
"fixed" from "changed to never report". The relation is deliberately
unchanged -- the prose match still reports a delivery -- so what the two
sides pin is the evidence the row now prints, plus the control that
deleting the one sentence still removes the pair entirely.

Five readers, five readings: H8's open side, H8's merged side through
`prFullyDeliversCard`, H31's carrier comparison, `claimDelivery` (whose
only consumer fires on ZERO delivery, so the kinds ride on the return
shape -- its only surface), and the pairing `check-clause2-carriers`
derives, pinned in that file's own battery together with its C1 row.

Three existing pins move one spelling on: the row parenthetical now
carries the evidence beside the draft marker, so `(draft)` is pinned as
`(draft,`. Their subject is unchanged.

The `Refs` axis gets the reading the ruling asked for: the hole is open
there too, reported rather than narrowed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review September 8, 2026 06:16
@hotlong
hotlong enabled auto-merge September 8, 2026 06:16
@hotlong
hotlong added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit f6480bc Sep 8, 2026
37 checks passed
@hotlong
hotlong deleted the claude/issue-16706-partof-prose-match branch September 8, 2026 06:44
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

2 participants