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
73 changes: 54 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,16 +183,38 @@ jobs:
# it matches neither filter above: a pin-only diff skipped `core` and
# `docs` alike, which is how #4288 moved the pin 76 commits with six of
# fourteen checks skipped and nothing anywhere building the new SHA.
# The last two entries are the "a change to the guard runs the guard"
# rule the repo applies to every other filtered gate; they are close to
# free here because an unmoved pin hits the dist cache.
#
# Every entry is one of two kinds, and scripts/check-ci-filter-parity.mjs
# holds each one to its kind (#20765):
# - a BUILD INPUT: something the console build reads to produce the
# cached dist or the injection stamp inside it. Each one is ALSO
# hashed into the `console-pin` job's dist-cache key. A head that
# selects the job by moving a build input and then restores the old
# dist from cache runs the gate without judging the change, because
# the build-time assertion lives inside the build step a hit skips.
# - a GUARD: code every run executes, cache hit or miss (the two check
# scripts, this workflow). That is the "a change to the guard runs the
# guard" rule the repo applies to every other filtered gate, and it is
# close to free here because an unmoved key hits the dist cache.
# The two packages/spec entries are the spec's ENTRY LAYOUT: the exports
# map that objectui's OBJECTSTACK_SPEC_DIST hook and the probe derivation
# both resolve entry by entry, and the tsup entry list that decides which
# built file each entry points at. A head that moves them runs this gate
# before it enqueues. #20695 moved the migration registries to a new
# entry, skipped this job on every push, and went red first in the merge
# queue, where a check outside the required set cannot stop a merge.
# ⛔ Not packages/spec/**: the rest of the spec is bundled CONTENT, and
# its lag behind a cached dist is the ruled cache design (#9667, #9706;
# see scripts/check-console-injection.mjs).
console:
- '.objectui-sha'
- 'scripts/build-console.sh'
- 'scripts/check-console-sha.mjs'
- 'scripts/check-console-injection.mjs'
- 'scripts/console-spec-probes.mjs'
- 'scripts/assert-console-spec-injection.mjs'
- 'packages/spec/package.json'
- 'packages/spec/tsup.config.ts'
- '.github/workflows/ci.yml'
# Test inputs that live OUTSIDE every package (#9829, #10015). Packages
# whose suites read across their own boundary declare that radius in
Expand Down Expand Up @@ -2307,10 +2329,12 @@ jobs:
# already covered in practice; the hole is a hand-edited pin, or that single
# line cherry-picked out of another branch.
#
# Cost is real but narrowly aimed. A moved pin is the only trigger that pays
# the full clone + vite build, and #4288's 76 commits of staleness are the
# measure of how rare that is. The other triggers (this file, the drift guard)
# leave the pin untouched, so they hit the dist cache and cost about a minute.
# Cost is real but narrowly aimed. A moved BUILD INPUT (the pin, the build and
# probe scripts, the spec's entry layout; see the `console` filter's comment)
# is the only trigger that always pays the full clone + vite build: 32 of the
# 3219 first-parent commits on main from 2026-08-31 to 9905e61ca2 moved one.
# The other triggers (this file, the check scripts) leave the key untouched,
# so they cost about a minute whenever the entry is still in the cache.
#
# Scope, stated plainly: this proves the PINNED SHA builds. It does NOT cover a
# packages/client change breaking the injected-client bundle — that input lives
Expand Down Expand Up @@ -2402,10 +2426,20 @@ jobs:
- name: Build @objectstack/client and its dependencies
run: pnpm exec turbo run build --filter=@objectstack/client... --concurrency=4

