Skip to content

Commit b42e034

Browse files
hotlongclaude
andauthored
build(turbo): build task hashes cover the root scripts each build runs (#21193) (#21199)
Fixes #21193 Clause-②: no. Turbo hash declarations change no contract's acceptance or rejection behaviour. This is finding F1 of the stage-1 report on #21186, the precondition for that card's stage 2. #21186 stays open; its draft PR #21191 (the `remoteCache` block) is untouched here. ## What changes `turbo.json` only, `build` task family only. Each build now declares the root files its build command loads, as task-level `$TURBO_ROOT$/…` inputs beside `$TURBO_DEFAULT$`: | task | added inputs | why it reads them | |:--|:--|:--| | `build` (generic) | `scripts/tsup-drop-sources-content.mjs`, `scripts/check-dts-emitted.mjs`, `scripts/invoked-as.mjs` | the root `tsup.config.ts` (loaded with `--config` by 47 builds) and 19 package tsup configs import the first, 66 builds in all (it shapes every `.map`); 67 build commands end with `node …/check-dts-emitted.mjs`, which imports the third | | `@objectstack/spec#build` | the generic three, plus `check-dts-references.mjs`, `ts-parse.mjs`, `check-regen-pending.mjs`, `regen-artifacts.mjs`, `git-env.mjs`, `import-prerequisite.mjs`, `cli-build-prerequisite.mjs`, `check-dev-prereqs.mjs`, `build-input-hash.mjs`, `workspace-enumerator.mjs`, `js-comment-mask.mjs` | `gen:schema` (`build-schemas.ts`) imports `check-regen-pending.mjs` to write the `json-schema/` stamp; the build runs `check-dts-references.mjs` and `check-dev-prereqs.mjs --stamp`; the rest is their import closure | | `@objectstack/core#build`, `@objectstack/organizations#build`, `@objectstack/plugin-auth#build` | the generic three, plus `check-dev-prereqs.mjs`, `build-input-hash.mjs`, `workspace-enumerator.mjs`, `js-comment-mask.mjs` | each build ends with `check-dev-prereqs.mjs --stamp`; `build-input-hash.mjs` computes the stamp bytes written into `dist/` | | `create-objectstack#build` | `check-dts-emitted.mjs`, `invoked-as.mjs`, `sync-scaffold-emission-policy.mjs`, `sync-template-versions.mjs`, `packages/cli/src/commands/init.ts` | its first step reads the `SCAFFOLD_*` policy out of `init.ts` and stamps it into the template that tsup copies to `dist/templates`; it does not run the root tsup config, so `tsup-drop-sources-content.mjs` is not declared there | All paths above are under `scripts/` unless spelled in full. The rule for "is it an input": a file Node loads while the build command runs. That is the static import closure of every script the command executes and every tsup config it loads, measured by walking the relative imports, not by reading names. Each of these files can change a build's output bytes (`tsup-drop-sources-content.mjs`, `build-input-hash.mjs`, `check-regen-pending.mjs`, `sync-scaffold-emission-policy.mjs`, `init.ts`) or its pass/fail verdict (all of them, at minimum by failing to load). Not `globalDependencies`: `globalCacheInputs` is byte-identical before and after. A root `pkg#task` key replaces the generic definition instead of merging with it (measured on turbo 2.11.5: an override carrying only `inputs` resolved to `dependsOn: []` and `outputs: []`), so each override restates the generic `dependsOn` and `outputs` verbatim. Measured: across all 320 tasks of `build test test:repo typecheck` (231 with a command), every resolved task definition is identical before and after except `inputs`. ## Probe (the #21186 stage-1 method, re-run) Method: append one comment line to the file, run `turbo run build test test:repo typecheck --dry=json`, compare every task hash with the unedited plan, restore with `git checkout HEAD -- FILE`, and prove the restore by blob hash. Before = base `ebdb6f2aca`; after = `120faa2030`. Turbo 2.11.5. Build Core has 72 build tasks; `+docs` means `@objectstack/docs#build` moved too. Expected = the direct readers' build tasks plus every build downstream of them in the task graph. "Exact" means the moved set equals the expected set task for task, not only in count. | file | direct readers | build moved, before | build moved, after | expected | test / test:repo / typecheck moved, before → after | |:--|--:|--:|--:|:--|:--| | `scripts/tsup-drop-sources-content.mjs` | 66 | 0/72 | 72/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/sync-scaffold-emission-policy.mjs` | 1 | 0/72 | 6/72 | exact | 2/1/0 → 7/1/7 | | `packages/cli/src/commands/init.ts` | 2 | 5/72 (no `create-objectstack#build`) | 6/72 | exact | 8/5/7 → 8/5/7 | | `scripts/check-dev-prereqs.mjs` | 4 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/check-regen-pending.mjs` | 1 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/check-dts-emitted.mjs` | 67 | 0/72 | 72/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/check-dts-references.mjs` | 1 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/invoked-as.mjs` | 67 | 0/72 | 72/72 +docs | exact | 2/1/0 → 70/6/75 | | `scripts/build-input-hash.mjs` | 4 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/workspace-enumerator.mjs` | 4 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/js-comment-mask.mjs` | 4 | 0/72 | 71/72 +docs | exact | 13/2/0 → 71/6/75 | | `scripts/regen-artifacts.mjs` | 1 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/git-env.mjs` | 1 | 0/72 | 71/72 +docs | exact | 2/1/0 → 70/6/75 | | `scripts/import-prerequisite.mjs` | 1 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/cli-build-prerequisite.mjs` | 1 | 0/72 | 71/72 +docs | exact | 2/1/0 → 70/6/75 | | `scripts/ts-parse.mjs` | 1 | 0/72 | 71/72 +docs | exact | 1/1/0 → 70/6/75 | | `scripts/sync-template-versions.mjs` | 1 | 0/72 | 6/72 | exact | 2/1/0 → 8/1/7 | | control: root `tsup.config.ts` | global | 72/72 +docs | 72/72 +docs | unchanged | 73/6/79 → 73/6/79 | | control: `packages/create-objectstack/tsup.config.ts` | 1 | 6/72 | 6/72 | exact | 7/5/8 → 7/5/8 | | negative control: `scripts/check-turbo-task-graph.mjs` | 0 | 0/72 | 0/72 | nothing new | 1/1/0 → 1/1/0 | The 71/72 rows: `@objectstack/sdui-parser#build` is the only build that is not downstream of `@objectstack/spec#build`, and it does not load those files. **End to end, on the real cache.** `turbo run build --filter=create-objectstack --only`, then `SCAFFOLD_TYPESCRIPT_RANGE` in `init.ts` edited from `^5.3.0` to `^5.9.0` and run again. Before (`ebdb6f2aca`): `cache hit, suppressing logs d14ac0ba80366056`, and `dist/templates/blank/package.json` still says `^5.3.0`. After (`120faa2030`): `cache miss, executing 78b5115918e44c53`, and the dist template says `^5.9.0`. Restored, the next run is a hit on the original hash `b336dcf35aaf1381` with `^5.3.0` again. ## Before/after plan diff (this commit itself) `turbo run build --dry=json` (same hashes as the four-task run, 80/80 identical) from `ebdb6f2aca` to `120faa2030`: - Input keys: the 67 generic build tasks with a command gain exactly the three generic files; spec gains its 14; core, organizations and plugin-auth gain their 7; create-objectstack gains its 5. Zero input keys are removed or re-hashed on any task, so `$TURBO_DEFAULT$` keeps the default file set. Zero test, test:repo or typecheck tasks gain or lose an input key. `@objectstack/docs#build` inputs are unchanged. - Hashes: 73/73 build tasks with a command move once (the definition changed; docs through its `^build` edge). This is the one-time "every build hash moves once" the stage-1 option A priced. - Test and typecheck: 70/73 test, 5/6 test:repo and 75/79 typecheck hashes move once. That is turbo folding each dependency task's hash into the dependent's hash along `dependsOn: ["^build"]`, not a declaration on those tasks. The 8 that do not move are exactly the tasks with zero task dependencies (`spec`, `sdui-parser` and `vitest-filter-preflight` test; `spec#test:repo`; `refd-timer-testkit`, `sdui-parser`, `spec` and `vitest-filter-preflight` typecheck). The same mechanism explains the probe's test column: after this change, an edit to a spec-closure script moves 70/6/75, against 73/6/79 for the root `tsup.config.ts` global control. The difference is the zero-dependency tasks, which a `globalDependencies` entry would also have moved. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands` at `120faa2030`. All 9 ran and exited 0: `check-closing-keyword-parity` (plus its `--self-test`), `check-comment-mask-corpus`, `check:driver-memory-census`, `check:gitlink-declared`, `check:nul-bytes`, `check:refd-timer-probe`, `check:turbo-task-graph` (43 package-scoped and 9 generic tasks judged), `check:watch-hint-literal`. `dispatch-gates --ran`: 9 derived, 9 run, 0 NOT-MEASURED, 0 unrun. No guard asserts build inputs. I also ran the other gates that parse `turbo.json` or read the declared files, all exit 0: `check:cross-package-test-inputs`, `check-ci-filter-parity` (`--self-test` and the run), `check:examples-live-imports`, `check-dev-prereqs.mjs --self-test` (it reads `globalDependencies`, which is unchanged) and `check:scaffold-emission-policy`. Labelled `skip-changeset`: `turbo.json` is repo-root configuration and ships in no package. ## Acceptance notes - **The declared set is wider than the card's table, on the card's own rule.** Beyond the five named rows, it adds `check-dts-references.mjs` (the spec build runs it; it was in the stage-1 option A list but not in the card's table) and the import closure of every script: `invoked-as`, `build-input-hash`, `workspace-enumerator`, `js-comment-mask`, `regen-artifacts`, `git-env`, `import-prerequisite`, `cli-build-prerequisite`, `ts-parse` and `sync-template-versions`. Before this change each one moved 0/72 builds, the same as the named rows. `build-input-hash.mjs` decides the stamp bytes written into `dist/` and `json-schema/`. - **The generic key over-reaches on five tasks at the declaration level only.** `@objectstack/cli#build` (tsc) and the four `example-*#build` tasks (`objectstack build`) load none of the three generic files. Each is downstream of `@objectstack/spec#build`, which does load them, so their moved set is the same either way (the probe rows are exact). Splitting them out would take five more overrides for no hash difference. - **`@objectstack/docs#build` is left alone.** Its build runs the spec `gen:schema` and `gen:docs`, whose out-of-package imports were measured to be a subset of the spec closure above. It reaches them through its `^build` edge to `@objectstack/spec#build`, the same edge that already carries `packages/spec/scripts/**` to it. Every spec-closure row shows `+docs`. - **Not declarable as a turbo input:** the spec build's authorable-surface check anchors on `merge-base(HEAD, origin/main)`, which is git state, and it is verdict-only (from the stage-1 audit). - **Nothing keeps these lists honest.** The test side has `check:cross-package-test-inputs`, but no gate compares a build's declared inputs with its import closure. A new relative import in any of these scripts reopens this gap silently. Carrier: none. - **Scheduling, not hashing, at PR time.** `ci.yml`'s `core` filter matches none of `scripts/**`, the root `tsup.config.ts` or `turbo.json` (picomatch 4.0.5 against the checked-in list). So a PR confined to these files, this one included, skips Build Core and its build-time gates (for example sourcemap-no-sources-content) until the merge queue, where the filter defaults to true. This PR leaves `ci.yml` alone by constraint. The Type Check lane still executes the new build definitions through `typecheck`'s `^build` edge. --- _Generated by [Claude Code](https://claude.ai/code/session_57c17198-e47f-48f8-9242-3fc94c651c85)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5e5ce48 commit b42e034

1 file changed

Lines changed: 82 additions & 1 deletion

File tree

‎turbo.json‎

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
"tasks": {
88
"build": {
99
"dependsOn": ["^build"],
10-
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"]
10+
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
11+
"inputs": [
12+
"$TURBO_DEFAULT$",
13+
"$TURBO_ROOT$/scripts/tsup-drop-sources-content.mjs",
14+
"$TURBO_ROOT$/scripts/check-dts-emitted.mjs",
15+
"$TURBO_ROOT$/scripts/invoked-as.mjs"
16+
]
1117
},
1218
"test": {
1319
"dependsOn": ["^build"],
@@ -30,6 +36,81 @@
3036
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
3137
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/content/**"]
3238
},
39+
"@objectstack/spec#build": {
40+
"dependsOn": ["^build"],
41+
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
42+
"inputs": [
43+
"$TURBO_DEFAULT$",
44+
"$TURBO_ROOT$/scripts/tsup-drop-sources-content.mjs",
45+
"$TURBO_ROOT$/scripts/check-dts-emitted.mjs",
46+
"$TURBO_ROOT$/scripts/invoked-as.mjs",
47+
"$TURBO_ROOT$/scripts/check-dts-references.mjs",
48+
"$TURBO_ROOT$/scripts/ts-parse.mjs",
49+
"$TURBO_ROOT$/scripts/check-regen-pending.mjs",
50+
"$TURBO_ROOT$/scripts/regen-artifacts.mjs",
51+
"$TURBO_ROOT$/scripts/git-env.mjs",
52+
"$TURBO_ROOT$/scripts/import-prerequisite.mjs",
53+
"$TURBO_ROOT$/scripts/cli-build-prerequisite.mjs",
54+
"$TURBO_ROOT$/scripts/check-dev-prereqs.mjs",
55+
"$TURBO_ROOT$/scripts/build-input-hash.mjs",
56+
"$TURBO_ROOT$/scripts/workspace-enumerator.mjs",
57+
"$TURBO_ROOT$/scripts/js-comment-mask.mjs"
58+
]
59+
},
60+
"@objectstack/core#build": {
61+
"dependsOn": ["^build"],
62+
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
63+
"inputs": [
64+
"$TURBO_DEFAULT$",
65+
"$TURBO_ROOT$/scripts/tsup-drop-sources-content.mjs",
66+
"$TURBO_ROOT$/scripts/check-dts-emitted.mjs",
67+
"$TURBO_ROOT$/scripts/invoked-as.mjs",
68+
"$TURBO_ROOT$/scripts/check-dev-prereqs.mjs",
69+
"$TURBO_ROOT$/scripts/build-input-hash.mjs",
70+
"$TURBO_ROOT$/scripts/workspace-enumerator.mjs",
71+
"$TURBO_ROOT$/scripts/js-comment-mask.mjs"
72+
]
73+
},
74+
"@objectstack/organizations#build": {
75+
"dependsOn": ["^build"],
76+
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
77+
"inputs": [
78+
"$TURBO_DEFAULT$",
79+
"$TURBO_ROOT$/scripts/tsup-drop-sources-content.mjs",
80+
"$TURBO_ROOT$/scripts/check-dts-emitted.mjs",
81+
"$TURBO_ROOT$/scripts/invoked-as.mjs",
82+
"$TURBO_ROOT$/scripts/check-dev-prereqs.mjs",
83+
"$TURBO_ROOT$/scripts/build-input-hash.mjs",
84+
"$TURBO_ROOT$/scripts/workspace-enumerator.mjs",
85+
"$TURBO_ROOT$/scripts/js-comment-mask.mjs"
86+
]
87+
},
88+
"@objectstack/plugin-auth#build": {
89+
"dependsOn": ["^build"],
90+
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
91+
"inputs": [
92+
"$TURBO_DEFAULT$",
93+
"$TURBO_ROOT$/scripts/tsup-drop-sources-content.mjs",
94+
"$TURBO_ROOT$/scripts/check-dts-emitted.mjs",
95+
"$TURBO_ROOT$/scripts/invoked-as.mjs",
96+
"$TURBO_ROOT$/scripts/check-dev-prereqs.mjs",
97+
"$TURBO_ROOT$/scripts/build-input-hash.mjs",
98+
"$TURBO_ROOT$/scripts/workspace-enumerator.mjs",
99+
"$TURBO_ROOT$/scripts/js-comment-mask.mjs"
100+
]
101+
},
102+
"create-objectstack#build": {
103+
"dependsOn": ["^build"],
104+
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
105+
"inputs": [
106+
"$TURBO_DEFAULT$",
107+
"$TURBO_ROOT$/scripts/check-dts-emitted.mjs",
108+
"$TURBO_ROOT$/scripts/invoked-as.mjs",
109+
"$TURBO_ROOT$/scripts/sync-scaffold-emission-policy.mjs",
110+
"$TURBO_ROOT$/scripts/sync-template-versions.mjs",
111+
"$TURBO_ROOT$/packages/cli/src/commands/init.ts"
112+
]
113+
},
33114
"@objectstack/service-settings#test": {
34115
"dependsOn": ["^build"],
35116
"outputs": [],

0 commit comments

Comments
 (0)