Uh oh!
There was an error while loading. Please reload this page.
ci(devx): assert every cross-package declared glob can schedule the test job - #10801
Merged
Conversation
…est job CROSS_PACKAGE_TEST_INPUTS has three layers acting on it and only two were checked. `check:cross-package-test-inputs` finds the escaping tests itself (Layer A's roster) and `--verify` makes turbo.json hash every declared glob so the task hash moves (Layer B). Nothing verified the third: the scheduler has to start the ci.yml `test` job that Layer A's `--union-into` step lives inside, and it decides that from `crosspkg:` — a hand-kept second recognizer of the same declarations. A declaration landing in a root no entry covers left the gate green, the turbo hash moving, and the test still not running at PR time. check-ci-filter-parity.mjs closes it with a pure-string coverage rule: a declared glob is covered iff a scheduling list contains it verbatim or contains `<prefix>/**` for a directory prefix of its leading literal segments. It reads the declaration table rather than copying it, reads the `test` job's `if:` so a filter leaving the OR is a refusal rather than a silent pass, and reports a `crosspkg` entry that covers no declaration any more. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
…osspkg-filter-parity
os-zhuang
marked this pull request as ready for review
August 21, 2026 13:18
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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#10379
The residual hole
CROSS_PACKAGE_TEST_INPUTShas three layers acting on it, andpnpm check:cross-package-test-inputsverified two:--union-intoadds the declaring package to the shard's package setturbo.jsoncarries a matching$TURBO_ROOT$/…input, so the task hash moves--verifyci.yml's
crosspkg:filter is a second, hand-kept recognizer of the same declarations. Nothing held the two in step, so a declaration landing in a root no entry covers left the gate green, the turbo hash moving, and the test still not running at PR time — #7802's shape one layer up.The hole, reproduced before it was closed
A
docker/**glob was added to@objectstack/metadata-protocol's declaration (with aheldBywitness and the matching$TURBO_ROOT$/docker/**input, so both existing layers were satisfied):Green gate, moving hash, unschedulable test. With this PR's gate on the same tree:
The demonstration edits were reverted;
git hash-objectconfirms both files byte-identical toHEAD.Route 2, and why not route 1
Route 1 (deriving the filter entries from the declarations at CI time) puts a new failure mode inside the
filterjob — the one job whose death THE FILTER CONTRACT (#4928) was written about — and makes the entries unreadable where a human looks for them. This is route 2: a static parity gate at lint time.The coverage rule is pure string:
Deliberately not a file-instantiating matcher.
core:carries the extglobapps/!(docs)/**, which the sibling gate's dependency-freeglobToRegExpdoes not support — matching would need a picomatch-compatible engine, i.e. a third recognizer of the same declarations, which is the defect this card is about. A pure-string rule needs no matcher at all.Its error direction is stated in the header and pinned in the self-test: sound (never reports covered for a path the scheduler would miss — both limbs imply containment) and knowingly incomplete (an entry carrying a wildcard of its own covers nothing, so a future declaration under
apps/reads as uncovered until an entry names it — one line of YAML, versus a silent scheduling gap in the other direction).The classification, measured on this branch's base
crosspkgrolled back to its pre-#10015 list (scripts/**alone)Those ten are exactly the ten #10015 fixed — including
.github/workflows/scaffold-e2e.yml, the same-root-different-file case a naive root-level rule false-greens (corenames.github/workflows/ci.yml, a different file under the same root). The card measured 55 unique globs / 45 green onf3996182b; the tree has since grown to 71 / 61. The ten are unchanged and the gate's self-test pins that rollback.Cross-checked once against the real matcher rather than argued: instantiating all 71 unique globs to the tracked files they match and running each through
core+crosspkgwith picomatch 4.0.5, the pure-string rule and picomatch agree on 71 of 71 rows, with no glob matching zero tracked files. That measurement is evidence, deliberately not machinery — reproducing it inside the gate is the third recognizer the rule refuses.What else the gate holds
testjob'sif:and refuses ifcoreorcrosspkghas left the OR. Without that limb, deletingcrosspkgfrom the condition would reopen the whole hole while the gate reported parity against a list that schedules nothing.crosspkgentry that covers no declaration any more — the staleness direction, so the hand-kept list cannot accumulate entries that make it look maintained.filterjob, nodorny/paths-filterstep, afilters:input that is not a string or not lists of strings, a missing scheduling filter, a filter gone from theif:, an empty declaration table).CROSS_PACKAGE_TEST_INPUTSfrom the sibling gate (newly exported) rather than holding a copy — a copy here would be the same defect one file further out. Importing that module runs nothing; its dispatch is behindisEntrypointand its own self-test spawns a child to pin that.Verification
All at
27f2dd8954(the final commit, after mergingorigin/main), each exit code captured before any pipe:node scripts/check-ci-filter-parity.mjs --self-test✓ check-ci-filter-parity --self-test: 36 assertions — …node scripts/check-ci-filter-parity.mjsOK: all 82 declared cross-package glob(s) (71 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.(backticks the gate prints around the four names elided for the table)pnpm check:cross-package-test-inputsAll 90 self-test cases passed./OK: 13 package(s) read outside themselves, all declared…pnpm check:entry-guard✓ check:entry-guard: 128 scripts/ file(s) — every entry guard goes through invoked-as.mjs…node scripts/check-parse-guard.mjsnode scripts/check-nul-bytes.mjscheck-nul-bytes: OK (scanned 6250 text file(s)…; no raw ASCII control bytes)node scripts/pm/dispatch-gates.mjs --self-test✓ dispatch-gates self-test: 463 cases pass.pnpm check:required-contextspnpm check:workflow-status-functionscheck-workflow-status-functions: OK (scanned 26 workflow file(s)…)node scripts/check-aggregator-roster.mjspnpm check:shard-attestation✓ check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s)…pnpm check:node-versionnode scripts/check-ratchet-remedy-authority.mjsOK check-ratchet-remedy-authority: 114 scripts swept…pnpm check:type-check-coveragecheck-type-check-coverage: OK — 64/77 workspace packages type-checked…node scripts/pm/dispatch-gates.mjs(no paths — derived from the merge base) named the same twelve families before and after the merge; every one was run exceptcheck:type-check-debt.One declared narrowing, and it is narrower than the family name suggests.
pnpm check:type-check-debtis--self-test && --re-measure;pnpm check:type-check-coverageis--self-test && <scan>. Both legs are the same script, and the limbs that read.github/workflows/lint.yml— the file this PR actually changes — live in the scan, asserting that the workflow still invokestypecheck:rootandturbo run typecheck. That leg ran and passed (exit 0, table above). What did not run locally is--re-measure, which reads no workflow at all: it refuses here with--re-measure cannot run: 55 workspace dependenc(ies) … have no built type entry point on disk, the gate correctly declining to measure a different world rather than a failure. Building the whole workspace closure to satisfy it buys nothing — this diff contains no TypeScript (one new.mjs, one.mjsedit, one workflow), so no ledger number it measures can move. lint.yml builds the closure before that step, so CI runs it for real.Ablation
The coverage rule's final fallthrough was mutated so it can never report uncovered:
Confirmed on disk by anchor count, not by an editor's exit code: the removed text went
1 -> 0occurrences and the injected marker0 -> 1. Predicted: the ten named assertions go red. Observed: 10 of 36, and they are exactly the ten enumerated — thedocker/**case, the extglob bound, both same-root-different-file cases, the covered-by-neither pair, the mixed table, the stale limb,main()'s report path, and the pre-#10015 rollback (got 0, from 10). (The count first written alongside that enumeration said "9"; that was an arithmetic slip in the enumeration, not a different prediction.)The second observation is the one worth keeping: under the same ablation, the gate over the real tree still printed
OKand exited 0, because today's tree has no uncovered glob. The self-test is the only thing holding this rule — which is why it is wired into lint.yml as both legs.Restored and re-measured: marker
1 -> 0,git hash-objectbyte-identical toHEAD, self-test back to36 assertions/ exit 0.Notes
skip-changesetapplied additively.ci.ymlis not touched. Under route 2 there was no need to, and THE FILTER CONTRACT (filterjob 一旦失败,Test Core / Build Core / Dogfood 会全部 skipped 而分支保护判为通过 —— 隐式 success() 今天已第三次咬人 #4928) governs that job.node scripts/…invocation inlint.yml, not apnpm check:*alias — rootpackage.jsonis inside the Migrate the release toolchain to @changesets/cli v3 — one atomic PR carrying the bump, the pre-mode restructure, and the gates that model v2's semantics #9465 fence, and that precedent already exists in the file for several gates. The gate's own self-test asserts that wiring against the workflow text, so a gate that exists and is not scheduled fails rather than sitting dormant.Generated by Claude Code
Generated by Claude Code