From 61585a3e805f9cf8a6f0e043c904d57961402bed Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 27 Jul 2026 10:02:06 +0000 Subject: [PATCH 1/2] =?UTF-8?q?chore(ci):=20cut=20PR=20wall-clock=20~9.5mi?= =?UTF-8?q?n=20=E2=86=92=20~5-6min=20=E2=80=94=20dedupe=20dogfood,=20shard?= =?UTF-8?q?=20it,=20cache=20lint's=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three data-driven fixes from job-level timing of recent PR runs: 1. Test Core re-ran the whole ~7.5-minute dogfood suite that the dedicated Dogfood job was already running in parallel (both gate on the same `core` filter). Exclude @objectstack/dogfood from both the PR --affected run and the push run; the Dogfood job remains the sole (and unconditional) runner of the suite. Verified locally: turbo unions inclusive filters and then subtracts `!` negations, so `--affected --filter=!@objectstack/dogfood` is exactly "affected minus dogfood" (140 → 137 tasks, nothing else lost). 2. The Dogfood job itself was the workflow's longest pole (7m34s for the ~60-file suite on one 4-vCPU runner). Shard it 2-way with vitest's deterministic file-level --shard, passed through turbo (pass-through args are part of the turbo task hash, so each shard caches independently — verified: distinct hashes per shard). The `objectstack verify` CLI step is shard-independent and runs on shard 1 only. NOTE: if branch protection requires "Dogfood Regression Gate", the required check must be renamed to the two sharded names. 3. lint.yml's "TypeScript Type Check" job runs a full workspace build but was the only build-running job with NO turbo cache step — it rebuilt everything from scratch every run (4m38s observed, vs 51s for the same build in ci.yml's cached Build Core). Add the same .turbo/cache step. Also: lint.yml and codeql.yml had no concurrency group, so superseded runs of both (they trigger on every PR sync) kept burning runners and delaying the queue. Add the same cancel-in-progress policy ci.yml already documents. Full-chain smoke: `turbo run test --filter=@objectstack/dogfood -- --shard=1/30` runs green end-to-end (turbo → pnpm → vitest, 61 tasks OK). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01ECTCrcCdZpCHw5zFSgcmGt --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++-------- .github/workflows/codeql.yml | 7 ++++++ .github/workflows/lint.yml | 21 ++++++++++++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91f7eb1fdd..aefb913639 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,18 +130,25 @@ jobs: # --concurrency=4: turbo's default (10) oversubscribes the 4-vCPU # hosted runner; matching the core count bounds peak memory and the # job is CPU-bound anyway. + # !@objectstack/dogfood: the ~7½-minute dogfood suite is the dedicated + # Dogfood job's whole purpose, and both jobs run under the same `core` + # filter — without the exclusion every core PR executed the suite twice + # in parallel, and it dominated this job's critical path. The exclusion + # subtracts from the affected set (verified: turbo unions inclusive + # filters, then applies `!` negations to the result). - name: Run affected tests (PR) if: github.event_name == 'pull_request' env: TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }} - run: pnpm turbo run test --affected --concurrency=4 + run: pnpm turbo run test --affected --filter=!@objectstack/dogfood --concurrency=4 # Push to main: full run, but exclude spec's plain test task — the # coverage step below executes the exact same 250-file spec suite once, - # with coverage. Previously the spec suite ran twice per push. + # with coverage. Previously the spec suite ran twice per push. Dogfood is + # excluded for the same reason as the PR step: the Dogfood job runs it. - name: Run all tests (push) if: github.event_name == 'push' - run: pnpm turbo run test --filter=!@objectstack/spec --concurrency=4 + run: pnpm turbo run test --filter=!@objectstack/spec --filter=!@objectstack/dogfood --concurrency=4 - name: Generate coverage report if: github.event_name == 'push' @@ -156,12 +163,22 @@ jobs: retention-days: 30 dogfood: - name: Dogfood Regression Gate + # Sharded 2-way: the suite is ~60 independent test files, each booting its + # own in-process app, and a single 4-vCPU runner needed ~7½ minutes for the + # lot — the longest pole in the whole workflow. vitest partitions the file + # list deterministically across shards; both shards must pass. If branch + # protection lists "Dogfood Regression Gate" as a required check, it must be + # updated to the two sharded check names. + name: Dogfood Regression Gate (${{ matrix.shard }}/2) needs: filter if: needs.filter.outputs.core == 'true' runs-on: ubuntu-latest permissions: contents: read + strategy: + fail-fast: false + matrix: + shard: [1, 2] steps: - name: Checkout repository @@ -191,14 +208,17 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store-v3- + # Shard-scoped key: the turbo test hash differs per shard (pass-through + # args are part of the task hash), and two same-named matrix jobs would + # otherwise race to save one cache entry per sha. - name: Setup Turbo cache uses: actions/cache@v6 with: path: .turbo/cache - key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}- - ${{ runner.os }}-turbo-${{ github.job }}- + ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}- + ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}- - name: Install dependencies run: pnpm install --frozen-lockfile @@ -206,15 +226,19 @@ jobs: # Boots real example apps in-process (in-memory SQLite) and exercises them # through the real HTTP + service stack — catches runtime regressions that # build / unit tests / spec-liveness pass over (e.g. the #2018 tz-bucketing - # break, which was green on every static gate). + # break, which was green on every static gate). The `--` args reach the + # package's `vitest run` and are hashed into the turbo task, so each + # shard caches independently. - name: Boot example apps and exercise real user flows - run: pnpm turbo run test --filter=@objectstack/dogfood + run: pnpm turbo run test --filter=@objectstack/dogfood -- --shard=${{ matrix.shard }}/2 # Replaces the former auto-verify dogfood tests: runs the published # `objectstack verify` engine over each example app through the CLI — # auto-derived CRUD round-trip fidelity + the cross-owner RLS invariant. # Exits non-zero on a real runtime failure, so it gates like the tests did. + # Not shard-dependent, so shard 1 alone runs it. - name: Verify example apps via the `objectstack verify` CLI + if: matrix.shard == 1 run: | pnpm turbo run build --filter=@objectstack/cli for app in examples/app-crm examples/app-showcase; do diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 813efc69dc..f962e0453c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -11,6 +11,13 @@ on: # Run at 02:00 UTC every Monday - cron: '0 2 * * 1' +# A superseded analysis of an outdated commit has no value — cancel it. PR runs +# group by PR number; push/schedule runs group by ref, so a newer main push +# cancels only an in-flight main analysis (the newer commit gets analyzed). +concurrency: + group: codeql-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 77b45511ff..a32f094a18 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,13 @@ on: branches: - main +# Same policy as ci.yml: superseded runs on the same PR/branch waste runners +# and delay feedback; cancel them. Push runs to main group by commit ref, so an +# in-flight main run is cancelled only by a newer main push. +concurrency: + group: lint-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: @@ -133,6 +140,20 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store-v3- + # This job runs the full workspace build below ("Build workspace + # packages"); without a restored turbo cache that step rebuilt every + # package from scratch on every run (~4½ min) while ci.yml's jobs — which + # do carry this cache — finished the same build in under a minute. Same + # key scheme as ci.yml so the fallback prefix can also hit main's caches. + - name: Setup Turbo cache + uses: actions/cache@v6 + with: + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}- + ${{ runner.os }}-turbo-${{ github.job }}- + - name: Install dependencies run: pnpm install --frozen-lockfile From 8c17c09be27fac2a90f504053471a64bc223137a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 27 Jul 2026 10:04:56 +0000 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20empty=20changeset=20=E2=80=94=20CI?= =?UTF-8?q?-only=20change,=20releases=20nothing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01ECTCrcCdZpCHw5zFSgcmGt --- .changeset/ci-performance-optimization.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changeset/ci-performance-optimization.md diff --git a/.changeset/ci-performance-optimization.md b/.changeset/ci-performance-optimization.md new file mode 100644 index 0000000000..a7e15756d4 --- /dev/null +++ b/.changeset/ci-performance-optimization.md @@ -0,0 +1,4 @@ +--- +--- + +CI-only: exclude the dogfood suite from Test Core (the dedicated Dogfood job runs it), shard the Dogfood gate 2-way, add the missing turbo cache to lint's typecheck job, and cancel superseded lint/CodeQL runs. Releases nothing.