diff --git a/.changeset/ci-cache-tier1-optimizations.md b/.changeset/ci-cache-tier1-optimizations.md new file mode 100644 index 0000000000..676cb470c6 --- /dev/null +++ b/.changeset/ci-cache-tier1-optimizations.md @@ -0,0 +1,4 @@ +--- +--- + +CI-only: turbo cache for the Release job, console-dist cache keyed on the objectui pin, restore-only turbo caches on PRs (main seeds them), CodeQL off pull_request (main push + weekly schedule), spec coverage moved to a nightly workflow. Releases nothing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67d71706a0..6019a739a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,8 +111,13 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store-v3- - - name: Setup Turbo cache - uses: actions/cache@v6 + # Restore-only on PRs: PR-side saves (~5 turbo entries per push) churned + # the repo's 10 GB Actions cache pool and evicted the main-branch seeds — + # observed as sudden cold-cache spikes (Build Core 51s → 4m30s). PRs fall + # back to main's entries via the prefix restore-keys; only main pushes + # save (the "Save Turbo cache" step at the end of the job). + - name: Restore Turbo cache + uses: actions/cache/restore@v6 with: path: .turbo/cache key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} @@ -142,25 +147,25 @@ jobs: TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }} 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. Dogfood is - # excluded for the same reason as the PR step: the Dogfood job runs it. + # Push to main: full run. Spec's suite runs here plain (uninstrumented); + # the coverage-instrumented pass moved to the nightly Spec Coverage + # workflow (coverage-nightly.yml) — instrumentation added minutes to + # every main push for a trend artifact that is consulted occasionally at + # best. 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 --filter=!@objectstack/dogfood --concurrency=4 - - - name: Generate coverage report - if: github.event_name == 'push' - run: pnpm --filter @objectstack/spec test:coverage - - - name: Upload coverage reports - if: github.event_name == 'push' - uses: actions/upload-artifact@v7 + run: pnpm turbo run test --filter=!@objectstack/dogfood --concurrency=4 + + # Seed the shared Turbo cache from main only (see the restore step + # above). always(): keep the seed fresh even when a test fails, matching + # the old actions/cache post-step behavior. + - name: Save Turbo cache (main only) + if: always() && github.event_name == 'push' + uses: actions/cache/save@v6 with: - name: coverage-report - path: packages/spec/coverage/ - retention-days: 30 + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} dogfood: # Sharded 2-way: the suite is ~60 independent test files, each booting its @@ -209,10 +214,11 @@ jobs: ${{ 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 + # args are part of the task hash). Restore-only on PRs — see the Restore + # Turbo cache comment in the test job; the save step at the end of this + # job seeds from main only. + - name: Restore Turbo cache + uses: actions/cache/restore@v6 with: path: .turbo/cache key: ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-${{ github.sha }} @@ -247,6 +253,15 @@ jobs: echo "::endgroup::" done + # Seed the shared Turbo cache from main only (see the restore step + # above); shard-scoped key so the two matrix jobs don't collide. + - name: Save Turbo cache (main only) + if: always() && github.event_name == 'push' + uses: actions/cache/save@v6 + with: + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-${{ github.sha }} + dogfood-gate: # Stable required-check name for a SHARDED job (#3622 follow-up). # @@ -312,8 +327,13 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store-v3- - - name: Setup Turbo cache - uses: actions/cache@v6 + # Restore-only on PRs: PR-side saves (~5 turbo entries per push) churned + # the repo's 10 GB Actions cache pool and evicted the main-branch seeds — + # observed as sudden cold-cache spikes (Build Core 51s → 4m30s). PRs fall + # back to main's entries via the prefix restore-keys; only main pushes + # save (the "Save Turbo cache" step at the end of the job). + - name: Restore Turbo cache + uses: actions/cache/restore@v6 with: path: .turbo/cache key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} @@ -377,6 +397,14 @@ jobs: packages/spec/json-schema/ retention-days: 30 + # Seed the shared Turbo cache from main only (see the restore step above). + - name: Save Turbo cache (main only) + if: always() && github.event_name == 'push' + uses: actions/cache/save@v6 + with: + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} + build-docs: name: Build Docs needs: filter diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f962e0453c..b96600be01 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,21 +1,24 @@ name: CodeQL Security Analysis +# Deliberately NOT on pull_request: the analysis took ~4½ runner-minutes per +# PR sync, and everything a PR introduces is analyzed on main within minutes +# of the merge anyway (multi-agent merge cadence). Alerts surface on main's +# runs + the weekly deep pass instead of blocking/occupying PR runners. +# If branch protection ever required the "Analyze (javascript)" check, that +# requirement must be dropped along with this trigger. on: push: branches: - main - pull_request: - branches: - - main schedule: # 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). +# A superseded analysis of an outdated commit has no value — cancel it. A newer +# main push cancels only an in-flight main analysis (the newer commit still +# gets analyzed). concurrency: - group: codeql-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: codeql-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: diff --git a/.github/workflows/coverage-nightly.yml b/.github/workflows/coverage-nightly.yml new file mode 100644 index 0000000000..cc099feef2 --- /dev/null +++ b/.github/workflows/coverage-nightly.yml @@ -0,0 +1,63 @@ +# Spec coverage, nightly. Moved off the per-push path: the coverage- +# instrumented spec suite added minutes of instrumentation overhead to every +# main push (ci.yml previously excluded spec's plain test task and ran this +# instead), for a trend artifact that is consulted occasionally at best. The +# plain (uninstrumented) spec suite still runs on every main push in ci.yml's +# Test Core; this workflow owns the coverage report. +name: Spec Coverage + +on: + workflow_dispatch: + schedule: + - cron: '0 5 * * *' # 05:00 UTC nightly + +permissions: + contents: read + +jobs: + coverage: + name: Spec coverage report + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: '20' + + - name: Enable Corepack + run: corepack enable + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + # Restore-only: scheduled runs read main's store cache; the per-push + # workflows own saving it. + - name: Restore pnpm cache + uses: actions/cache/restore@v6 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store-v3- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # The spec package sits at the root of the workspace graph (no workspace + # deps) and its suite reads src/ directly, so no build step is needed — + # same reason lint.yml's typecheck gates run pre-build. + - name: Generate coverage report + run: pnpm --filter @objectstack/spec test:coverage + + - name: Upload coverage report + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: packages/spec/coverage/ + retention-days: 30 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a32f094a18..cbaa07357b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -145,8 +145,11 @@ jobs: # 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 + # Restore-only on PRs (same policy as ci.yml): PR-side saves churned the + # 10 GB Actions cache pool and evicted the main seeds; only main pushes + # save (the "Save Turbo cache" step at the end of the job). + - name: Restore Turbo cache + uses: actions/cache/restore@v6 with: path: .turbo/cache key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} @@ -243,3 +246,11 @@ jobs: # so it runs after the build step alongside the other consumer gates. - name: Check skills TypeScript examples compile run: pnpm --filter @objectstack/spec run check:skill-examples + + # Seed the shared Turbo cache from main only (see the restore step above). + - name: Save Turbo cache (main only) + if: always() && github.event_name == 'push' + uses: actions/cache/save@v6 + with: + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6699d6728..d5e3ae68c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,19 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store-v3- + # This job ran the full workspace build cold on every main push (~4½ + # min) — the same omission lint.yml's typecheck job had. Same key scheme + # as ci.yml. Runs only on main pushes, so plain save+restore is right + # here (this job is one of the cache seeders). + - 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 @@ -57,13 +70,34 @@ jobs: - name: Build run: pnpm run build + # The vendored Console dist is a pure function of the objectui pin (and + # of the build script itself) — yet it was cloned + vite-built from + # scratch on every main push (~3 min). Cache the finished dist keyed on + # exactly those inputs; the stamp check below still verifies whatever + # dist ends up in place, restored or fresh. + - name: Cache vendored Console dist (keyed on the objectui pin) + id: console-dist-cache + uses: actions/cache@v6 + with: + path: packages/console/dist + key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }} + - name: Build vendored @objectstack/console SPA # Clones objectstack-ai/objectui at the SHA pinned in .objectui-sha, # builds @object-ui/console, and copies dist/ into # packages/console/dist/. Must run before publish so the - # prepublishOnly guard in @objectstack/console passes. + # prepublishOnly guard in @objectstack/console passes. Skipped when the + # cache above restored a dist built from the same pin. + if: steps.console-dist-cache.outputs.cache-hit != 'true' run: bash scripts/build-console.sh + # Belt-and-suspenders for the cache path: build-console.sh stamps the + # objectui SHA it built from into dist/.objectui-sha; check:console-sha + # fails loudly if the dist in place (restored or fresh) drifts from the + # pin. + - name: Verify Console dist stamp matches pin + run: pnpm check:console-sha + - name: Downstream backward-compat smoke (live hotcrm) # Pre-publish gate (#2035): the about-to-publish @objectstack/spec must # not break a real third-party consumer pinned to a published release.