# Same key as release.yml's "Cache vendored Console dist" step. Actions
# caches are repo-scoped, so a PR reads whatever main already built for this
# pin — that is what makes the non-pin triggers cheap. Only a pin the repo
# has never built misses, and a miss is exactly when this gate has work to do.
# Keyed on every BUILD INPUT the `filter` job's `console` list names (the
# pin, the build script, the two scripts that derive and stamp the injection
# probes, and the spec's entry layout) and on nothing else.
# scripts/check-ci-filter-parity.mjs holds this key and that list in step
# (#20765). A head that moves a build input MISSES, so the build and the
# assertion inside it judge the change on the PR head, not first in the
# merge queue. A head that moves only a guard hits, which is what keeps
# those triggers cheap. A PR reads what main, or its own earlier pushes,
# saved under the same key.
#
# ⚠️ release.yml's restore step still keys on the pin and the build script
# alone, so the two workflows no longer share dist entries: each builds its
# own on a miss. The narrower release key is deliberate, and that file's
# comment says why (#20765).
#
# Split restore/save where release.yml uses the combined action, because the
# combined form's post-step saves even when the job FAILED. build-console.sh
Expand All @@ -2414,12 +2448,12 @@ jobs:
# run restores it, skips the build, and sails through the stamp check. Saving
# only once every assertion below is green is what lets a restored dist carry
# the same guarantees as a freshly built one.
- name: Restore vendored Console dist (keyed on the objectui pin)
- name: Restore vendored Console dist (keyed on the pin and its other build inputs)
id: console-dist
uses: actions/cache/restore@v6
with:
path: packages/console/dist
key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}
key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh', 'scripts/assert-console-spec-injection.mjs', 'scripts/console-spec-probes.mjs', 'packages/spec/package.json', 'packages/spec/tsup.config.ts') }}

# The gate. Shallow-clones objectui at the pinned SHA, builds
# @object-ui/console against this tree's client, copies the dist into
Expand Down Expand Up @@ -2458,10 +2492,11 @@ jobs:
# The spec-injection half of the same question, and the reason it is a
# SEPARATE step from build-console.sh (#9667).
#
# The dist cache key is hashFiles('.objectui-sha', 'scripts/build-console.sh')
# — packages/spec is deliberately NOT in it, because adding it would bust the
# key on every spec change and force a ~20 min cold console rebuild on a repo
# doing ~18 merges a day. The cost model stays; what does not stay is the
# The dist cache key carries only the spec's ENTRY LAYOUT (package.json and
# tsup.config.ts, see the restore step). The rest of packages/spec is
# deliberately NOT in it, because adding it would bust the key on every spec
# change and force a ~20 min cold console rebuild on a repo doing ~18 merges
# a day. The cost model stays; what does not stay is the
# silence. scripts/assert-console-spec-injection.mjs runs INSIDE
# build-console.sh, so the `if: cache-hit != 'true'` above skips it exactly
# when the dist was NOT built here — which is the run that most needs asking.
Expand All @@ -2482,7 +2517,7 @@ jobs:
# a poisoned entry is reachable from here and must not pass quietly.
- name: Verify the restored Console dist bundles this tree's spec
env:
CONSOLE_DIST_CACHE_KEY: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}
CONSOLE_DIST_CACHE_KEY: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh', 'scripts/assert-console-spec-injection.mjs', 'scripts/console-spec-probes.mjs', 'packages/spec/package.json', 'packages/spec/tsup.config.ts') }}
run: pnpm check:console-injection --require-stamp

# Reached only with every assertion above green (see the split-restore note).
Expand All @@ -2495,4 +2530,4 @@ jobs:
uses: actions/cache/save@v6
with:
path: packages/console/dist
key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}
key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh', 'scripts/assert-console-spec-injection.mjs', 'scripts/console-spec-probes.mjs', 'packages/spec/package.json', 'packages/spec/tsup.config.ts') }}
10 changes: 7 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1378,9 +1378,13 @@ jobs:
- name: Build
run: pnpm run build

# ci.yml's Console Pin Gate (#4290) uses this exact key, so the pin bump's
# PR run and this job share one build — keep the two in step if either
# input set changes.
# This key hashes the pin and the build script. ci.yml's Console Pin Gate
# (#4290) hashes a SUPERSET: those two, the probe scripts and the spec's
# entry layout (#20765). So the two workflows no longer share dist entries,
# and each one builds its own on a miss. Keeping this key narrower is
# deliberate. Following ci.yml would rebuild the console on every Version
# Packages merge, and publishing a cached console whose spec content lags
# is the existing cache design.
#
# Split restore/save, matching ci.yml, and not the combined actions/cache
# action: the combined form's post-step saves even when the job FAILED,
Expand Down
Loading
Loading