@@ -78,22 +78,29 @@ jobs:
7878 run : bun run build
7979
8080 # The suite is sharded so the slowest slice, not the whole suite, sets the
81- # wall clock. Every shard still goes through check:projects-dir-guard: the
82- # guard forwards these path filters to the suite it wraps, and the union of
83- # the shards' filters is exactly ./src ./tests ./evals ./scripts, so the gate covers
84- # the same tests as before, all of them sandboxed.
81+ # wall clock. Four time-balanced shards split the same
82+ # ./src ./tests ./evals ./scripts union via bun's --shard=k/4, balanced by
83+ # the checked-in per-file durations in scripts/ci-timings.json (--timings).
84+ # Every shard still goes through check:projects-dir-guard: the guard
85+ # forwards the path union plus the shard flags to the suite it wraps, so the
86+ # gate covers the same tests as before, all of them sandboxed.
87+ #
88+ # Timings refresh policy: regenerate scripts/ci-timings.json by running the
89+ # full union locally with --update-timings (same seeded flags as `test`):
90+ # bun run check:projects-dir-guard ./src ./tests ./evals ./scripts \
91+ # --timings=./scripts/ci-timings.json --update-timings
92+ # Regen when the slowest shard's Test step skews more than ~20% above a
93+ # quarter of the one-process suite time (shards drifting apart means the
94+ # timings no longer describe the suite), or proactively whenever slow files
95+ # land. A scheduled refresh artifact is a future option, not current setup.
8596 test :
8697 runs-on : ubuntu-latest
8798 strategy :
88- # A red shard must not cancel the other; both results are the signal.
99+ # A red shard must not cancel the others; all results are the signal.
89100 fail-fast : false
90101 matrix :
91- shard :
92- - name : src
93- paths : ./src
94- - name : tests-evals-and-scripts
95- paths : ./tests ./evals ./scripts
96- name : test (${{ matrix.shard.name }})
102+ shard : ["1/4", "2/4", "3/4", "4/4"]
103+ name : test (${{ matrix.shard }})
97104 steps :
98105 - name : Checkout
99106 uses : actions/checkout@v4
@@ -122,19 +129,63 @@ jobs:
122129 - name : Install dependencies
123130 run : bun install --frozen-lockfile
124131
125- # The same script the local `bun run check` gate runs, with the shard's
126- # path filters forwarded through the guard to the suite. The guard
127- # routes a filtered run through test:paths, which carries the same
128- # seeded flags as the `test` script; bun test filters are additive, so
129- # appending filters to `bun run test` could not narrow it. Randomized
130- # order catches tests that only pass in the default file order (shared
131- # module-level state, an unrestored global mock, a leaked env var).
132+ # The same script the local `bun run check` gate runs, with the full
133+ # path union plus the shard's --shard/--timings flags forwarded through
134+ # the guard to the suite. The guard routes a filtered run through
135+ # test:paths, which carries the same seeded flags as the `test` script;
136+ # bun test filters are additive, so appending filters to `bun run test`
137+ # could not narrow it. --shard splits by file (balanced by --timings),
138+ # so every shard covers the same union and the four shards together
139+ # cover the whole suite. Randomized order catches tests that only pass
140+ # in the default file order (shared module-level state, an unrestored
141+ # global mock, a leaked env var).
132142 # The seed stays 424242 in every shard rather than varying per shard:
133143 # the shards already run disjoint file sets, and a fixed seed keeps
134144 # any failure reproducible locally with the same
135- # `bun run test:paths <paths>`.
145+ # `bun run test:paths <paths> --shard=k/4
146+ # --timings=./scripts/ci-timings.json`.
136147 - name : Test
137- run : bun run check:projects-dir-guard ${{ matrix.shard.paths }}
148+ run : bun run check:projects-dir-guard ./src ./tests ./evals ./scripts --shard=${{ matrix.shard }} --timings=./scripts/ci-timings.json
149+
150+ # Cross-shard pollution detector: the shards above split the path union,
151+ # but the union is not the isolation domain — a mock.module leak across
152+ # files fails in the one-process suite yet passes when the files land in
153+ # different shards (CL-6967). This job reruns the whole union in one
154+ # process with no filters, exactly like the local `bun run check` gate.
155+ # Non-blocking (continue-on-error) so a slow or flaky full run cannot hold
156+ # the gate; a real pollution failure still shows up red for triage.
157+ test-one-process :
158+ name : test (one-process pollution detector)
159+ continue-on-error : true
160+ runs-on : ubuntu-latest
161+ steps :
162+ - name : Checkout
163+ uses : actions/checkout@v4
164+
165+ - name : Setup Node
166+ uses : actions/setup-node@v4
167+ with :
168+ node-version : " 24"
169+
170+ - name : Setup Bun
171+ uses : oven-sh/setup-bun@v2
172+ with :
173+ bun-version : " 1.3.14"
174+
175+ - name : Install ripgrep
176+ run : sudo apt-get install -y ripgrep
177+
178+ - name : Cache dependencies
179+ uses : actions/cache@v4
180+ with :
181+ path : node_modules
182+ key : bun-${{ hashFiles('bun.lock') }}
183+
184+ - name : Install dependencies
185+ run : bun install --frozen-lockfile
186+
187+ - name : Test
188+ run : bun run check:projects-dir-guard
138189
139190 # protect-main still requires the pre-restructure check names. These jobs
140191 # exist only to publish those contexts after the real work succeeds.
0 commit comments