77 *
88 * ## The hole this closes (objectstack#9667)
99 *
10- * The vendored Console SPA is cached under
11- *
12- * ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}
13- *
14- * spelled identically in ci.yml (twice: restore + save) and release.yml. The key
15- * does NOT include packages/spec, so a dist built while spec was at state X is
16- * restored and reused after spec moves on — and because
10+ * The vendored Console SPA is cached under a key each workflow spells in its
11+ * own restore step: ci.yml's `console-pin` job and release.yml's publish job.
12+ * The key is deliberately not restated here, because a copy in a comment is one
13+ * that nothing holds in step (scripts/check-ci-filter-parity.mjs holds ci.yml's
14+ * spellings to its `console` filter). The two keys no longer match
15+ * (objectstack#20765): release.yml hashes the pin and the build script, and
16+ * ci.yml hashes those plus the probe scripts and the spec's ENTRY LAYOUT
17+ * (package.json, tsup.config.ts). Neither includes the spec's CONTENT, so a dist
18+ * built while spec was at state X is restored and reused after spec moves on,
19+ * and because
1720 * scripts/assert-console-spec-injection.mjs runs INSIDE build-console.sh, a
1821 * cache hit skips the entire build step and therefore skips the assertion too.
1922 * The injection fixed resolution; the cache could still serve a console whose
2023 * bundled spec is not the one this build proved.
2124 *
22- * Adding packages/spec to the cache key was considered and REJECTED: it busts
23- * the key on every spec change and forces a full cold console rebuild (~20 min,
25+ * Adding the whole of packages/spec to the cache key was considered and
26+ * REJECTED: it busts the key on every spec change and forces a full cold console rebuild (~20 min,
2427 * measured) on a repo doing ~18 merges a day. The cache's economics — including
2528 * ci.yml's deliberate split restore/save, which exists so a failed build never
2629 * poisons the entry — are kept exactly as they are. Only the silent half is
4851 * the stamped detector is STILL ABSENT from this tree's spec. Once the published
4952 * spec catches up, the stamp is expired and says so instead of passing.
5053 *
51- * ## Why packages/spec is NOT in ci.yml's console filter (objectstack#9710)
54+ * ## Why the rest of packages/spec is NOT in ci.yml's console filter (objectstack#9710)
5255 *
53- * That filter lists the pin, the build script and this gate's own sources — not
54- * packages/spec — so a spec-only PR never schedules Console Pin Gate and never
55- * reaches this check. Adding it is the obvious next thought; it was measured and
56+ * That filter lists the pin, the build and probe scripts and this gate's own
57+ * sources, and since objectstack#20765 also the spec's ENTRY LAYOUT
58+ * (package.json's exports map and tsup.config.ts). ci.yml's dist key hashes
59+ * each of those build inputs too, so a head that moves the entry layout MISSES
60+ * the cache, rebuilds, and assert-console-spec-injection.mjs judges it; a
61+ * cache hit could not have, as the paragraphs below explain. The rest of
62+ * packages/spec is its CONTENT, which is in neither the filter nor the key, so
63+ * a content-only spec PR never schedules Console Pin Gate and never reaches
64+ * this check. Adding the content is the obvious next thought; it was measured and
5665 * DECLINED, and the reason is not cost, which is why it is recorded here rather
5766 * than left on a card: the job it would schedule is vacuous, not expensive.
5867 *
5968 * Count what this gate can fail on. Of its six failure verdicts, FIVE are pure
6069 * functions of the RESTORED DIST and its stamp — a missing dist, unreadable
6170 * assets or a malformed stamp, a missing stamp, the published-only detector
6271 * present in the bundle, the stamp's own fresh witness missing from it. A
63- * spec -only diff cannot move any of those: the cache key is the one spelled at
64- * the top of this header — the pin and the build script, nothing else — and
65- * entries under it are IMMUTABLE, so all five replay what the last
72+ * content -only spec diff cannot move any of those: it does not move the cache
73+ * key (see the top of this header for what each key hashes), and entries under
74+ * a key are IMMUTABLE, so all five replay what the last
6675 * console-filtered run already saw. Exactly ONE verdict reads this tree, the
6776 * expiry re-check, and it needs packages/spec/dist because readSpecBlob resolves
6877 * the package's exports map. So the restore-only job proposed there — no
@@ -214,13 +223,17 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
214223 * How an operator clears a cached dist this gate refused.
215224 *
216225 * ⛔ The `gh cache delete` line is MAINTAINER-ONLY — it needs repo write scope,
217- * and it deletes an entry shared by ci.yml and release.yml. It is named anyway
218- * because ruling out a remedy an operator cannot discover is how a red becomes
219- * noise; a contributor who cannot run it should hand this message to a
220- * maintainer rather than guess.
226+ * and it deletes an entry every later run of that workflow under the same key
227+ * would restore. It is named anyway because ruling out a remedy an operator
228+ * cannot discover is how a red becomes noise; a contributor who cannot run it
229+ * should hand this message to a maintainer rather than guess.
230+ *
231+ * The key comes from the CI step (`CONSOLE_DIST_CACHE_KEY`). Without it (a local
232+ * run) the line names WHERE each workflow spells its key instead of restating
233+ * one: ci.yml's and release.yml's keys differ (objectstack#20765), and a copy
234+ * here would be a third spelling nothing holds in step.
221235 */
222236function remedy ( cacheKey ) {
223- const key = cacheKey || '${{ runner.os }}-console-dist-${{ hashFiles(\'.objectui-sha\', \'scripts/build-console.sh\') }}' ;
224237 return [
225238 ' How to clear this:' ,
226239 '' ,
@@ -231,7 +244,14 @@ function remedy(cacheKey) {
231244 ' • In CI — the restored artifact is a CACHE ENTRY, not anything in this PR.' ,
232245 " Nothing in the diff can fix it; the entry has to go. ⛔ MAINTAINER-ONLY:" ,
233246 '' ,
234- ` gh cache delete "${ key } "` ,
247+ ` gh cache delete "${ cacheKey || 'KEY' } "` ,
248+ ...( cacheKey
249+ ? [ ]
250+ : [
251+ '' ,
252+ " KEY is the key the failing job's restore step printed. ci.yml's" ,
253+ " `console-pin` job and release.yml's publish job each spell their own." ,
254+ ] ) ,
235255 '' ,
236256 ' then re-run the Console Pin Gate job. The next run misses, rebuilds,' ,
237257 ' and re-stamps.' ,
0 commit comments