ci(lint): a turbo.json package task must name a real package and a real script - #12374
Draft
yinlianghui wants to merge 2 commits into
Draft
ci(lint): a turbo.json package task must name a real package and a real script#12374yinlianghui wants to merge 2 commits into
yinlianghui wants to merge 2 commits into
Conversation
…al script
`turbo.json` is the task graph for the whole monorepo and no gate read it
structurally except one narrow limb — `check:cross-package-test-inputs`' Layer
B, which inspects only the `<pkg>#test` keys of the packages declared in
`scripts/cross-package-test-inputs.mjs`. Every other key was judged by nothing.
Turbo does not cover the difference. Measured on turbo 2.10.10 against a
fixture workspace, each case a real `turbo run <task> --dry=json`:
`@fx/nope#build` (no such package) exit 0, no diagnostic, key never reaches
the graph
`@fx/a#typecheck` (package has no
such script) exit 0, task shown in the dry-run graph,
never runs
`dependsOn: ["prebuildxyz"]` exit 1, `x Could not find
"@fx/a#prebuildxyz"`
So the two silent shapes are the population and `dependsOn` is deliberately
left to turbo, which polices it better.
The gate also closes the derivation half of the same finding. It declares
`turbo.json/**` — the subtree spelling `AGENTS.md/**` already uses for a
repo-root file — so a card whose surface is `turbo.json` now derives a family
where it derived zero before.
Part of #12046
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
…te leaves out The header stated the deliberate omission and its live violation (`test:e2e`, held by zero of 78 packages) but had nothing to point at. Part of #12046 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
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.
Part of #12046
Adds
check:turbo-task-graph— the first gate in the repo whose declared population isturbo.json.The measurement the card asked for
The card's central reading holds.
check-cross-package-test-inputs.mjs' Layer B (lines 1345–1362) iteratesCROSS_PACKAGE_TEST_INPUTSand looks upturbo.tasks[`${name}#test`]— nothing else. A#typechecktask is structurally invisible to it.Option 1 (widen Layer B) was measured and rejected as dishonest. That table's own header states what it declares: "which packages' tests read outside their own directory", and the escaping detector that keeps it honest walks
*.test.*files only. There is no comparable population for#typecheckor#build, so widening Layer B would mean demanding that apkg#typechecktask carry test-derived globs — a population the gate does not police. That is the #11556 shape from the other side, which the card names as the thing not to do.A finding the card did not have: option 1 would not have closed the derivation half either.
extractWatchHintsrefuses a literal with no path separator, andcheck-cross-package-test-inputs.mjsreaches this file asjoin(REPO_ROOT, 'turbo.json'). Measured:So whichever gate ends up owning
turbo.jsonhas to declare the subtree spelling — theROOT_FILE_WATCH_HINTSidiom eight gates already use forAGENTS.md/**. This one does.Before, at
1f6b8bb193:After, on this branch:
What the gate judges, and why that population and not a wider one
Turbo itself decides the split. Measured on turbo 2.10.10, on a fixture workspace, each case a real
turbo run [task] --dry=json:@fx/nope#build— package does not exist@fx/a#typecheck— package has no such scriptdependsOn: ["prebuildxyz"]— unresolvablex Could not find "@fx/a#prebuildxyz" in root turbo.json or "prebuildxyz"The gate takes the two silent rows and deliberately leaves
dependsOnto turbo, which polices it better — re-checking it would be this gate claiming a population it does not own, which is the failure one level up.What a silent entry costs is intent, not a red build: misspell the package or the script and the override is not applied, the task falls back to the generic definition, and every signal the author has says the edit landed.
Package membership comes from
scripts/workspace-enumerator.mjs— the repo's one parse ofpnpm-workspace.yaml. Importing it grants this gate no path population (that module declares none, deliberately), soturbo.json/**stays the whole of what this file names.What it deliberately does NOT judge — stated, not discovered later
Generic keys (no
#) are out of population, and the header says why with the number:test:e2eis defined inturbo.jsonwithoutputs: ["playwright-report/**", …]and held by zero of the 78 workspace packages. The real Playwright script inexamples/app-showcaseis spelledtest:smoke, whichturbo.jsondoes not configure at all. Repairing that needs an edit toturbo.json, outside this card's declared file surface, and a gate that ships red is worse than no gate. Filed as #12373; widen the population in the same change that repairs the entry.Ablation — both directions, restored under
trap … EXIT INT TERMNo build is involved: this gate is a plain Node script that reads source at runtime, so there is no
dist/for a mutation to hide in andablation-dist-preflightdoes not apply. Every mutation was confirmed on disk by grepping both the injected and the removed text before the reading was taken.Subject mutations (
turbo.json), gate unmodified:@objectstack/plugin-auth#typecheck→@objectstack/plugins-auth#typecheck@objectstack/plugin-auth@objectstack/spec#test→@objectstack/spec#typechekRule mutations on a clean tree — the #11150 property, that a matching rule's regression is invisible to production because the empty finding set is the fixed point of shrinking:
--self-testan unknown package in a task key is a finding,the unknown-package finding suggests the near missa task the package has no script for is a finding,the missing-script finding prints what the package DOES declareROOT_FILE_WATCH_HINTSreworded to'turbo.json'the root file is declared in the SUBTREE spelling; anddispatch-gates turbo.jsoncollapses back to "No check family names the given paths"The first rule ablation initially killed the self-test with a
TypeErrorinstead of reporting named cases — the #12273 shape, where a mutation aborts the suite exactly when the instrument is being read. The two arms are now independent (scripts && !scripts.has(task)) so each ablation yields named failures; the table above is the re-run after that change.Restore proof, both files,
git hash-objectbefore vs after: byte-identical (f79ad7085715…/a47b23984253…), andgit diff --stat -- turbo.jsonempty.Verification
Gate union re-derived on the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no hand-written path list), at86b3bcc195— all 26 derived families run, exit codes captured before any pipe:check:turbo-task-graph—OK: 21 package-scoped turbo task(s) judged against 78 workspace package(s), self-test all cases passedcheck:cross-package-test-inputs,check-ci-filter-parity,check:entry-guard,check:parse-guard,check:agent-test-spelling,check:bash32-floor,check:cli-command-ids,check:pnpm-filter-targets,check:pnpm-acquisition,check:node-version,check:nul-bytes,check:required-contexts,check:aggregator-roster,check:step-collectors,check:self-test-wired,check:self-test-workflow-commands,check:whole-set-label-write,check:workflow-status-functions,check:shard-attestation,check:pm-dispatch-gates(664 cases),scripts/pm/ci-failure.mjs --self-test,scripts/docs-audit/check-drift-comment.mjs— all exit 0check:type-check-coverageandcheck:type-check-debt --re-measureon a built closure, under the shared verify lock:32 ledger entr(ies) re-measured in 287.0s, 1843 raw tsc error(s) total, none above its recorded numberpnpm lint(eslint . --no-inline-config, the full repo, not a narrowing) — exit 0, under the lockbare-root-worklistowes nothing here.--self-test:46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing, unchanged with this gate present. The species is a population constant holding a bare single-segment top-level directory word;turbo.json/**carries a separator (sobareRootLiteralsskips it as already visible),turbo.jsonis a file rather than a directory, and this gate names no bare root anywhere. Soscripts/pm/bare-root-worklist.mjsis untouched.No changeset: root⚠️ The label itself could not be applied from this seat:
package.jsonisprivate: trueand nothing published changes —skip-changeset, matching #12346 / #12347 / #12338, which shipped the same way.api.github.comrefuses direct REST here (GitHub access is not enabled for this session), and the MCP write path resolves a PR number as an Issue and fails. Needs a hand.Generated by Claude Code