@@ -75,11 +75,13 @@ jobs:
7575 - '.github/workflows/ci.yml'
7676
7777 test :
78- # Sharded 2-way BY PACKAGE: a core-touching PR ran the affected suite
79- # ~11½ min on one 4-vCPU runner — the longest pole in the whole workflow.
80- # scripts/partition-test-shards.mjs splits the package list into two
81- # deterministic, test-file-count-balanced halves (573/572 at the time of
82- # writing) and each shard runs its half through turbo. NOT the dogfood
78+ # Sharded 3-way BY PACKAGE: a core-touching PR ran the affected suite
79+ # ~11½ min on one 4-vCPU runner — the longest pole in the whole workflow —
80+ # and at 2 shards the slower shard still ran ~10 min, keeping merge-queue
81+ # builds at ~11 min end-to-end (#4859).
82+ # scripts/partition-test-shards.mjs splits the package list into three
83+ # deterministic, test-file-count-balanced thirds and each shard runs its
84+ # slice through turbo. NOT the dogfood
8385 # job's vitest --shard passthrough, deliberately: that works for dogfood
8486 # because it is ONE package with ~60 files, but applied workspace-wide,
8587 # vitest 4 hard-fails every package with fewer test files than the shard
@@ -90,13 +92,13 @@ jobs:
9092 # Branch protection requires the bare "Test Core" context, which a matrix
9193 # can never publish again — the test-gate job below carries that name
9294 # (the #3622 lesson; see dogfood-gate).
93- name : Test Core (${{ matrix.shard }}/2 )
95+ name : Test Core (${{ matrix.shard }}/3 )
9496 needs : filter
9597 if : needs.filter.outputs.core == 'true'
9698 runs-on : ubuntu-latest
9799 # Backstop only — the stall guard on the test steps is the primary
98100 # detector for a #4250-style hang and fires well before this. 30 min is
99- # ~4 × a normal sharded run (~6-7 min), with margin for a cold Turbo cache;
101+ # ~5 × a normal sharded run (~4-6 min), with margin for a cold Turbo cache;
100102 # the old 45 left a hung job "running" for half an hour past any plausible
101103 # healthy finish.
102104 timeout-minutes : 30
@@ -105,7 +107,7 @@ jobs:
105107 strategy :
106108 fail-fast : false
107109 matrix :
108- shard : [1, 2]
110+ shard : [1, 2, 3 ]
109111
110112 steps :
111113 - name : Checkout repository
@@ -190,7 +192,7 @@ jobs:
190192 pnpm exec turbo ls --output=json > "$RUNNER_TEMP/turbo-ls.json"
191193 fi
192194 node scripts/partition-test-shards.mjs "$RUNNER_TEMP/turbo-ls.json" \
193- --shard ${{ matrix.shard }}/2 --exclude @objectstack/dogfood \
195+ --shard ${{ matrix.shard }}/3 --exclude @objectstack/dogfood \
194196 > "$RUNNER_TEMP/shard-packages.txt"
195197 echo "Packages on this shard:"
196198 cat "$RUNNER_TEMP/shard-packages.txt"
@@ -499,18 +501,19 @@ jobs:
499501 test
500502
501503 dogfood :
502- # Sharded 2-way: the suite is ~60 independent test files, each booting its
503- # own in-process app, and a single 4-vCPU runner needed ~7½ minutes for the
504- # lot — the longest pole in the whole workflow. vitest partitions the file
505- # list deterministically across shards; both shards must pass. If branch
506- # protection lists "Dogfood Regression Gate" as a required check, it must be
507- # updated to the two sharded check names.
508- name : Dogfood Regression Gate (${{ matrix.shard }}/2)
504+ # Sharded 3-way: the suite is ~60 independent test files, each booting its
505+ # own in-process app; a single 4-vCPU runner needed ~7½ minutes for the
506+ # lot, and at 2 shards each half still ran ~7 min — the longest pole left
507+ # once Test Core went 3-way (#4859). vitest partitions the file list
508+ # deterministically across shards; all shards must pass. Branch protection
509+ # requires only the bare "Dogfood Regression Gate" context, carried by the
510+ # dogfood-gate job below — the shard count can change without touching it.
511+ name : Dogfood Regression Gate (${{ matrix.shard }}/3)
509512 needs : filter
510513 if : needs.filter.outputs.core == 'true'
511514 runs-on : ubuntu-latest
512515 # Backstop only — the stall guard on the test step is the primary detector
513- # for a #4250-style hang (see Test Core). 30 min is ~4 × a shard (~7 min;
516+ # for a #4250-style hang (see Test Core). 30 min is ~6 × a shard (~5 min;
514517 # the verify-CLI pass that used to ride shard 1 is its own parallel job
515518 # now — dogfood-verify below).
516519 timeout-minutes : 30
@@ -519,7 +522,7 @@ jobs:
519522 strategy :
520523 fail-fast : false
521524 matrix :
522- shard : [1, 2]
525+ shard : [1, 2, 3 ]
523526
524527 steps :
525528 - name : Checkout repository
@@ -552,7 +555,10 @@ jobs:
552555 # Shard-scoped key: the turbo test hash differs per shard (pass-through
553556 # args are part of the task hash). Restore-only on PRs — see the Restore
554557 # Turbo cache comment in the test job; the save step at the end of this
555- # job seeds from main only.
558+ # job seeds from main only. The job-level catch-all (same as Test Core's)
559+ # is what keeps a NEW shard number warm before main has ever saved it:
560+ # turbo's cache is content-addressed per task, so another shard's entries
561+ # replay the shared build closure even when the test slice differs.
556562 - name : Restore Turbo cache
557563 uses : actions/cache/restore@v6
558564 with :
@@ -561,6 +567,7 @@ jobs:
561567 restore-keys : |
562568 ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-
563569 ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-
570+ ${{ runner.os }}-turbo-${{ github.job }}-
564571
565572 - name : Install dependencies
566573 run : pnpm install --frozen-lockfile
@@ -585,7 +592,7 @@ jobs:
585592 mkdir -p "$RUNNER_TEMP/stall-reports"
586593 node scripts/run-with-stall-guard.mjs --log "$RUNNER_TEMP/dogfood.log" --stall-minutes 10 \
587594 --report-dir "$RUNNER_TEMP/stall-reports" -- \
588- pnpm turbo run test --filter=@objectstack/dogfood -- --shard=${{ matrix.shard }}/2
595+ pnpm turbo run test --filter=@objectstack/dogfood -- --shard=${{ matrix.shard }}/3
589596
590597 # Dogfood boots real apps in-process, so a native/OOM abort is likelier
591598 # here than in the unit suites — and a shard that dies silently looks like
0 commit comments