From 00fb95e38809f8adfa4957eed3f705786848f753 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 11:57:03 +0000 Subject: [PATCH] ci(repo-gates): restore the build's turbo cache, save and prune on push only (objectui#13348) Repo Gates cold-built `./packages/*` on every pull request, push and queue build: the one build-carrying job with no `.turbo/cache` restore. Add the `performance-budget.yml` shape (objectui#12276): one restore key under this job's own `turbo--repo-gates-` prefix, TURBO_RUN_SUMMARY on the build step, then the replay report, the push-only prune and the push-only bounded save as bookkeeping after the last gate. The build's filter set is unchanged. The two in-tree pins that read this workflow move with it: the action-ref convention test's list of repo-gates.yml refs, and the CI/CD pipeline page's Repo Gates section, which must name `scripts/report-turbo-cache.mjs`. Refs objectui#13348 Claude-Session: https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj Co-authored-by: Claude --- .github/workflows/repo-gates.yml | 91 +++++++++++++++++++ content/docs/guide/ci-cd-pipeline.md | 6 +- .../check-action-ref-convention.test.ts | 6 +- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/.github/workflows/repo-gates.yml b/.github/workflows/repo-gates.yml index 27d01d77cf..cee95e9f45 100644 --- a/.github/workflows/repo-gates.yml +++ b/.github/workflows/repo-gates.yml @@ -399,12 +399,43 @@ jobs: # word-split, so they stay unquoted. A derive step that failed contributes # nothing here and is red on its own; its gate then reports `unbuilt` # rather than judging a tree it cannot see. + # + # ── The build's turbo cache: this job's own, and only `main` writes it ── + # (objectui#13348) Until this step the build above ran cold on every + # pull request, push and queue build. The shape is `performance-budget.yml`'s + # `bundle-analysis` cache (objectui#12276), which `report-turbo-cache.test.ts` + # holds every `.turbo/cache` in `.github/workflows/` to: ONE restore key, + # this job's own `repo-gates` prefix, so no other job's entry is ever + # restored here; split restore/save (objectui#6577, objectui#7048), the + # restore UNBOUNDED for the reason written there; pull-request and queue + # runs restore and never save, so on a pull request's ref this key finds + # nothing and falls through to `main`. The report, the prune and the save + # are this job's last steps. + # + # It changes what the build REPLAYS, never what it builds: the filter set + # on the build step is untouched, and the header's "never an unfiltered + # `turbo run build`" holds as before. Gated on the install like the build + # it serves, so a run that will not build does not spend a restore. + - name: Restore Turbo Cache + id: turbo-cache + if: ${{ !cancelled() && steps.install.outcome == 'success' }} + uses: actions/cache/restore@v6 + with: + path: .turbo/cache + key: turbo-${{ runner.os }}-repo-gates-${{ github.sha }} + restore-keys: | + turbo-${{ runner.os }}-repo-gates- + + # `TURBO_RUN_SUMMARY` makes turbo write its run summary to `.turbo/runs/`; + # it changes nothing turbo runs or caches, and `Report what the turbo + # cache replayed` below reads it (objectui#12276). - name: Build every package the gates below read id: build if: ${{ !cancelled() && steps.install.outcome == 'success' }} env: SKILL_FILTER: ${{ steps.skill-filter.outputs.args }} SNIPPET_FILTER: ${{ steps.snippet-filter.outputs.args }} + TURBO_RUN_SUMMARY: 'true' run: pnpm exec turbo run build --filter='./packages/*' $SKILL_FILTER $SNIPPET_FILTER # The self-test runs BEFORE the corpus, and after the build because its @@ -481,6 +512,66 @@ jobs: if: ${{ !cancelled() && steps.build.outcome == 'success' }} run: pnpm check:readme-exports + # ── What the cache replayed, and only `main` writes it (objectui#13348) ── + # `performance-budget.yml`'s two bookkeeping steps (objectui#12276), for + # this job's cache. The report reads the summary the build step asked turbo + # for and puts what the restored cache replayed, what re-ran and the + # restored key on ONE `::notice` annotation, readable through the + # check-runs API by anyone who cannot open this log. The prune deletes + # every entry this run did not use, on `push` only -- the runs that save -- + # so `main`'s entry is exactly the current build outputs; pull-request and + # queue runs restore and never save, because what they would save is + # scoped to a ref no other pull request can read, and every build-sized + # entry saved is one more a size-limited cache store can evict `main`'s for. + # + # `push` alone: this workflow has no `schedule`. Read before relying on it + # (objectui#13348): the push concurrency group here is the ref with + # `cancel-in-progress`, so a merge can cancel the previous merge's run, and + # 18 of the 20 newest `main` push runs succeeded (2 cancelled) -- the save + # lands on most merges. + # + # Bookkeeping after the last checking step, bounded and + # `continue-on-error`. `timeout-minutes: 1` each: the script reads one run + # summary and lists one directory (87 ms measured on objectui#12276); a + # minute is several hundred times that. + - name: Report what the turbo cache replayed + if: ${{ !cancelled() }} + timeout-minutes: 1 + continue-on-error: true + env: + RESTORED_CACHE_KEY: ${{ steps.turbo-cache.outputs.cache-matched-key }} + run: node scripts/report-turbo-cache.mjs + + - name: Prune the turbo cache to what this run used + if: github.event_name == 'push' + timeout-minutes: 1 + continue-on-error: true + run: node scripts/report-turbo-cache.mjs --prune + + # The bookkeeping half of the split documented at the restore step above, + # placed after every checking step. `cache-hit != 'true'` skips an exact + # primary-key hit; a `restore-keys` prefix match leaves it false and the + # save runs; the implicit `success()` applies; same `path` and `key` as the + # restore. Only `push` saves, after the prune above. + # + # `timeout-minutes: 5` is NOT yet derived from this site's own save: no run + # of this job has saved this cache. What it rests on is the nearest + # measured save of the same entry -- `bundle-analysis` builds the same + # task set (`--filter='./packages/*'`; the two derived filters here add no + # task on objectui#13348's base) and its save measured 1s (objectui#7048 + # arithmetic, `performance-budget.yml`). Re-derive it from this step's own + # runs. With the pinned build's 2 below, this job's saves stay at 7 of 30. + - name: Save Turbo Cache + if: >- + steps.turbo-cache.outputs.cache-hit != 'true' + && github.event_name == 'push' + uses: actions/cache/save@v6 + timeout-minutes: 5 + continue-on-error: true + with: + path: .turbo/cache + key: turbo-${{ runner.os }}-repo-gates-${{ github.sha }} + # The pinned @objectstack build, saved only when this job built it (a cache # miss) and whatever the verdict above was -- it does not depend on it. # Bookkeeping: bounded and non-fatal (objectui#6577, objectui#7048). The bound diff --git a/content/docs/guide/ci-cd-pipeline.md b/content/docs/guide/ci-cd-pipeline.md index 78abc67b53..7fb615009c 100644 --- a/content/docs/guide/ci-cd-pipeline.md +++ b/content/docs/guide/ci-cd-pipeline.md @@ -636,7 +636,11 @@ import graph free of `node_modules`. The three gates that judge the *published* one build: `turbo run build` over every package under `packages/` (the README export check reads each package's declared type entry), in union with the closures the two snippet gates derive with `--build-filter`. ⛔ It is never an unfiltered workspace build, and a derived filter that names no -package is refused at the step that derives it. +package is refused at the step that derives it. That build restores this job's own turbo cache +(key prefix `turbo--repo-gates-`, [#13348](https://github.com/objectstack-ai/objectui/issues/13348)): +pull-request and queue runs restore and never save; a push saves it after pruning it to what the +run used, and every run reports what it replayed — bookkeeping only, after the verdict — +with `scripts/report-turbo-cache.mjs`. The sections below are the fifteen gates, each under the name its step carries. diff --git a/scripts/__tests__/check-action-ref-convention.test.ts b/scripts/__tests__/check-action-ref-convention.test.ts index 64759d6ab2..59f565aea5 100644 --- a/scripts/__tests__/check-action-ref-convention.test.ts +++ b/scripts/__tests__/check-action-ref-convention.test.ts @@ -268,8 +268,12 @@ describe('prose cannot be mistaken for a reference', () => { expect(parseRefs(source, 'repo-gates.yml').map((r: { ref: string }) => r.ref)).toEqual([ 'actions/checkout@v7', 'actions/setup-node@v7', - // The pinned @objectstack build's restore and save (objectui#12957). + // In file order: the pinned @objectstack build's restore (objectui#12957), + // the build's turbo cache restore and save (objectui#13348), then the + // pinned build's save. 'actions/cache/restore@v6', + 'actions/cache/restore@v6', + 'actions/cache/save@v6', 'actions/cache/save@v6', ]); });