3838 docs : ${{ steps.changes.outputs.docs || 'true' }}
3939 core : ${{ steps.changes.outputs.core || 'true' }}
4040 console : ${{ steps.changes.outputs.console || 'true' }}
41- # Gates the `test` job ONLY, ORed with `core` — see the `scripts `
42- # filter below and the `test` job's `if:` (#9829).
43- scripts : ${{ steps.changes.outputs.scripts || 'true' }}
41+ # Gates the `test` job ONLY, ORed with `core` — see the `crosspkg `
42+ # filter below and the `test` job's `if:` (#9829, #10015 ).
43+ crosspkg : ${{ steps.changes.outputs.crosspkg || 'true' }}
4444 # ── THE FILTER CONTRACT, both halves (#4928) ──────────────────────────
4545 #
4646 # Half 1 is the `|| 'true'` above: when in doubt, RUN EVERYTHING. It
@@ -110,30 +110,66 @@ jobs:
110110 - 'scripts/console-spec-probes.mjs'
111111 - 'scripts/assert-console-spec-injection.mjs'
112112 - '.github/workflows/ci.yml'
113- # Test inputs that live OUTSIDE every package (#9829). Packages whose
114- # suites read across their own boundary declare that radius in
113+ # Test inputs that live OUTSIDE every package (#9829, #10015 ). Packages
114+ # whose suites read across their own boundary declare that radius in
115115 # scripts/check-cross-package-test-inputs.mjs, and turbo.json mirrors each
116- # as a `$TURBO_ROOT$/…` input. SIX of those declarations are rooted at
117- # `scripts/` — @objectstack/spec (`scripts/**`), @objectstack/cli,
118- # @objectstack/metadata-protocol and create-objectstack (five single
119- # files) — and the step that pulls those packages back into the affected
120- # set, `--union-into` in the `test` job below, is a step INSIDE that job.
121- # So on a `scripts/`-only diff `core` was false, the job never started,
122- # the union never ran, and the declarations bought nothing at PR time:
123- # the merge queue was the first signal, which is the #7802 failure this
124- # whole mechanism exists to prevent. Measured with picomatch, the matcher
125- # dorny/paths-filter uses: a diff confined to
126- # `scripts/sync-template-versions.mjs` yields core=false.
116+ # as a `$TURBO_ROOT$/…` input. The step that pulls those packages back into
117+ # the affected set, `--union-into` in the `test` job below, is a step
118+ # INSIDE that job — so when NO filter matches the changed path the job
119+ # never starts, the union never runs, and the declaration buys nothing at
120+ # PR time: the merge queue is the first signal, which is the #7802 failure
121+ # this whole mechanism exists to prevent.
127122 #
128- # Deliberately a SEPARATE output rather than a widening of `core`,
129- # because it gates ONE job — the `test` job ORs it with `core`, while
130- # Build Core, Dogfood and Temporal Conformance stay filtered exactly as
131- # they are today. `scripts/**` rather than the six declared paths: the
132- # narrower filter does not exist while @objectstack/spec declares
133- # `scripts/**`, and a hand-kept list of script paths is the failure mode
134- # #7802 recorded ("a list you must remember to update").
135- scripts:
123+ # Measured by instantiating every one of turbo.json's `$TURBO_ROOT$`
124+ # declarations to a real tracked file and running it through these filters
125+ # with picomatch — the matcher dorny/paths-filter uses — under BOTH 2.3.1
126+ # (what the action's own lockfile resolves and ncc-bundles) and 4.0.5 (what
127+ # this tree resolves); the two agree on every row. On `0bfbeb70c`, 19 of 64
128+ # declarations were core=false. #9829 closed the nine rooted at `scripts/`;
129+ # the four roots added below are the remaining ten (#10015).
130+ #
131+ # Deliberately a SEPARATE output rather than a widening of `core`, because
132+ # it gates ONE job — the `test` job ORs it with `core`, while Build Core,
133+ # Dogfood and Temporal Conformance stay filtered exactly as they are today.
134+ # That separation is what makes `content/**` affordable at all. Widening
135+ # `core` would put the whole core pipeline on every docs PR; here a
136+ # docs-only diff starts Test Core, whose package set is `turbo ls
137+ # --affected` (empty for such a diff) UNIONED with the packages whose
138+ # declarations matched — so it runs the declaring package, not the
139+ # workspace. Measured through the real `--union-into` on all three
140+ # content-only commits in the window below: `create-objectstack` alone,
141+ # all three times, against a positive control where a
142+ # `packages/lint/src/**` diff unions in @objectstack/spec and
143+ # @objectstack/core.
144+ #
145+ # WIDTH, per entry — each is exactly what some package declared, never a
146+ # root rounded up:
147+ # `scripts/**` and `content/**` are declared verbatim, by
148+ # @objectstack/spec and create-objectstack respectively, so the
149+ # narrower filter does not exist and a hand-kept list of the paths
150+ # inside them is the failure mode #7802 recorded ("a list you must
151+ # remember to update"). Eight further single-file `scripts/`
152+ # declarations and six narrower `content/docs/…` ones (spec, cli,
153+ # dogfood) sit inside the two.
154+ # The last three are a single file, a single file and a single subtree,
155+ # because that is what the declarations say — NOT `docs/**`,
156+ # `skills/**` or `.github/workflows/**`. Over the last 100 first-parent
157+ # commits of `main` at `0bfbeb70c`, those three roots would newly have
158+ # started Test Core on 5, 2 and 3 commits; the declared paths on ZERO.
159+ #
160+ # PRICE, re-measured over that same 100-commit window through this matcher:
161+ # Test Core scheduling goes 80/100 → 83/100. All three additions are
162+ # content-only docs commits, and each runs one package's suite.
163+ #
164+ # This list is a SECOND recognizer of those declarations and is kept in
165+ # step with them BY HAND: nothing fails today when a declaration lands in a
166+ # root no entry here covers. Closing that is #10379.
167+ crosspkg:
136168 - 'scripts/**'
169+ - 'content/**'
170+ - 'docs/audits/2026-07-unknown-key-strictness-ledger.md'
171+ - 'skills/objectstack-formula/**'
172+ - '.github/workflows/scaffold-e2e.yml'
137173
138174 test :
139175 # Sharded 3-way BY PACKAGE: a core-touching PR ran the affected suite
@@ -160,7 +196,7 @@ jobs:
160196 # in, never ANDed: two independent reasons to run this job, and it skips only
161197 # when BOTH filters explicitly said false, so the contract's "when in doubt,
162198 # RUN EVERYTHING" posture is unchanged (#9829).
163- if : ${{ !cancelled() && (needs.filter.outputs.core != 'false' || needs.filter.outputs.scripts != 'false') }}
199+ if : ${{ !cancelled() && (needs.filter.outputs.core != 'false' || needs.filter.outputs.crosspkg != 'false') }}
164200 runs-on : ubuntu-latest
165201 # Backstop only — the stall guard on the test steps is the primary
166202 # detector for a #4250-style hang and fires well before this. 30 min is
0 commit comments