Skip to content

The console dist cache key omits packages/spec, and the new injection assertion lives inside build-console.sh — so a cache hit serves a console whose bundled spec lags this tree, silently #9667

Description

@claude

Filed by the PM seat (domain:devx) from PR #9660's open question. The dev measured it while wiring CI for #8134 and deliberately did not act on it — correctly, since it changes CI cost characteristics.

The residue

PR #9660 makes scripts/build-console.sh inject OBJECTSTACK_SPEC_DIST so the console bundles this tree's@objectstack/spec instead of the published tarball, and adds scripts/assert-console-spec-injection.mjs as a post-build assertion.

But the console dist cache key does not include packages/spec, and it is spelled identically in three places (verified on origin/main):

.github/workflows/ci.yml:1439 key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}
.github/workflows/ci.yml:1485 (identical)
.github/workflows/release.yml:491 (identical)

So a console dist built while spec was at state X is restored and reused after spec moves to X+1 — and because the new assertion runs insidebuild-console.sh, a cache hit skips it entirely.

⚠️The injection fixes resolution; the cache can still serve a console whose bundled spec lags this tree. PR #9660 busts the key once (it edits build-console.sh), so the gap reopens on the next spec-only change, silently — which is the same shape as the defect #8134 fixed, one layer out.

Ruled shape: B — lift the assertion into a standalone check:* gate

The four options the dev laid out, with the PM ruling:

OptionVerdict
AAdd a packages/spec content hash to the cache key⛔ Rejected — closes the class fully, but every spec change busts the cache and forces a full cold console rebuild (~20 min, measured) on a repo doing ~18 merges/day
BKeep the key; run the assertion against the restored dist on every PRRuled
CAccept it, scope the concern to release.yml⛔ Rejected — leaves PR-time verification blind, and a defect first seen at release time is the expensive kind
DDo nothing⛔ Rejected

Why B: ci.yml's comments show the split restore/save was deliberately engineered so a failed build never poisons the cache. B preserves those economics and removes only the silent half — the assertion already exists and is content-derived, so running it against a restored dist costs one node process.

⚠️ Two constraints, because B has a trap of its own

  1. A gate that reddens a PR because a cached artifact is stale will red on PRs that did nothing wrong. Decide deliberately whether the right response is to fail or to invalidate and rebuild. If it fails, the message must name the remedy, not just report the mismatch — a red nobody knows how to clear is how a gate becomes noise.
  2. Do not let it become a second --re-measure. Whatever it does must be cheap enough to run on every PR, or it will be skipped and we are back here.

Worth checking while you are in there

Refs: #8134 · PR #9660 · #9659 · #8893 (the .objectui-sha pin lag)


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions