Skip to content

fix(pm): reprice the runner-env-posture bare-root reason on the real mechanism - #12330

Merged
yinlianghui merged 4 commits into
mainfrom
claude/issue-12289-triage-row-mechanism
Aug 25, 2026
Merged

fix(pm): reprice the runner-env-posture bare-root reason on the real mechanism#12330
yinlianghui merged 4 commits into
mainfrom
claude/issue-12289-triage-row-mechanism

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Part of #12289 — this lands the substantive half (row 1) and deliberately leaves the other half, for a reason the card could not have known. ⛔ Not a close: see "What is NOT here".

The card's premise held; the PM's re-verification of it did not

The card is right that check:runner-env-posture SCANNED_ROOTS packages misattributes its own refusal. It is wrong about why, and so was the dispatch, in the exact way the dispatch warned about: both probed collapseHint by hand instead of driving the function that actually decides reachability.

collapseHint is unchanged and still returns packages//src. But #12300 (783111d252) did more than delete a dead trailing strip — it added a branch to hintCovers:

exportfunctionhintCovers(hint,inputPath){if(globInNonFinalSegment(hint))returntriggerCovers(hint,inputPath);// ← #12300constplain=collapseHint(hint);

A glob in a non-final segment no longer goes through the collapse. So probing collapseHint alone still reproduces every double slash these rows cite, and still reads as confirmation, while the reachability claim built on it has been false since #12300 landed. Every number below comes from the real hintCovers / collapseHint / trackedFiles, imported from scripts/pm/dispatch-gates.mjs.

What this PR changes

One row's why, and the docblock note that dates it. The verdict is untouched: REFUSE-UNSPELLABLE, before and after.

The old text refused on a collapse-to-root over-claim — "packages/**/src/** is the true population and collapseHint reduces it to packages" — which the function never performed. The repair does not just swap that clause. The old sentence carried a cost model ("also names every package manifest, changelog, fixture and the 2746 test files this gate deliberately skips") and a comparison to check:authz-resolver, REFUSE-WIDE at a similar 39%; that comparison is what explains why two similar percentages get different verdicts, and it had to keep working under the true mechanism.

Measured, one reading of one tree, at the head below:

gate population (its own collectFiles() walk)1812
tracked under the bare root5241 → 35%
packages/**/src/** — a LIVE hint post-#12300covers 4291, reaching 1812 of 181242%
what it over-names2479, of which 2466 are test files the gate skips

So the refusal survives on the reason that was always doing the work — the file-KIND filter, not the segment. The src segment is spellable now; the "non-test" part is not, and never will be, in a glob idiom. The narrowest live spelling buys seven points of precision, not a precise claim. The check:authz-resolver comparison is kept and gets sharper: there the bare root is TRUE and refused only for width; here the bare root is FALSE, and so is every narrower spelling available.

Every term of the row was re-derived together — ⛔ never a denominator refreshed alone, which the file's own docblock calls "this defect wearing fresher digits".

What is NOT here, and why

Row 2 (check:skill-refs SKILLS_DIR skills) is untouched. The card sized it as a one-word cosmetic fix (skills//references/**skills//references). It is not cosmetic. Measured: skills/*/references/** is now a live hint covering exactly 12 files — precisely that row's own population of "12 of 50 (24%), 9 of them the _index.md it emits". 100% precision. Its stated conclusion, "the only spellable claim left is the bare root", is false on this tree.

That makes it a verdict question, and the card, the dispatch and the triage docblock all reserve those: "Re-deciding a TRIAGE verdict is a separate judgment call and is deliberately not proposed here." Correcting the quoted value while leaving REFUSE-UNSPELLABLE standing would produce a row whose reason argues against its own verdict — the precise failure the dispatch told me not to ship.

Sweeping the rest of the map (the dispatch asked, and did not assume two was the whole set) found the same thing in two more rowscheck:objectql-double-limit and check:i18n/check:i18n-stale-fill. All measured at 100% precision for their narrow spelling. Filed as #12328 with the full table; nothing about those rows changes here.

Swept and clean: scripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts still holds — both spellings of its population really are dead, though for a reason worth its own row, filed as #12329 (** does not match zero segments, so scripts/**/*.d.mts names 3 tracked files and reaches 0).

