Repository navigation
Commit d78bd01
ci: Build Core starts on a diff confined to one of its own guard scripts, held to the job steps by check-ci-filter-parity (#21368)
Fixes #21341
Clause-②: no
## What changes
1. **`.github/workflows/ci.yml`, the `core:` filter**, gains five
literal rows after the build-input rows #21202 added:
- `scripts/check-dts-closure.mjs`
- `scripts/check-dual-build-cjs-loads.mjs`
- `scripts/check-lean-entry-closure.mjs`
- `scripts/check-sourcemap-no-sources-content.mjs`
- `scripts/population-floor.mjs` (the dual-build guard imports it)
A PR that changes only one of them now starts Build Core, so the changed
guard runs its `--self-test` and its real leg at PR time. Before this
change Build Core skipped on such a PR. PR #17100 changed only
`scripts/check-lean-entry-closure.mjs`, and its Build Core concluded
`skipped`.
2. **`scripts/check-ci-filter-parity.mjs`** holds those rows. The Build
Core subject #21202 added (`judgeBuildInputs`) now judges a second
population beside the build inputs from turbo.json: every repository
script that a `build-core` `run:` step runs, plus that script's
first-party imports.
- **Derived from the job, not kept by hand.** Each step goes through
`collectInvocations`. That is the recognizer `check:self-test-wired`
already uses to answer "which repository scripts does this CI command
run": a `scripts/...` path named directly, or one reached through a root
`package.json` alias chain. Each script it names brings its first-party
closure from `first-party-closure.mjs`.
- **Uncovered goes red.** If no `core` row covers a script that a step
runs, the gate fails and names the path and the step. So a guard step
added to Build Core without a filter row goes red.
- **Leftovers go red.** #21202's leftover rule now covers this
population too. A literal `core` row next to the build inputs and the
guards that covers neither is reported. So a row left behind by a
deleted guard step goes red too.
- **No new gate, no new workflow step, no new list.** The existing
parity gate gains a population, and the rows in ci.yml are the one list.
## Why derived, and not a `BUILD_GUARDS` table like `CONSOLE_GUARDS`
`CONSOLE_GUARDS` exists because the console subject has to classify each
row. A `console:` row is either hashed into the dist key or a guard, and
nothing in the workflow says which. Build Core rows need no
classification: the scripts its steps run can be read from the steps,
just as #21202 reads build inputs from turbo.json.
A `BUILD_GUARDS` table would be a second list of the same fact, kept in
step by hand. It would also need two holding rules of its own: table
against steps, and table against rows. The derivation needs only one
rule in each direction, and the self-test pins it against the real job.
## Measurements
Branch head `aa0b21ae2c`, base `96b12b589f`.
**The card's instance, through the matcher.** `core` is read at base and
at head. Each path is evaluated with picomatch using `{ dot: true }`,
which is how `dorny/paths-filter@v4` calls it. Version 2.3.1 is the one
the action bundles; 4.0.5 is the one this tree resolves.
| path | before, 2.3.1 | before, 4.0.5 | after, 2.3.1 | after, 4.0.5 |
|---|---|---|---|---|
| `scripts/check-dts-closure.mjs` | false | false | true | true |
| `scripts/check-dual-build-cjs-loads.mjs` | false | false | true | true
|
| `scripts/check-sourcemap-no-sources-content.mjs` | false | false |
true | true |
| `scripts/check-lean-entry-closure.mjs` | false | false | true | true |
| `scripts/population-floor.mjs` | false | false | true | true |
| `scripts/check-dts-emitted.mjs` (already a build input) | true | true
| true | true |
| control: `scripts/check-ci-filter-parity.mjs` | false | false | false
| false |
| control: `scripts/check-self-test-wired.mjs` | false | false | false |
false |
| control: `scripts/pm/dispatch-gates.mjs` | false | false | false |
false |
| control: `scripts/dual-build-cjs-loads.baseline.json` | false | false
| false | false |
The two versions agree on every row.
**Width.** The window is the 3287 first-parent commits of `main` in the
30 days to `96b12b589f`. `scripts/pm/git-history.mjs` proved it
complete; the history floor is 2026-06-27.
- `core` matched 2276 of those commits. With the five rows it matches
2278: +2, identical under 2.3.1 and 4.0.5.
- Seven commits touched a guard path. Only one touched nothing but guard
paths: `91f65c4ea4`, which is PR #17100, the card's own instance.
- The other newly scheduled commit is `83649b870a`. It changed
`check-dual-build-cjs-loads.mjs` and `population-floor.mjs` along with
two lint-side gates. `population-floor.mjs` on its own adds 0 commits in
the window.
**The gate before the rows.** This is the new script run over the base
ci.yml: exit 1, naming exactly five scripts, each with the step that
runs it.
```text
- 5 script(s) the `build-core` job's own steps run are covered by no `core:` entry in .github/workflows/ci.yml. ...
scripts/check-dts-closure.mjs (Sweep the built closure for vanished declarations (pnpm check:dts-closure))
scripts/check-dual-build-cjs-loads.mjs (Every published require entry point actually loads (pnpm check:dual-build-cjs-loads))
scripts/population-floor.mjs (imported by scripts/check-dual-build-cjs-loads.mjs)
scripts/check-sourcemap-no-sources-content.mjs (No published source map embeds source text (pnpm check:sourcemap-no-sources-content))
scripts/check-lean-entry-closure.mjs (The lean engine entry loads no forbidden or unlisted package (pnpm check:lean-entry-closure))
```
**After:** exit 0. The gate prints: `all 20 build input(s) ... and all
11 script(s) the build-core job's own steps run (5 named by a step, 6
only imported by one) are covered by core ... no literal core entry
beside them is a leftover`.
**Self-test.** 131 assertions, exit 0. The new battery (10) has a floor
of 30, which is the count it registers. The roster floor goes from 9 to
10.
On fixtures, battery (10) checks each of these:
- A guard row dropped from `core:` goes red, naming the path and the
step.
- A guard step added with no row goes red, whether it reaches its script
through an alias or names it directly.
- An imported module dropped from `core:` goes red.
- An alias chain is followed to its script.
- A commented-out line requires nothing.
- A guard that is also a build input needs only its one row.
- A row whose step is gone is reported as a leftover, including in a
directory that holds only a guard.
- A script whose closure cannot be computed is refused.
Over the real tree, it checks:
- The five step-run scripts and `population-floor.mjs` are each covered
by their own literal row.
- With the lean-entry row dropped, `main()` returns 1.
- The two recognizer bounds below are pinned to the real job.
The scratch tree that the report-path cases run in now stages the
guards' closures. Without that, a "returns 1" case could pass only
because the build subject refused.
**Ablation, run once on the real script and then restored.** The step
loop in `buildGuardsOf` was mutated to read nothing, through
`scripts/ablation-replace.mjs`: anchor 1 → 0, blob `bfb0ec815a` →
`2f03c5416d`.
- `--self-test`: exit 1, with 27 of 131 assertions failing.
- 24 of battery (10)'s 30 failed.
- Three real-tree cases in (5) and (9) that read the same verdict also
failed.
- Six cases in (10) held. Five of them do not depend on the derivation:
the anchor, the control, the two bound pins, and the shared-row case.
- The sixth is the report-path `main() === 1`, which the leftover
direction below satisfied. Its sibling, which asserts the exact finding,
failed.
- The production gate: exit 1 as well, but in the reverse direction.
With no population derived, the five rows became leftovers. So the rows
are held to the steps from both sides, and an emptied derivation cannot
pass silently.
- Restore: the blob equals HEAD `bfb0ec815a`, `git diff HEAD` is empty,
and the marker count is 0.
## Known bounds (stated in the header, two of them pinned in the
self-test)
- **Package-scoped scripts are not followed.** This means a script that
a package's own manifest runs, via `pnpm --filter PKG SCRIPT`. Build
Core's only such step today is the spec's `analyze`, which runs a file
inside `packages/spec`, and `packages/**` covers that. Pinned.
- **Local composite actions are not followed.** Build Core's only one is
setup-pnpm. Its `run:` steps name no repository script, and every job in
every workflow runs it. Pinned.
- **`pnpm install`'s lifecycle is not a guard.** `prepare` runs
`scripts/setup-git-hooks.mjs`, but every job installs, including the
unfiltered lint.yml jobs, so a change to it already runs at PR time.
- **Some extensions are not read.** A script in `.cjs` or `.ts` is not
seen. This is the same as `check:self-test-wired`, because the
recognizer is shared.
- **Data files are not scripts.** A data file a guard reads is not
derived.
## Acceptance notes
- **The dual-build baseline is not covered.**
`scripts/dual-build-cjs-loads.baseline.json` is a shrink-only ledger
that `check:dual-build-cjs-loads` reads. A diff confined to it still
skips Build Core (control row above). Closing that needs either a
declared data-input list or reading the gate's own declaration, which
goes beyond this card's "guard scripts". There is one such baseline
today. Noted, not filed.
- **A pre-existing comment overstates when Build Core runs.**
`scripts/check-dual-build-cjs-loads.mjs` (its dispatch-gates declaration
note) says Build Core is "a required context that runs on every PR".
Build Core is path-scoped by `core:`, so that was not true before this
PR either. Its operative claim, that a card touching `packages/**` still
gets Build Core, holds. This PR does not make the sentence false, and
the file is outside this card's surface. Noted, not edited.
- **Comments this PR corrects.** These sentences were made false by the
new population and are corrected here:
- ci.yml's build-input comment ("a literal entry beside them covers none
(a leftover)")
- the parity header's leftover sentence
- the parity header's known-bounds sentence
## Local verification
All readings at `aa0b21ae2c`.
- **Derived gate families.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` (no paths) derives 58 commands,
the same 58 the seat derived at dispatch.
- 53 exit 0.
- The four dist-reading guards (`check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`,
`check:sourcemap-no-sources-content`) pass their `--self-test` legs, and
their real legs exit 3 PREREQUISITE NOT MET because there is no `dist/`
here. NOT MEASURED locally, reason: no build. This PR's ci.yml change
schedules Build Core, which runs them.
- `check:pm-dispatch-gates` exits 0: 1976 cases, and the battery took
791.9s.
- `--ran` reconciliation: 58 derived, 54 run, 4 NOT-MEASURED (the exit-3
four), 0 unrun.
- **Roster gates.** Those whose roster sits under a directory this diff
touches: `check-platform-checklist-watchdog`,
`check-published-list-mirrors`, `check:engine-double-contract` and
`check:i18n-stale-fill` exit 0. `check:published-readme-exports` exits
3: NOT MEASURED, it needs built `.d.ts`.
- **ESLint.** Narrowed to the changed lintable files and run with
`--no-inline-config`.
- That is one file, `scripts/check-ci-filter-parity.mjs`; ci.yml matches
no `files` glob in `eslint.config.mjs`.
- JSON output: 1 file, 0 errors, 0 warnings.
- The config has no `parserOptions.project`, so linting is not
type-aware and this diff cannot move any untouched file's result.
- **The edited script's own tests.** No `*.test.*` in `scripts/` or in
the root package names `check-ci-filter-parity`. The three hits
elsewhere (`packages/types`, `packages/objectql`, `packages/runtime`)
mention it in comments only.
No changeset: workflow and repo-gate code only, nothing any package
publishes (`skip-changeset`).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3937ad2 commit d78bd01
2 files changed
Lines changed: 413 additions & 39 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
191 | 191 | | |
192 | 192 | | |
193 | 193 | | |
194 | | - | |
| 194 | + | |
| 195 | + | |
195 | 196 | | |
196 | 197 | | |
197 | 198 | | |
| |||
220 | 221 | | |
221 | 222 | | |
222 | 223 | | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
223 | 252 | | |
224 | 253 | | |
225 | 254 | | |
| |||
0 commit comments