Repository navigation
Commit 3ad65b0
ci(console-pin): a head that moves the spec's entry layout runs Console Pin Gate and misses its dist cache (#20945)
Fixes #20765
Clause-②: no
A head that moves the spec's entry layout now runs `Console Pin Gate` on
the PR, and misses the dist cache so the gate judges it. Before this,
such a head skipped the job on every push, and the first run was the
merge-queue build, where a check outside the required set cannot stop a
merge (#20695: `main` red on this check for about 2 h 17 min).
## What changes
- **`ci.yml` `filter` job, `console:` list** gains
`packages/spec/package.json` and `packages/spec/tsup.config.ts`. The
comment above the list now says which entries are build inputs and which
are guards, and why the rest of `packages/spec` stays out.
- **`ci.yml` `console-pin` job, dist-cache key** (all three spellings:
restore, the `CONSOLE_DIST_CACHE_KEY` env, save) now hashes every build
input the list names: the pin, `scripts/build-console.sh`, the two probe
scripts (`scripts/assert-console-spec-injection.mjs`,
`scripts/console-spec-probes.mjs`), and the two spec files. Comments
that described the old key are corrected. The restore step name now says
what it is keyed on.
- **`scripts/check-ci-filter-parity.mjs`** gains a second subject,
`judgeConsole`, run by `main()` in both lint.yml legs. It holds five
things of the checked-in ci.yml: every `console` entry is a literal
path; the key is spelled one way everywhere the job uses it; every
hashed path is a `console` entry; every `console` entry is hashed or a
declared guard (`CONSOLE_GUARDS`: ci.yml and the two check scripts,
which run on a hit and a miss alike); and every declared guard is still
listed and not hashed. There is a new self-test battery (8) with 20
cases, and the battery floor goes from 7 to 8.
- **`scripts/pm/check-expected-skips.mjs`**: the `Console Pin Gate`
row's reason text names the two spec files. Only the prose changes. The
row's gate, the job's `if:` and the roster shape are unchanged.
- **`scripts/check-console-injection.mjs`** (comments and the `remedy()`
fallback text only; seat ruling 5920200169, Q2): the header no longer
restates the dist key. It names the two restore steps that each spell
one, and says the keys now differ. Its section is renamed "Why the rest
of packages/spec is NOT in ci.yml's console filter" and says the spec's
entry layout is in both the filter and ci.yml's key. The `remedy()`
fallback, used only when `CONSOLE_DIST_CACHE_KEY` is unset, prints `gh
cache delete "KEY"` plus a line naming where each workflow spells its
key, instead of a stale copy of the old key.
- **`.github/workflows/release.yml`** (comment only; no key, step or env
change; seat ruling 5920200169, Q1 = B): the comment above the dist
restore step says its key hashes the pin and the build script, ci.yml's
hashes a superset, the two workflows no longer share dist entries, and
the narrower key is deliberate.
## Why the key had to move too (H1, measured)
On a cache hit the job skips `Build the Console SPA`, and
`assert-console-spec-injection.mjs` runs inside that step. What runs on
a hit is `check:console-injection`, which replays the stamp the earlier
build wrote. The only thing it reads from the tree is the probe-expiry
check over the union of all exports entries, and moving text between
entries does not change that union.
- **The queue build that went red was a cache MISS.** Run 36645820047,
job 109668493655: `Restore vendored Console dist` took 0 s, `Build the
Console SPA…` ran 23:37:45 to 23:41:37 and failed, and the only failure
annotation is "Process completed with exit code 2" (the assert script's
inconclusive exit, "Neither spec appears in the built console"). The job
log itself could not be read from this container because the log blob
host is refused by the egress policy.
- **Fixture replay of the #20695 shape** (scratch fixture: a root entry
whose first injected-only description moves to a new `./migrations`
entry the console does not import; the assert and probe scripts as they
stood at `9c8f113c~1`):
- leg 0: the pre-move build stamps the dist. Exit 0; witness "A public
export added or removed by one release."
- leg 1, **cache hit** on the post-move head: `node
scripts/check-console-injection.mjs --dist dist-pre --spec tree-post
--require-stamp` prints "✓ @objectstack/spec: the dist carries this
tree's copy, and not the published one." with **exit 0**. The hit is
green without judging the change.
- leg 2, **cache miss** on the same head: the rebuild prints "✗ Neither
spec appears in the built console" with **exit 2**. This is the queue's
red.
- **How often the cache is cold anyway.** On `merge_group`, 32 of the 38
finished `Console Pin Gate` runs from 2026-09-30T13:46Z to 20:23Z
rebuilt, and 6 hit. On the hourly `schedule`, 25 of 30 runs from
2026-09-24 to 09-27 rebuilt. So widening the filter alone would have
judged this shape most of the time, but not every time. Moving the key
makes the rebuild certain for such a head.
## Which spec paths (H2, measured)
What the console build and the probe derivation read from
`packages/spec`:
- `package.json` `exports`: objectui's `OBJECTSTACK_SPEC_DIST` hook
derives one alias per exports entry and refuses a missing target
(`apps/console/vite.config.ts`,
`scripts/vite-objectstack-spec-dist.ts`), and `readSpecBlob` in
`scripts/console-spec-probes.mjs` resolves every entry.
- `tsup.config.ts`: its `entries` list decides which built file each
exports entry points at, and `build-console.sh` builds the spec through
it.
- `dist/index.mjs` and `json-schema/openapi.json` are build sentinels.
They are generated, not authored.
Over the first-parent commits on `main` from 2026-08-31 to `9905e61ca2`
(3219 commits, window proven by `scripts/pm/git-history.mjs`), 4 commits
moved the entry layout: `fbec216e2` (#20695), `c23cfb346`, `9165d5cd4`,
`776d64cd3`. Every one of them touched both `package.json` and
`tsup.config.ts`. `src/index.ts` changed in 5 other commits, all of them
content (a new metadata kind, a cycle fix, schema edits, citation
re-anchoring), so it is not in the list.
`browser-reachable-entries.json` (touched by #20695) is read only by a
lint gate, not by the console build, so it is not in the list either.
The triage control holds: no `console` entry reaches
`packages/spec/src`.
## Where the pins live (H3)
Before this PR, nothing asserted the `console` list. The only readers
were `dispatch-gates.mjs` fixtures, and `check-expected-skips.mjs`,
which reads outputs and `if:` but never paths. The pins go in
`check-ci-filter-parity.mjs`, the gate that already reads and pins the
filter job's lists, through the same YAML reader. Its battery (8) pins
the following:
- A head touching only `packages/spec/package.json` selects the job and
moves the key. The same holds for `tsup.config.ts`.
- The control: a head touching only `packages/spec/src/ui/view.zod.ts`
(a tracked file) does not select the job.
- Each way the filter and key can drift turns red on a synthetic tree: a
filter entry the key does not hash, a hashed path the filter does not
name, a pattern entry, a split key, a stale guard and a hashed guard.
There are four refusals, and the report path turns red over the
checked-in ci.yml with one hashed path dropped from the filter.
## A bounded in-place fix, declared
The two probe scripts were already `console` entries but not in the key,
so a head that changed the derivation could also get a green hit without
the new derivation ever running. The new rule, applied to the base
`ci.yml`, turns red on exactly those two (leg B below). It is the same
defect class and the same parameter, a mechanical edit to the same file,
and it adds no new verification surface, so it is fixed here. Cost: 1
commit in the 30-day window touched either script.
## Cost
- Selection of the job on a pushed head: 37 → 53 of the 3219 commits.
The key moves on 13 → 32 of them.
- The Version Packages PR bumps `packages/spec/package.json`, so its
runs now select the job (93 CI runs on that branch in the same 30 days).
The first push of each version rebuilds; later pushes with the same file
content hit that PR's own entry.
- The queue already runs this job on every `merge_group` build and
rebuilds most of the time (above), so the queue's cost barely moves.
## Reverse verification (from the committed state, `1bd002357`)
- Leg A, **the hazard itself**: filter widened, key not moved. `node
scripts/ablation-replace.mjs --file .github/workflows/ci.yml --anchor
NEW_HASHFILES_ARGS --replacement OLD_HASHFILES_ARGS --expect 3 -- node
scripts/check-ci-filter-parity.mjs` reported anchor 3 → 0 and blob
`24aac400cf2d` → `14c292d64529`. The gate printed "FAIL: ci.yml's
`console` filter and the console dist key are out of step", naming the
four unhashed entries, and exited 1. The restore brought the blob back
to `24aac400cf2d`, equal to HEAD, and `git diff HEAD` was empty.
- Leg B, the new rule over the base `ci.yml` (`bee75cebe6`): exit 1,
naming exactly `scripts/console-spec-probes.mjs` and
`scripts/assert-console-spec-injection.mjs`. That is the in-place fix
above.
- Direction observed: red, as expected.
## Gates
Derived from this diff with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` at `1bd002357` (57
commands). Exit codes and verdict lines are in the report on #20765.
On-point results:
- `node scripts/check-ci-filter-parity.mjs`: exit 0, "OK: all 9
`console` entr(ies) are literal paths; 6 are build inputs the console
dist key hashes and 3 are declared guards; the key is spelled one way in
all 3 place(s)…"
- `node scripts/check-ci-filter-parity.mjs --self-test`: exit 0, 68
assertions.
- `pnpm check:pm-expected-skips`, `pnpm check:console-injection`, `pnpm
check:console-sha`, `pnpm check:workflow-status-functions`, `pnpm
check:workflow-step-name-quoting`, `node
scripts/check-self-test-workflow-commands.mjs` (both legs) and `pnpm
check:nul-bytes`: all exit 0.
- NOT MEASURED, prerequisite not met: `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:sourcemap-no-sources-content` need built package `dist/` trees.
This diff touches no package source; CI builds before them.
- ESLint, narrowed and proven: the population is `eslint.config.mjs`'s
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` block, which includes both edited
`.mjs` files. `--format json` reports 2 files, 0 errors and 0 warnings.
The config enables no type-aware linting, so this diff cannot move an
untouched file's verdict, and `ci.yml` is not an ESLint input.
- `Console Pin Gate` runs on this PR's own head, because `ci.yml` and
the key's inputs moved. Its verdict is read from CI.
- Patch round at `f57066dd5` (comment and fallback text in
`check-console-injection.mjs` and `release.yml`, one `ci.yml` comment):
`dispatch-gates.mjs --commands` names the same 57 commands; reconciled
with `--ran` (53 run, 4 NOT MEASURED as above). `node
scripts/check-console-injection.mjs --self-test` exit 0 (44 assertions);
both `remedy()` branches exercised on a scratch dist.
## release.yml's key (seat ruling 5920200169, Q1 = B)
`release.yml` keeps its narrower key, and only its comment changes. The
two workflows no longer share dist entries. 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.
## Acceptance notes
- The prose this PR made stale in `scripts/check-console-injection.mjs`
and `release.yml` is corrected in the patch round (`f57066dd5`). Neither
the header nor the remedy fallback restates the key any more, so
`check-ci-filter-parity.mjs`'s hold on ci.yml's spellings is the only
copy that matters.
- Observation, not filed (a read-only inference with no measured reach):
`build-console.sh` copies the tracked `sdui.manifest.json` into the
dist, and that file is in neither the key nor the list. `a093ce3e2`
changed it without a pin move, so a cached dist would carry the older
copy until the pin moves. Carrier: none.
- #20764 (the seat protocol watching non-required queue checks) and
making this job a required context are out of scope here, as triage
ruled.
Body edited by the `domain:spec` seat 2 PM
(`session_017VaLJnYwhPsanVCe9dMCJU`) after the patch round, from the
dev's wording in its report on #20765.
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 013f97d commit 3ad65b0
5 files changed
Lines changed: 478 additions & 70 deletions
File tree
- .github/workflows
- scripts
- pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
183 | 183 | | |
184 | 184 | | |
185 | 185 | | |
186 | | - | |
187 | | - | |
188 | | - | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
189 | 209 | | |
190 | 210 | | |
191 | 211 | | |
192 | 212 | | |
193 | 213 | | |
194 | 214 | | |
195 | 215 | | |
| 216 | + | |
| 217 | + | |
196 | 218 | | |
197 | 219 | | |
198 | 220 | | |
| |||
2307 | 2329 | | |
2308 | 2330 | | |
2309 | 2331 | | |
2310 | | - | |
2311 | | - | |
2312 | | - | |
2313 | | - | |
| 2332 | + | |
| 2333 | + | |
| 2334 | + | |
| 2335 | + | |
| 2336 | + | |
| 2337 | + | |
2314 | 2338 | | |
2315 | 2339 | | |
2316 | 2340 | | |
| |||
2402 | 2426 | | |
2403 | 2427 | | |
2404 | 2428 | | |
2405 | | - | |
2406 | | - | |
2407 | | - | |
2408 | | - | |
| 2429 | + | |
| 2430 | + | |
| 2431 | + | |
| 2432 | + | |
| 2433 | + | |
| 2434 | + | |
| 2435 | + | |
| 2436 | + | |
| 2437 | + | |
| 2438 | + | |
| 2439 | + | |
| 2440 | + | |
| 2441 | + | |
| 2442 | + | |
2409 | 2443 | | |
2410 | 2444 | | |
2411 | 2445 | | |
| |||
2414 | 2448 | | |
2415 | 2449 | | |
2416 | 2450 | | |
2417 | | - | |
| 2451 | + | |
2418 | 2452 | | |
2419 | 2453 | | |
2420 | 2454 | | |
2421 | 2455 | | |
2422 | | - | |
| 2456 | + | |
2423 | 2457 | | |
2424 | 2458 | | |
2425 | 2459 | | |
| |||
2458 | 2492 | | |
2459 | 2493 | | |
2460 | 2494 | | |
2461 | | - | |
2462 | | - | |
2463 | | - | |
2464 | | - | |
| 2495 | + | |
| 2496 | + | |
| 2497 | + | |
| 2498 | + | |
| 2499 | + | |
2465 | 2500 | | |
2466 | 2501 | | |
2467 | 2502 | | |
| |||
2482 | 2517 | | |
2483 | 2518 | | |
2484 | 2519 | | |
2485 | | - | |
| 2520 | + | |
2486 | 2521 | | |
2487 | 2522 | | |
2488 | 2523 | | |
| |||
2495 | 2530 | | |
2496 | 2531 | | |
2497 | 2532 | | |
2498 | | - | |
| 2533 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1378 | 1378 | | |
1379 | 1379 | | |
1380 | 1380 | | |
1381 | | - | |
1382 | | - | |
1383 | | - | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
1384 | 1388 | | |
1385 | 1389 | | |
1386 | 1390 | | |
| |||
0 commit comments