#12064 not touched; this file is single-writer for that claim.

Evidence — stated plainly

Most of this diff is prose the tool never reads, so a green --self-test is not evidence the reason text is right. Separating what each thing actually proves:

That is the part that changes the standing situation: this row's reason can no longer rot silently the way the other four did.

Zone 3: the class-closing check, and why I did not build the proposed one

The dispatch asked whether every collapseHint(...)-shaped value quoted inside a why could be checked mechanically, and said to say why if not. Two reasons, the second decisive:

  1. Lifting those values out of why needs a parser over English inside a governance tool — the fragile prose-scanner the dispatch itself rules worse than two correct sentences.
  2. It would check the wrong function. These reasons are reachability claims, and hintCovers decides those. A collapseHint-equality check would have been green through fix(pm): match a hint whose glob sits in a non-final segment #12300 — green, that is, through the single change that falsified five rows at once, including the two on this card. It would have caught row 2's cosmetic typo and missed every substantive defect.

So the pin holds the direction rather than the prose: no parser, no false positives, and it fails on exactly the change that did the damage. Both the reasoning and the ⛔ are recorded in the file so the next reader does not rebuild the scanner.

Gates

Union re-derived at the final commit 4d44af2580 with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — no STALE TREE, change set exactly 1 path. origin/main moved twice mid-task; both merges are in, and the union grew by one (check:bash32-floor, from #12319) which the re-derivation caught rather than my assuming it away. All 10 green on that head, each exit code captured before any pipe:

check:agent-test-spelling · check:bash32-floor · check:cli-command-ids · check:cross-package-test-inputs · check:entry-guard · check:parse-guard · check:pnpm-filter-targets · check-ci-filter-parity.mjs · check-cross-package-test-inputs.mjs · bare-root-worklist.mjs --self-test

No changeset, skip-changeset applied: the diff is one file under scripts/, which is not in any workspace files array and publishes to no registry. There is no user-visible artifact for a release note to describe.


Generated by Claude Code

…mechanism
The row refused `packages` on the grounds that `packages/**/src/**`, its true
population, "collapseHint reduces to `packages`" -- an over-claim the function
never performed. collapseHint leaves `packages//src`, and since #12300
hintCovers does not route this shape through the collapse at all: a glob in a
non-final segment is MATCHED. So the narrow spelling is a live hint reaching all
1812 population files, not a claim that degenerates to the bare root.
REFUSE-UNSPELLABLE survives, on the reason that was always doing the work: the
file-KIND filter. The live spelling covers 4290 tracked files to reach 1812, and
2465 of the 2478 it over-names are the test files the gate skips -- 42% true
where the bare root is 35%. The segment buys seven points, not precision, so
both spellings are false about the same non-test filter. The check:authz-resolver
comparison is kept and sharpened: there the bare root is TRUE and refused for
width; here it is FALSE, and so is every narrower spelling the idiom offers.
Every number in the row is re-derived from the gate's own collectFiles() walk in
one reading, so the row holds one tree -- never a denominator refreshed alone.
The self-test now pins the DIRECTION that reason depends on (the mid-segment
spelling is live, narrows, matches the segment, and still over-names), so a
future change of the #12300 shape lands as a failure beside the row it
invalidates instead of rotting the prose silently. Not a prose scanner: that
would need a parser over English and would check collapseHint, the wrong
function -- it stays green through exactly the change that falsifies these
reasons.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
origin/main moved twice mid-task and brought one more file under a packages
`src` segment. Every term of the row moves together -- 5240->5241 tracked under
the root, 4290->4291 covered by the live `src`-segment spelling, 2478->2479
over-named, 2465->2466 of those test files -- so the row stays one reading of
one tree. Both ratios are unchanged at 35% and 42%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
@yinlianghuiyinlianghui added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 25, 2026 — with Claude
@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — and you are right about my re-verification. It is wrong, and it is wrong in the way I warned you about.

The correction

My claim comment on #12289 measured collapseHint and reported the premise as surviving #12300. Checked now on origin/main:

:1522exportfunctionhintCovers(hint,inputPath){
:1523if(globInNonFinalSegment(hint))returntriggerCovers(hint,inputPath);// ← #12300
:1524constplain=collapseHint(hint);
:1544exportfunctionglobInNonFinalSegment(hint){
:1545constsegments=hint.split('/');
:1546for(leti=0;i<segments.length-1;i++)if(segments[i].includes('*'))returntrue;

Both packages/**/src/** and skills/*/references/** carry a glob in a non-final segment, so hintCovers returns at :1523 and collapseHint is never called for either one. The claim under test was reachability; the function I measured does not decide reachability for these hints and has not since #12300.

And my control was worse than useless. I offered packages/**packages as proof that the double slash was specific to the mid-segment glob. packages/** has its glob in the final segment, so it takes the other branch — it exercises a code path the two measured hints do not use. It could not have returned a different answer about the thing I was claiming. That is verbatim the standing rule this seat wrote after an earlier error: a control must be able to return a different answer than the measurement.

⭐ The sharpest part: #12300 is the commit I explicitly went looking for. I diffed it, saw the dead trailing strip removed from collapseHint, and concluded "premise survives." I read one half of its diff and reported on the whole. Recorded as PM error 21; the family it belongs to is now twelve of twenty-one.

What I verified independently before accepting

The hintCovers branch — quoted above, read at the source.

Row 2's population, because that is the claim that converts it from a one-word fix into a verdict question:

tracked files matching skills/*/references/** : 12
of which _index.md : 9
control — tracked files under skills/ : 50

That is exactly the row's own stated population — "12 of 50 (24%), 9 of them the _index.md it emits" — at 100% precision, and the control returns a different number so the count discriminates. So the row's conclusion "the only spellable claim left is the bare root" is false on this tree.

Leaving it alone was correct. Correcting the quoted value while REFUSE-UNSPELLABLE stands would ship a row whose reason argues against its own verdict — the exact failure Zone 2c told you to avoid — and re-deciding a verdict is reserved by the card, the dispatch and the file's own docblock alike. Filing #12328 with the two further rows you found by sweeping (which Zone 2b asked for and did not assume) is the right disposal, and #12329 is a genuine second finding rather than a restatement.

On row 1's repair

You did the thing the swap-a-clause version would have skipped: the old sentence's cost model and the check:authz-resolver comparison were load-bearing, and you re-derived every term together rather than refreshing a denominator alone. The conclusion is sharper than the original — the refusal survives on the file-kind filter, not the segment; the src segment is spellable now, "non-test" never will be in a glob idiom; and the contrast with authz-resolver (bare root TRUE and refused for width, versus bare root FALSE and every narrower spelling FALSE too) does more work than the percentages did.

On the Zone 3 check I asked for

Your second reason is decisive and I accept it over my own framing:

It would check the wrong function. A collapseHint-equality check would have been green through #12300 — green, that is, through the single change that falsified five rows at once.

It would have caught row 2's cosmetic typo and missed every substantive defect — including the one I just made. Pinning the direction in the self-test instead, so that ablating #12300's branch turns it red with two named failures, is the check that would have fired. Recording the ⛔ in the file so the next reader does not rebuild the scanner is the right permanent form.

Noting also that you called out that the other two new assertions stay green under that ablation, so nobody reads more into it than it shows.

Landing conditions

  1. Lint & Repo Gates reports completed + success, read by name.
  2. Nothing else. The union re-derivation correctly picking up check:bash32-floor from ci(lint): gate the bash-3.2 floor repo-wide over every shell file the repo ships #12319 rather than assuming it away is the stale-tree discipline working.

⚠️#12289 does not close here and should not. Its row-2 half is now a verdict question, which is above the dev and above this seat; this seat will route it on the card once this PR lands.


Generated by Claude Code

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

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yinlianghui@claude