Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/12276-lint-turbo-cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

No package released: CI configuration, one docs page and tests. `turbo.json` gives the root `//#lint:root` task the inputs it lints, so every package's `lint` hash stops moving on unrelated files and the `Lint` job's restored cache can replay it. The four turbo caches that restored a bare `turbo-<os>-` key (`Lint`, `Build Docs`, `Changeset Release`, `Bundle Analysis`) each get their own key prefix, are written only by runs on `main`, are pruned to what the run used, and report what they replayed (objectui#12276). No published package's files change.
58 changes: 51 additions & 7 deletions .github/workflows/changeset-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -709,14 +709,23 @@ jobs:
#
# The restore half is deliberately left UNBOUNDED: a restore stall fails
# BEFORE anything is published, which is the honest failure.
#
# ONE restore key, this job's own prefix (objectui#12276, the shape
# `ci.yml`'s `type-check` job took in objectui#12222). Until then this key
# was `turbo-<os>-<sha>` with a bare `turbo-<os>-` fallback, and `Lint`
# and `Bundle Analysis` saved under that same key, so this job restored
# whichever of their caches reached the sha first -- and its own save
# below found an exact hit and skipped. Every run of this job is on
# `main` (push, `schedule`, dispatch), so its own previous entry is what
# the prefix finds.
- name: Restore Turbo Cache
id: turbo-cache
uses: actions/cache/restore@v6
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-${{ github.sha }}
key: turbo-${{ runner.os }}-release-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-
turbo-${{ runner.os }}-release-

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -738,6 +747,8 @@ jobs:
# commit; this one only ever duplicated it, ~34 minutes later.

- name: Build packages
env:
TURBO_RUN_SUMMARY: 'true'
run: pnpm build

# ══════════════════════════════════════════════════════════════════════
Expand Down Expand Up @@ -1408,6 +1419,37 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# ── What the cache replayed, and only what it used is kept (objectui#12276) ──
# The `type-check` job's two bookkeeping steps (objectui#12222), for this
# job's cache. `Build packages` above sets `TURBO_RUN_SUMMARY`, so turbo
# wrote its run summary to `.turbo/runs/`; the report reads it 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 then deletes every entry
# that build did not use, so the entry saved below is exactly the
# current build outputs. Every run of this job is a `main` run, so both
# run on every run, after every release step: `!cancelled()` on the
# report so a failed release still reports, the prune on success like
# the save it prepares.
#
# Bounded and `continue-on-error`, like the save: neither can redden a
# job that has already published. `timeout-minutes: 1` each is DERIVED
# FOR THIS SITE: the script reads one run summary and lists one
# directory, and measured 87 ms on a lint run's summary
# (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
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 HERE — last in the job, after publish, after the post-version
# tree validation and after the refresh — because that is exactly where
Expand All @@ -1417,14 +1459,16 @@ jobs:
# `timeout-minutes: 5` is DERIVED, and the derivation is weaker here than
# at the other six sites — said plainly rather than dressed up.
# objectui#7048 fences inheriting PR #7047's 5, so: this cache is
# `.turbo/cache` under the same `turbo-${{ runner.os }}-${{ github.sha }}`
# key shape as `lint.yml` and `performance-budget.yml`, and the saves of
# THAT cache measured 1s, 2s, 5s and 6s across four jobs on 2026-09-02
# `.turbo/cache`, under the same `turbo-${{ runner.os }}-${{ github.sha }}`
# key as `lint.yml` and `performance-budget.yml` when this was derived
# (each job has its own key prefix since objectui#12276), and the saves
# of that cache measured 1s, 2s, 5s and 6s across four jobs on 2026-09-02
# (`Bundle Analysis` 100096569559, `Type Check` 100096569872,
# `Build Docs` 100094597253, `Lint` 100097466846). A same-SITE save was
# not observable in the sampled runs: this job runs on `push` to `main`
# only when the version is not yet on npm, and on the 6-hourly `schedule`
# the primary key is an exact hit, so `Post Turbo Cache` measured 0s on
# the primary key was an exact hit -- another job's save under the shared
# key -- so `Post Turbo Cache` measured 0s on
# the sampled scheduled run (job 99021319583, 2026-08-29) with its
# restore at 4s. 5 minutes is 50x the slowest of the four same-cache
# saves; if a directly measured save of this site ever lands and lies
Expand All @@ -1449,4 +1493,4 @@ jobs:
continue-on-error: true
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-${{ github.sha }}
key: turbo-${{ runner.os }}-release-${{ github.sha }}
51 changes: 50 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2484,6 +2484,14 @@ jobs:
# The restore half is deliberately left UNBOUNDED: a restore stall fails
# BEFORE any verdict exists — a gate that did not run, which is honest —
# rather than a recorded verdict discarded.
#
# ONE restore key, this job's own prefix (objectui#12276, the shape the
# `type-check` job above took in objectui#12222). Until then a bare
# `turbo-<os>-` key followed this one, and when the job's own prefix
# found nothing it matched any job's cache -- `Lint`'s, the bundle and
# release builds' -- which this job then saved again under its own key.
# Pull-request and queue runs save nothing now (see the prune step
# below), so on those refs this key falls through to `main`'s entry.
- name: Restore Turbo Cache
id: turbo-cache
if: steps.docs-changes.outputs.should_run == 'true'
Expand All @@ -2493,14 +2501,18 @@ jobs:
key: turbo-${{ runner.os }}-docs-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-docs-
turbo-${{ runner.os }}-

- name: Install dependencies
if: steps.docs-changes.outputs.should_run == 'true'
run: pnpm install --frozen-lockfile

# `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 Site
if: steps.docs-changes.outputs.should_run == 'true'
env:
TURBO_RUN_SUMMARY: 'true'
run: pnpm turbo run build --filter='@object-ui/site'

# REPORT-ONLY, by the 2026-09-06 ruling on objectui#7851, and it must stay
Expand Down Expand Up @@ -2533,6 +2545,40 @@ jobs:
if: steps.docs-changes.outputs.should_run == 'true'
run: node scripts/check-doc-expression-carriage.mjs

# ── What the cache replayed, and only `main` writes it (objectui#12276) ──
# The `type-check` job's two bookkeeping steps (objectui#12222), for this
# job's cache. The report reads the summary `Build Site` 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` and `schedule` only -- the runs that save --
# so `main`'s entry is exactly the current build outputs instead of
# every build output any earlier run left behind; pull-request and queue
# runs restore and never save (see the save step below).
#
# Both are bookkeeping after the last checking step, bounded and
# `continue-on-error`. `timeout-minutes: 1` each is DERIVED FOR THIS
# SITE: the script reads one run summary and lists one directory, and
# measured 87 ms on a lint run's summary (objectui#12276); a minute is
# several hundred times that, and with the save's 5 this job's
# bookkeeping stays at 7 of its 15 -- inside half the ceiling, which is
# the rule the save's own derivation below applies.
- name: Report what the turbo cache replayed
if: steps.docs-changes.outputs.should_run == 'true' && !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: >-
steps.docs-changes.outputs.should_run == 'true'
&& (github.event_name == 'push' || github.event_name == 'schedule')
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 HERE — after `Build Site`, the step that records this job's
# verdict — because that is exactly where the post phase it replaces
Expand Down Expand Up @@ -2563,10 +2609,13 @@ jobs:
# - the condition names no status function, so the implicit `success()`
# still applies — matching the combined action's `post-if: success()`.
# - same `path` and same `key` as the restore step above.
# One deliberate change (objectui#12276): only `push` and `schedule`
# save, after the prune step above, as in the `type-check` job.
- name: Save Turbo Cache
if: >-
steps.docs-changes.outputs.should_run == 'true'
&& steps.turbo-cache.outputs.cache-hit != 'true'
&& (github.event_name == 'push' || github.event_name == 'schedule')
uses: actions/cache/save@v6
timeout-minutes: 5
continue-on-error: true
Expand Down
82 changes: 79 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,22 +322,44 @@ jobs:
# The restore half is deliberately left UNBOUNDED: a restore stall fails
# BEFORE any verdict exists — a gate that did not run, which is honest —
# rather than a recorded verdict discarded.
#
# ONE restore key, this job's own prefix (objectui#12276, the shape
# `ci.yml`'s `type-check` job took in objectui#12222). Until then this key
# was `turbo-<os>-<sha>` with a bare `turbo-<os>-` fallback: two other
# workflows' build jobs (`changeset-release.yml`, `performance-budget.yml`)
# saved under that same key, and the fallback also matched the `docs` and
# `type-check` prefixes, so this job restored and re-saved whichever
# job's cache came first on the ref -- build outputs, for a task whose
# own entries hold only logs. `actions/cache` tries the run's
# OWN ref before `main`; pull-request and queue runs save nothing now (see
# the save step below), so on those refs this key finds nothing and the
# lookup falls through to `main`'s lint entry.
- name: Restore Turbo Cache
id: turbo-cache
if: steps.relevant.outputs.should_run == 'true'
uses: actions/cache/restore@v6
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-${{ github.sha }}
key: turbo-${{ runner.os }}-lint-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-
turbo-${{ runner.os }}-lint-

- name: Install dependencies
if: steps.relevant.outputs.should_run == 'true'
run: pnpm install --frozen-lockfile

# `TURBO_RUN_SUMMARY` makes turbo write its run summary to `.turbo/runs/`
# -- per task, the hash, the cache status and the tasks it depends on. It
# changes nothing turbo runs or caches; `Report what the turbo cache
# replayed` below reads it (objectui#12276). Every package's `lint`
# dependsOn `//#lint:root`, and turbo folds that task's hash into theirs,
# so `turbo.json` gives the root task the inputs it actually lints: with
# none it hashed every file in the repository, and no commit could replay
# a single package's lint.
- name: Run linter
if: steps.relevant.outputs.should_run == 'true'
env:
TURBO_RUN_SUMMARY: 'true'
run: pnpm lint

# ── vi.mock override VALUE shapes (objectui#8903) ─────────────────────
Expand Down Expand Up @@ -490,6 +512,57 @@ jobs:
if: steps.relevant.outputs.should_run == 'true'
run: pnpm check

# ── What the cache replayed (objectui#12276) ─────────────────────────
# The `type-check` job's report step (objectui#12222), here for this job's
# cache. Bookkeeping, like the save below: it runs after the last checking
# step, so it can neither delay nor change the verdict. It reads the run
# summary `Run linter` asked turbo for and prints, per task, what the
# restored cache replayed and what re-ran -- each re-run task with the
# tasks it depends on, so a dependency whose hash moved every package's
# is named in the log -- and puts the counts and the restored key on ONE
# `::notice` annotation, which the check-runs API serves to readers who
# cannot open this log (AGENTS.md #9: the instrument is the claim, its
# answer is not written down). `!cancelled()` so a red lint still
# reports what it replayed.
#
# `timeout-minutes: 1` is DERIVED FOR THIS SITE: the script reads one
# summary and lists one directory, and measured 87 ms on a lint run's
# summary (objectui#12276); a minute is several hundred times that, and
# with the save's 5 below this job's bookkeeping stays at 7 of its 25.
# `continue-on-error: true` because a broken report says nothing about
# the code under lint.
- name: Report what the turbo cache replayed
if: steps.relevant.outputs.should_run == 'true' && !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

# ── Only `push` writes this cache, and only what it used ─────────────
# The `type-check` job's rule (objectui#12222): pull-request and queue
# runs restore and never save, because what they would save is scoped to
# a ref no later run of another pull request can read, and every entry
# saved is one more a size-limited cache store can evict `main`'s for.
# A `push` run saves, after this step deletes every entry the run did
# not use, so `main`'s entry is exactly the current lint verdicts.
#
# `push` alone, not `push` or `schedule`: this workflow has no
# `schedule`. Measured on objectui#12276 before relying on it: the push
# concurrency group here is the ref, so a merge can cancel the previous
# merge's run, and 14 of the 20 newest `main` push runs succeeded (6
# cancelled) -- the save lands on most merges, and a pull request
# restores the newest one that did. Bookkeeping like the report step:
# bounded, `continue-on-error`; `scripts/report-turbo-cache.mjs` refuses
# to prune on a summary that lists no task.
- name: Prune the turbo cache to what this run used
if: >-
steps.relevant.outputs.should_run == 'true'
&& 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 HERE — after the last checking step — because that is exactly
# where the post phase it replaces already ran. Nothing about which
Expand Down Expand Up @@ -517,13 +590,16 @@ jobs:
# - the condition names no status function, so the implicit `success()`
# still applies — matching the combined action's `post-if: success()`.
# - same `path` and same `key` as the restore step above.
# One deliberate change (objectui#12276): only `push` saves, after the
# prune step above; see there for why.
- name: Save Turbo Cache
if: >-
steps.relevant.outputs.should_run == 'true'
&& 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 }}-${{ github.sha }}
key: turbo-${{ runner.os }}-lint-${{ github.sha }}
Loading
Loading