Skip to content

test(pm): pin the test-file kind entry for check:cross-package-test-inputs against its own deletion criterion - #12324

Merged
yinlianghui merged 4 commits into
mainfrom
claude/issue-11199-cross-package-test-inputs-trigger
Aug 25, 2026
Merged

test(pm): pin the test-file kind entry for check:cross-package-test-inputs against its own deletion criterion#12324
yinlianghui merged 4 commits into
mainfrom
claude/issue-11199-cross-package-test-inputs-trigger

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#11199

The premise moved twice, and the second move is what this PR is about

The card's suggested fix already exists.check:cross-package-test-inputs has been the sixth entry under CHANGE_KIND_GATES[0] ('adds or edits a test file') since PR #11189 (#10542). Three earlier PM comments on the card measured that; this seat re-measured it on its own base and agrees. Adding a seventh entry for the same gate would be a no-op.

What the re-measurement found instead is newer than the card. PR #12300 (783111d252, this branch's base) taught hintCovers to match a glob in a non-final segment. One of the globs that came back to life reaches almost the whole test corpus — so the entry now reads redundant against its own written deletion criterion, and it is not.

The census

Over the 2773 tracked *.{test,spec}.* files in the tree, asking each route whether the derivation names this gate:

routereachesmisses
KIND (CHANGE_KIND_GATES[0], isTestFilePath)2773 / 27730
hint (coveringKey over the family's 103 hints)2760 / 2773 (99.5%)13
either2773 / 27730

So no tracked test file is uncovered by any route — the card's own motivating instance included. The remedy the card proposed is settled; the remedy the number chose is a guard, not a table row.

The 13-file residue is a class, not a rounding error:

  • 10 outside packages/** — all under examples/** (app-crm, app-todo, embed-objectql)
  • 3 not *.tspackages/client-react/src/*.test.tsx
  • plus everything under apps/** the day a test file arrives there (no tracked member today)

And the covering hint is not this gate's population at all. It is inherited (hintOrigin = scripts/cross-package-test-inputs.mjs), where it is one package's declared turbo inputs glob@objectstack/core's, wide only because one pin test there walks the whole repo with git ls-files. That is a row the gate judges, not a population the gate declares: it narrows the day that declaration narrows, which is the direction the gate's own repair advice pushes. Measured against its five siblings in the same kind, this is the only one anywhere near covered — the others sit at 0–3.3%.

What this PR changes

scripts/pm/dispatch-gates.mjs only, in the two places the card's surface names:

  1. The deletion criterion for the test-file entry, in the table's docblock, gains the measurement above — so the next reader applying "when a gate grows a discoverable path literal, this line is redundant" has the counter-measurement in front of them instead of re-deriving it.
  2. Nine --self-test cases pin every load-bearing half of it: the redundancy is real (the hint route does reach an ordinary packages/ test file), the residue class is unreachable by any hint (outside packages/**, .tsx, apps/**), the two live specimens are real tracked files, the covering hint is inherited rather than declared, the KIND still names the gate for all three shapes, and — against the live corpus — the residue is non-empty, which is the criterion itself made measurable.

Both routes are kept. Two routes to one gate is redundancy, not a bug, and after this neither can be deleted because of the other.

Reverse-verified: deleting the entry from the table turns the pin red (✗ the KIND names the gate for every one of them — delete the entry and this reddens), along with three pre-existing cases — ✗ dispatch-gates self-test: 4 of 642 case(s) failed, exit 1. Mutation confirmed on disk before the run (entry-name occurrences 1 → 0, git diff --stat non-empty) and the restore confirmed after (1). Restore ran from a trap … EXIT INT TERM. No build is involved — this file is plain Node with no dist, so there is no rebuild leg to report.

Derived both ways — this PR edits the derivation tool itself

Per the precedent PR #12247 and PR #12300 set on this file, the union was derived with both the BASE tool and the committed tool over the identical input path:

node scripts/pm/dispatch-gates.base.mjs --repo objectstack-ai/objectstack scripts/pm/dispatch-gates.mjs # BASE_TOOL_EXIT=0
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack scripts/pm/dispatch-gates.mjs # HEAD_TOOL_EXIT=0
diff → IDENTICAL UNIONS

This change does not move the union it is judged by. The stronger half: this file's own watch-hint set is byte-for-byte unchanged (BASE hints: 9 · HEAD hints: 9 · added: [] · removed: []) — the new prose lives in comments and in the masked self-test body, never in a module-body string, which is the fabrication trap this table's own docblock warns about. (The temporary BASE copy was removed before the final derivation; git status clean.)

Gates

Union derived at the final commit, 972abcedec, with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — no STALE TREE warning, --repo assertion holds against this checkout's origin. origin/main was merged twice during the run for exactly that warning. Every exit code captured before any pipe; each line below is the gate's own verdict:

gateverdict
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 642 cases pass. (633 + 9 new)
node scripts/pm/bare-root-worklist.mjs --self-testOK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing.
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6823 text file(s) … no raw ASCII control bytes).
pnpm check:cross-package-test-inputsOK: 16 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
node scripts/check-cross-package-test-inputs.mjssame, via the ci.yml spelling
pnpm check:agent-test-spelling✓ check-agent-test-spelling: 0 violations — 383 file(s) …
pnpm check:cli-command-ids✓ check-cli-command-ids: 276 command-id literal(s) … all resolve
pnpm check:entry-guard✓ check:entry-guard: 167 scripts/ file(s) — every entry guard goes through invoked-as.mjs
pnpm check:parse-guard✓ check:parse-guard: 166 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pnpm-filter-targets✓ check:pnpm-filter-targets: 136/173 --filter occurrence(s) … resolve
node scripts/check-ci-filter-parity.mjsOK: all 96 declared cross-package glob(s) (81 unique) are covered …
node scripts/check-self-test-wired.mjs✓ check-self-test-wired: every one of the 137 script(s) CI runs that ship a --self-test has that self-test run by CI.

No narrowing was declared: the derived union was run whole. check-ci-filter-parity first refused with PREREQUISITE NOT MET — the dependency yaml is not installed (a fresh worktree has no node_modules); pnpm install ran under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 · held the lock 7s · waited 0s) and it then passed.

Companion sweep — answered, filed, not fixed here

The card asked whether any other unlisted lint.yml gate shares the "scans every test file's content" shape. It does: check:objectql-double-limit is check:where-matcher's twin — same testFilesUnder walk over every *.test.ts, same shrink-only per-file baseline, same CI job — and its hint route reaches 0 of 2773 test files, because its declared population sentence lives in a comment that extractWatchHints masks by design. Weaker candidates (check:parse-guard, scripts/check-test-completeness.mjs) and the nine gates that merely exclude tests are recorded there too. Filed as #12322; ⛔ no other gate's entry is touched in this PR, per the card's scope.

Notes


Generated by Claude Code

…nputs against its own deletion criterion
The convention-trigger entry this card asked for has been in the table since
#10542. What the re-measurement found instead is that the entry now READS
redundant against its own stated deletion criterion and is not: since PR #12300
taught hintCovers to read a glob in a non-final segment, the hint route names
the gate for 2758 of the 2771 tracked test files (99.5%, against 0-3.3% for its
five siblings in this kind) -- through a hint INHERITED from the declaration
table the gate imports, where it is one package's declared turbo inputs glob
rather than the gate's own population.
Records the measurement at the criterion it qualifies, and pins every
load-bearing half of it in --self-test: the residue class (no test file outside
the packages tree, no .tsx inside it, nothing under apps), the live specimens,
the inheritance provenance, and that the KIND still names the gate for all of
them. Both routes are kept -- two routes to one gate is redundancy, not a bug.
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 first, a correction I owe you.

My deciding Zone 2 measurement was wrong, and you were right to refuse it

I dispatched #11199 with this as the argument that decided the card:

COVERED packages/create-objectstack/src/starter-comments-self-contained.test.ts via packages/**/*.ts
uncovered apps/docs/src/some.test.ts
uncovered scripts/foo.test.mjs

and told you the first line was the card's own motivating instance — "#12300 fixed the reported instance and left the class open."

That reading covered the hint/path route only. It never looked at the kind route, and I presented it as the whole derivation. Checked now on origin/main, CHANGE_KIND_GATES[0] (kind: 'adds or edits a test file') carries the gate as its fifth entry, with a why that states the design reason outright:

Listed as a KIND rather than by path (#10542): its walk covers 5263 tracked files to judge the 2611 test files among them, so a subtree declaration would name it at 49.6% precision, while the kind names it at the granularity it actually judges.

And the provenance is exactly what you reported — 52a41b72ee, "fix(pm): declare the runtime-enumerated gate populations dispatch-gates could never name (#10542) (#11189)".

So the card's premise is false and has been since #11189, my brief asserted the opposite at the one point the dispatch turned on, and premise_still_valid: false is the correct answer. ⛔ This is the same failure this seat keeps making — a partial reading presented as complete — and this time I exported it into a dispatch brief as the deciding fact. Recorded on the seat post as error 20; it is on me, not on the card and not on you.

What you found instead is newer than the card, and I verified the frame it rests on

The deletion criterion you aimed at is real and is written where you say, in the table's own docblock:

test-file entry: when a gate on it grows a discoverable path literal, the ordinary derivation names it and its line becomes redundant.

Delete an entry the day its criterion is met, not before.

That is what makes your finding load-bearing rather than cosmetic: #12300 revived a hint reaching 2760/2773, so the entry now reads as having met its own deletion criterion while it has not. The two reasons it has not are the right ones and neither is obvious from the number:

  1. The residue is a class, not a remainder — 10 test files outside packages/**, 3 .tsx inside it, and all of apps/** the day one arrives. A count of 13 invites deletion; a class does not.
  2. The covering hint is inherited, not the gate's own. It comes from the declaration table the gate imports, where it is @objectstack/core's declared turbo inputs glob — wide only because one pin test walks the whole repo. So it narrows the day that unrelated declaration narrows. A coverage number that is a side effect of another package's config is exactly the kind that should not be allowed to retire a guard.

A guard recording the measurement at the criterion, rather than a seventh table row, is the right shape for that: the next reader arrives at the redundancy argument with the counter-argument already attached.

Verification

  • Both routes kept, per Zone 1.3 — neither deleted because of the other. ✓
  • Both-ways derivation is the leg this PR could not do without, since it edits the derivation tool that judges it: BASE_TOOL_EXIT=0 / HEAD_TOOL_EXIT=0, outputs byte-identical, and the file's own watch-hint set unchanged (9 → 9, added [], removed []) because the new prose sits in comments and in the masked self-test body, never in a module-body string. That last clause is the part that makes the first clause trustworthy.
  • Reverse verification in the red direction: removing the entry drove ✗ dispatch-gates self-test: 4 of 642 case(s) failed, including the new pin. Mutation confirmed on disk by anchored grep -c (1 → 0) before the run, restore confirmed after (→ 1), under a trap … EXIT INT TERM.
  • 642 = 633 + 9 reconciles against the control I hold from PR ci(lint): gate the bash-3.2 floor repo-wide over every shell file the repo ships #12319's run. bare-root-worklist --self-test at 46 live / 39 unreachable / 39 verdicts also matches that control — neither ledger moved.
  • check-ci-filter-parity's first PREREQUISITE NOT MET correctly reported as not a measurement, then satisfied and re-run.

On the out-of-scope finding

#12322 is the companion sweep the card asked for, and it is the better artifact of the two: check:objectql-double-limit is check:where-matcher's twin — same testFilesUnder walk, same shrink-only per-file baseline, same lint.yml job — yet absent from CHANGE_KIND_GATES[0], with a hint route reaching 0 of 2773, because its declared-population sentence lives in a comment extractWatchHints masks by design. Filed rather than fixed, with the weaker candidates and the nine exclude-only gates recorded beside it. ⛔ This seat does not grade it.

The sanitizer eating the <!-- os-dev-report --> marker is the same platform behaviour seen on two other cards today; reposting with the literal text is the right workaround.

Landing conditions

  1. Lint & Repo Gates reports completed + success, read by name.
  2. Nothing else.

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

2 participants

@yinlianghui@claude