Skip to content

Commit 57c9fe3

Browse files
ci: the core filter starts Build Core for a build-input-only diff, held to turbo.json by check-ci-filter-parity (#21338)
Fixes #21202 Clause-②: no ## What changes 1. **`.github/workflows/ci.yml`, the `core:` filter** gains 18 literal entries: `turbo.json`, the root `tsup.config.ts`, and the 16 `scripts/*.mjs` files that a build task Build Core runs declares as a `$TURBO_ROOT$/...` input. A PR that changes only one of them now starts Build Core at PR time. Before this change the merge queue was the first place that build ran (PR #21199, a `turbo.json`-only diff, skipped Build Core). 2. **`scripts/check-ci-filter-parity.mjs`** gets a third subject, `judgeBuildInputs`. It derives the required set and does not keep a copy: - from `turbo.json`: the file itself, its `globalDependencies`, and every `$TURBO_ROOT$/...` input of a `build` or `PKG#build` task; - minus the builds that Build Core's `run: pnpm build` skips. Those are read from the root `build` script's `--filter=!PKG` tokens (today `@objectstack/docs#build` and its `content/**` input). If the script is misread, the gate can only drop an exclusion, which makes it require more of `core`. So a misread is a loud red, never a silent gap. - It reds when a declared build input has no covering `core` entry. - It also checks the reverse direction where the declarations alone can decide it. A literal `core` entry sitting in a directory that holds declared build inputs, but covering none of them, reds as a leftover. - It refuses eight unread states: no `build-core` job; its `if:` no longer names `core`; no `run: pnpm build` step; `turbo.json` or the root manifest unreadable or not JSON; no `tasks` map; no build task; a `build` script that is not `turbo run build ...`. - It uses the same pure-string coverage rule as the existing `crosspkg` subject, so no third matcher is added. 3. **Two forced follow-ons, both outside the claim's declared file surface.** Each is the remedy text of a gate that this change turned red. Neither is a design choice. - **Build Core gains `node scripts/check-dts-emitted.mjs --self-test`.** Once `core:` named that file, `check:self-test-wired` reported that a workflow names a script whose `--self-test` no workflow runs. That is true: 67 package build commands end with that checker, and its self-test ran nowhere in CI. The gate's remedy is "wire it into the step that runs it". The step takes milliseconds, needs no prerequisites, and runs before `pnpm build`. - **`scripts/pm/dispatch-gates.mjs`, one self-test specimen.** The case "a name-shaped script no family runs is NOT a gate script" used `scripts/check-dts-emitted.mjs`, which item 3a turned into a family file. The specimen is now `scripts/check-test-typecheck.mts`, which only package manifests run. That file is already named in the same docblock's list of such files. 4. **Three comments this change made false, corrected in `e6aa08f322` (patch round 2, comments only):** the GAP 1 block in `scripts/sync-template-versions.mjs`, the same block in `.github/workflows/lint.yml`, and the extglob docblock sentence in `scripts/pm/dispatch-gates.mjs` ("one entry of six"). Each now reads true with `core` matching the root build inputs. The re-measure for a diff confined to `scripts/sync-template-versions.mjs` reads `core=true`, `crosspkg=true`. No change to `turbo.json` or `fleet-write.yml`. `lint.yml` changes only its GAP 1 comment block (about lines 2660 to 2675), which is disjoint from #21295's pm roster block. ## Granularity: literal files, not `scripts/**` I measured this over the 3288 first-parent commits of `main` in the 30 days to `1371dc980c`, with `scripts/pm/git-history.mjs` proving the window was present (floor 2026-06-27). I used picomatch `{ dot: true }`, the options `dorny/paths-filter` uses, under both 2.3.1 and 4.0.5. The two versions agree on every commit. | `core` filter | commits it matches | added by this change | |:--|--:|--:| | before | 2247 | — | | + the 18 literal build inputs (this PR) | 2275 | **+28** | | + `scripts/**`, `turbo.json`, `tsup.config.ts` | 2840 | +593 | 516 of the 593 that `scripts/**` would add touch a `scripts/pm/` tool or a `scripts/check-*` gate. The gate holds the literal list to the declarations, so the narrow choice costs nothing in drift. Widening `core` starts every job that `core` gates, not only Build Core: Test Core, Temporal Conformance and Dogfood also run on those 28. That matches turbo's own view, because every one of those jobs builds, and a moved build hash re-runs everything downstream of it. ## Acceptance: the filter evaluated per path The `core` list is parsed out of `ci.yml` at the base and at this head, in the action's order (YAML, then the `filters` string). Each path is matched with picomatch `{ dot: true }`. | path | before (2.3.1 / 4.0.5) | after (2.3.1 / 4.0.5) | |:--|:--|:--| | `turbo.json` | false / false | true / true | | `tsup.config.ts` | false / false | true / true | | each of the 16 `scripts/*.mjs` build inputs | false / false | true / true | | `tsconfig.json` (globalDependencies) | true / true | true / true | | `packages/cli/src/commands/init.ts` (create-objectstack#build) | true / true | true / true | | control: `scripts/check-ci-filter-parity.mjs` | false / false | false / false | | control: `scripts/pm/dispatch-gates.mjs` | false / false | false / false | | control: `content/docs/index.mdx` | false / false | false / false | | control: `packages/spec/src/ui/view.zod.ts` | true / true | true / true | The 16 scripts are each checked individually: `build-input-hash`, `check-dev-prereqs`, `check-dts-emitted`, `check-dts-references`, `check-regen-pending`, `cli-build-prerequisite`, `git-env`, `import-prerequisite`, `invoked-as`, `js-comment-mask`, `regen-artifacts`, `sync-scaffold-emission-policy`, `sync-template-versions`, `ts-parse`, `tsup-drop-sources-content`, `workspace-enumerator`. **Live run: none yet.** This PR changes `ci.yml`, which `core` already matched, so its own run proves nothing. The first demonstration will be the first PR after this one lands whose diff is confined to listed paths. At the measured rate that is about one a day (28 in 30 days). The `turbo.json` card #20790 is the known candidate if its PR touches only `turbo.json`. ## The new leg can go red - **Self-test battery (9), 33 cases; the gate goes from 68 to 101 assertions.** - A build input dropped from a fixture `core:` reds, naming the path and the task that declared it. - `turbo.json` itself, a `globalDependencies` entry, and an input under a new root are each required. - Test-task inputs, negated inputs, and the excluded docs build are left out. The docs build's input is required again once the build script stops excluding it. - A leftover entry reds. - All eight refusals are covered (ten cases). - The checked-in tree is green. With `scripts/invoked-as.mjs` dropped from it, the gate reds through `main()`, the report path. - **Battery (8)'s report-path scratch root** now carries the real `turbo.json` and root manifest, so a red there cannot come from the new subject refusing a missing file. - **Before/after on the real tree.** The extended gate run over the base `ci.yml` exits 1 and lists the 18 inputs. Over this head it exits 0. - **Ablations**, done with `scripts/ablation-replace.mjs`. Each mutation was confirmed on disk, and each restore was proven by blob equal to `HEAD` and an empty `git diff HEAD`: - Coverage forced true in `judgeBuildInputs`: production exit 0 (green), `--self-test` exit 1 with 9 named failures. - Leftover rule disabled: production exit 0, `--self-test` exit 1 with 1 named failure. - My first attempt at the leftover ablation was a no-op. Its replacement text contained the anchor, so the tool refused and wrote nothing. I re-ran it with a non-overlapping replacement. ## Gates (local, head ec413ce) - I re-derived the list with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths; merge base `1371dc980`): 58 commands. - `--ran` reconciliation: "58 derived famil(ies) accounted for — 54 run, 4 NOT-MEASURED (0 DERIVED from a recorded exit 3, 4 claimed)". Every one of the 54 recorded exit 0. - That includes `check-ci-filter-parity` and its `--self-test`, `check:self-test-wired`, `bare-root-worklist --self-test`, `check:required-contexts`, `check:workflow-step-name-quoting`, `check:step-collectors`, `check:stall-guard-budget` and `check:nul-bytes`. - It also includes `check:pm-dispatch-gates`, whose self-test passed 1976 cases (778 s on this box). Its "fabrication direction" case passes with the new specimen. - The four `dist/`-reading Build Core gates (`check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`): their `--self-test` legs exit 0. Their real legs are **NOT MEASURED** locally, because they read a full workspace `dist/` and this diff moves no build hash. The changed files are a workflow and two root scripts, none of them a declared build input. Build Core runs them on this PR. ## Changeset None. The changed files are a workflow, a root script and a `scripts/pm/` tool. None of them is in any published package's `files[]`, and the root package is private. `skip-changeset` applies. ## Acceptance notes (not changed here) - **Two comments this change made false, plus a third the grep found:** all three are corrected in this PR (`e6aa08f322`, item 4 above), per the seat's patch round. The carrier this note first named, #21294's PR, had already landed. (Seat edit.) - **Build Core's own guard scripts do not schedule Build Core.** `check-dts-closure`, `check-dual-build-cjs-loads`, `check-sourcemap-no-sources-content` and `check-lean-entry-closure` run only inside Build Core. A diff confined to one of them leaves `core` false (picomatch 2.3.1 and 4.0.5, both before and after this PR). Measured on PR #17100 (`91f65c4ea4`), which changed only `scripts/check-lean-entry-closure.mjs`: Build Core was `skipped`, so neither the changed gate's self-test nor its real leg ran at PR time. This is a different population from build inputs, because turbo does not declare these files, so it is reported here and left unchanged. --- _Generated by [Claude Code](https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 99e1912 commit 57c9fe3

5 files changed

Lines changed: 515 additions & 41 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,48 @@ jobs:
178178
- 'pnpm-lock.yaml'
179179
- 'tsconfig.json'
180180
- '.github/workflows/ci.yml'
181+
# BUILD INPUTS outside every package (#21202): turbo.json itself,
182+
# its `globalDependencies`, and each `$TURBO_ROOT$/…` input of a
183+
# build task Build Core's `pnpm build` runs. A diff confined to
184+
# one of them moves the build hashes it reaches, and before these
185+
# entries it matched nothing here, so Build Core skipped and the
186+
# merge queue ran that build first (PR #21199, turbo.json only).
187+
# Every core-gated job starts on such a diff, not Build Core
188+
# alone: each one builds, and turbo re-runs every task downstream
189+
# of a moved build hash.
190+
#
191+
# scripts/check-ci-filter-parity.mjs DERIVES the required set from
192+
# turbo.json and the root `build` script, and reds while a declared
193+
# build input is uncovered here, or while a literal entry beside
194+
# them covers none (a leftover). `tsconfig.json` and the
195+
# `packages/cli/...` input are covered by the entries above;
196+
# `@objectstack/docs#build` (its input is `content/**`) is the
197+
# docs filter's, since `pnpm build` excludes that package.
198+
#
199+
# WIDTH: literal files, not `scripts/**`. Over the 3288
200+
# first-parent commits of `main` in the 30 days to `1371dc980c`,
201+
# through picomatch 2.3.1 and 4.0.5 (they agree on every row),
202+
# `core` matched 2247; these entries add 28, `scripts/**` with
203+
# turbo.json and tsup.config.ts would add 593, 516 of them diffs
204+
# touching a `scripts/pm/` tool or a `scripts/check-*` gate.
205+
- 'turbo.json'
206+
- 'tsup.config.ts'
207+
- 'scripts/build-input-hash.mjs'
208+
- 'scripts/check-dev-prereqs.mjs'
209+
- 'scripts/check-dts-emitted.mjs'
210+
- 'scripts/check-dts-references.mjs'
211+
- 'scripts/check-regen-pending.mjs'
212+
- 'scripts/cli-build-prerequisite.mjs'
213+
- 'scripts/git-env.mjs'
214+
- 'scripts/import-prerequisite.mjs'
215+
- 'scripts/invoked-as.mjs'
216+
- 'scripts/js-comment-mask.mjs'
217+
- 'scripts/regen-artifacts.mjs'
218+
- 'scripts/sync-scaffold-emission-policy.mjs'
219+
- 'scripts/sync-template-versions.mjs'
220+
- 'scripts/ts-parse.mjs'
221+
- 'scripts/tsup-drop-sources-content.mjs'
222+
- 'scripts/workspace-enumerator.mjs'
181223
# Build inputs of the vendored Console SPA — see the Console Pin Gate
182224
# job at the bottom of this file. `.objectui-sha` is a ROOT DOTFILE, so
183225
# it matches neither filter above: a pin-only diff skipped `core` and
@@ -2047,6 +2089,16 @@ jobs:
20472089
- name: Install dependencies
20482090
run: pnpm install --frozen-lockfile
20492091

2092+
# 67 package build commands end with `node …/scripts/check-dts-emitted.mjs`,
2093+
# so the `pnpm build` below runs that checker on every package it builds,
2094+
# and until this step no workflow ran its `--self-test`. It surfaced when
2095+
# the `core:` filter began naming the file as a build input (#21202) and
2096+
# `check:self-test-wired` saw a workflow name a self-test it never runs.
2097+
# Milliseconds, no prerequisites; placed before the build so a broken
2098+
# checker is reported before the 67 verdicts that rely on it.
2099+
- name: check-dts-emitted self-test
2100+
run: node scripts/check-dts-emitted.mjs --self-test
2101+
20502102
# ── Turbo remote cache (#21186) ─────────────────────────────────────
20512103
# Vercel's managed Turborepo remote cache. objectstack-ai/cloud's
20522104
# test.yml builds this repo at its pinned SHA with the same command and

‎.github/workflows/lint.yml‎

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,19 +2660,17 @@ jobs:
26602660
# GAP 1 — SCHEDULING. CLOSED by #9829 / #10014. The original claim:
26612661
# `create-objectstack#test` is reachable, at PR time, only from ci.yml's
26622662
# `test` job; that job was gated on the `core` paths-filter ALONE; and
2663-
# `core` — `packages/**`, `examples/**`, `apps/!(docs)/**`,
2664-
# `package.json`, `pnpm-lock.yaml`, `tsconfig.json`,
2665-
# `.github/workflows/ci.yml` — matches no path under `scripts/`, so a diff
2666-
# confined to scripts/sync-template-versions.mjs skipped Test Core
2667-
# ENTIRELY, and the vitest with it. `core` is still false on that diff — it
2668-
# was never widened — but the `test` job now ORs in a SECOND filter output,
2669-
# `scripts: ['scripts/**']`, which matches, so
2670-
# `if: ... (core != 'false' || scripts != 'false')` resolves to RUN.
2671-
# Re-measured against the merged filter with picomatch 2.3.1 — the version
2672-
# dorny/paths-filter@v4's own package-lock.json resolves, NOT this repo's
2673-
# 4.0.5, which is what the old text cited; the two agree on these globs, so
2674-
# the wrong figure never produced a wrong verdict — `core=false`,
2675-
# `scripts=true`. With the job running the rest of the chain follows:
2663+
# `core` then matched no path under `scripts/`, so a diff confined to
2664+
# scripts/sync-template-versions.mjs skipped Test Core ENTIRELY, and the
2665+
# vitest with it. Two changes closed it, either one enough: the `test` job
2666+
# ORs in a SECOND filter output, `crosspkg:` (named `scripts:` at #9829),
2667+
# whose `scripts/**` matches; and `core` itself now names that file,
2668+
# because it is a declared build input of `create-objectstack#build`
2669+
# (#21202). So `if: ... (core != 'false' || crosspkg != 'false')` resolves
2670+
# to RUN. Re-measured against the filter at #21202 with picomatch 2.3.1 —
2671+
# the version dorny/paths-filter@v4's own package-lock.json resolves, NOT
2672+
# this repo's 4.0.5; the two agree on these globs — `core=true`,
2673+
# `crosspkg=true`. With the job running the rest of the chain follows:
26762674
# `--union-into` is a step inside it, so it runs and unions
26772675
# create-objectstack back in off its declared globs; `create-objectstack#test`
26782676
# declares

0 commit comments

Comments
 (0)