fix(pm): let every delivery row state the evidence it rests on - #16798
Merged
Conversation
`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
This was referenced Sep 8, 2026
hotlong
marked this pull request as ready for review
September 8, 2026 06:16
hotlong
enabled auto-merge
September 8, 2026 06:16
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16706
prDeliversCardanswers one bit, and five readers print rows from it. Atruesourced from a closing keyword and atruesourced 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
deliveryEvidencebeside 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.
partOfRe/refsReThe 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
partOfTargetsis also read on the commit surface — over the complete 512-commit window of this checkout:Part of #Ndeclaration-position / elsewhereRefs #Ndeclaration-position / elsewhereThe single body-surface match elsewhere is a real declaration. PR #16543's opening line reads:
Part ofsits 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 itspartOfset and dropsprDeliversCardthrough to the branch-name fallback — the channel its own docblock reserves for bodies that declare nothing. Measured directly:One real declaration lost is exactly the cost the ⛔ in
prDeliversCard's docblock names, so the relation stays wide. The one commit-surfaceRefsmatch elsewhere (* wip: changeset + ledger refs #15728) is prose, not a declaration.The
Refsaxis 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 aspartOfRe, 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 precedenceprDeliversCardalready used, ornull:closing-keywordpart-ofPart of #Nat the declaration positionpart-of-inlinePart of #Nelsewhere on a line —branch-nameprDeliversCardnow derives its boolean asdeliveryEvidence(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 ofbeside 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,prDeliversCardisfalseand there is no evidence to report. Pinned in both files' batteries —check-half-states.mjs(#16706 specimen/#16706 control) andcheck-clause2-carriers.mjs(derivePairsand its C1 row).2. 阴性对照. A line-leading
Part of #7918still delivers and is gradedpart-of(list-item, blockquote and bold-wrapped forms too).Fixes #7760still parses in the same run, on the same body, and is gradedclosing-keyword.3. 五个读者逐一验证. Every name on the docblock's list has a reading:
#8354 (draft, ⚠️ via ...)prFullyDeliversCardclaimDeliverycheck-clause2-carrierspairing / C1evidence; the C1 row prints it4.
Refs侧读数. The hole is open on that axis too: a proserefs #9999 already landedreachesrefsTargetsexactly as the accounting sentence reachedpartOfTargets. 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.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackand 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 governed — ordinary queue landing applies to a PR with exactly this file list.skip-changeset: the root package is private and no publishedfiles[]ships ascriptsdirectory, 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, sopart-of-inlinebecomes unreachable.check-half-states.mjsbloba455bce3479780918dddf9f23472c447f4d1218ab47367eb375f1ef297f8e2b2346b7f403c4b5ac8git checkout HEAD --a455bce3479780918dddf9f23472c447f4d1218aOn-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 HEADis empty — never by an exit code. The mutation ran under atrap ... 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.
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 theevidencefield prints exactly as it always did, which is pinned too.Generated by Claude Code