Skip to content

Commit 5eb5400

Browse files
ci(spec-shape-gate): install objectui through its pinned-build directories, packed from this commit (#24469)
Fixes #24424 Clause-②: no ## What was red Every `Spec Shape Gate` run since objectui `749ed760` (objectstack-ai/objectui#12957): step "Install objectui dependencies" ran a bare `pnpm install --frozen-lockfile` in objectui, whose root `package.json` now overrides every `@objectstack/*` package to `file:node_modules/.cache/objectstack/NAME`, a directory only objectui's `scripts/objectstack-pin.mjs build` fills. Reproduced locally against objectui `4016370af3` (its `main` when this was measured): exit 254, `ENOENT: no such file or directory, scandir '.../objectui/node_modules/.cache/objectstack/client'`. Run `39188135125` step 14 concluded `failure` with the annotation `Process completed with exit code 254` (its raw log host answers 403 from this container, so the annotation is the reading). ## The change `.github/workflows/spec-shape-gate.yml`: 1. The objectstack-side install and build widen from the spec to the packages objectui overrides. The names come from `objectui/package.json`'s `pnpm.overrides`, the list the pin script packs, and the selector is `--filter-prod=NAME...`: runtime edges only, as the pin script's own build uses (a plain `...` filter pulls in the client's dev closure of runtime, drivers and services). If no override is found, that step exits 2. 2. A new step runs before the frozen install: objectui's `pnpm run objectstack:build` with `OBJECTSTACK_ROOT` set to this checkout. That is objectui's stand-in input: it packs every overridden package from this tree into those directories, with no lockfile change. The step calls objectui's script by name rather than by path. `check-self-test-wired`'s live corpus maps every workflow `node PATH` to a file in THIS repository, and its self-test went red on the path spelling at `bfa828074` (exit 0 on BASE). 3. The `inject` step stays (measured below). Its comment now says what it still adds. `scripts/ci/spec-shape-gate.mjs`: two summary lines now name the subject the compile reads, `@objectstack/spec` and every other `@objectstack/*` package objectui installs. The self-test's pinned substring (`type-checks against`) is unchanged. No gate was added, no step skipped, and no `continue-on-error` set. `report`'s exit-2 meaning is unchanged. ## The dispatch's hypotheses, measured Readings at BASE `725a5b131`, objectui `4016370af3`, pnpm 10.31.0, node 22.22.0. 1. **The red is the bare frozen install failing with ENOENT on the cache directory.** Confirmed, as above. 2. **`OBJECTSTACK_ROOT=TREE` packs every package and leaves the lockfile unchanged.** Confirmed. Seven packages were packed from `725a5b1311ec` in 15 s and the pin script's lockfile check passed. `pnpm install --frozen-lockfile` then exited 0 in 7 s, and objectui's `git status` stayed clean. `pnpm run setup` would pass the variable through too. But `setup` is also THIS repository's script name, so the self-test-wired corpus would expand it against our `package.json`. The build and the install are therefore two named steps. 3. **`inject` becomes redundant.** On BASE it changes nothing: every declaration file is identical, it made 0 substitutions, and it verified 32 consumers. The only differences between npm pack and pnpm pack output are `package.json` (npm keeps the `prepare` script) and one nested `src/migrations/entries/README.md`. It is not redundant, though: it is the only range check of the spec's declared dependencies. A tarball declaring `zod: ^4.7.0` against the store's `zod@4.6.7` made `inject` exit 2 with nothing written, while the pin script's check passed the same drift. It stays. 4. **A manifest change since objectui's pin makes the frozen install refuse.** BASE has no drift. Across the seven packages, 0 fields changed in `dependencies`, `optionalDependencies`, `peerDependencies`, `peerDependenciesMeta` or `engines` over the 157 commits from objectui's pin `2c9bf9430a` to BASE. The mechanism itself is **falsified**. I mutated the packed spec manifest four ways: a new dependency name, peer `ai: ^8.0.0`, `engines.node` at 24 or above (under objectui's `engine-strict=true`), and `zod: ^4.7.0`. objectui's frozen install exited 0 every time and linked the drifted manifest. Only the pin script refuses, and only for the name class. **How the gate reads the name class.** The pin step exits 1 with the pin script's own `objectstack-pin` error annotation, which names the package and the dependency. Install and compile are skipped, and `report` exits 2: no reading, never a pass. That is a red with no compile verdict, and the remedy is objectui's: move its `.objectstack-sha` with `scripts/bump-objectstack.sh` and commit the lockfile. It is neither a shape break nor a registered retirement, because the registry matches diagnostics and there are none. I propose no new registry kind: a "known" reading of a compile that never ran is a skip, which this card's ruling forbids. Nothing refuses peer, engines or range drift in the other six packages. In that case the compile reads objectui's lockfile-resolved dependencies, which is what objectui's own frozen install does with the same build. ## Local end-to-end I extracted the edited workflow's own `run:` texts from the YAML and replayed them under `bash -eo pipefail`. - objectstack side, in a detached worktree at BASE after the filtered install: step 9 (install) exit 0 in 3.6 s; step 10 (build the seven) exit 0 in 125 s. - objectui side, in a throwaway clone at `4016370af3` with a fresh `node_modules`: step 13 (`pnpm run objectstack:build`) exit 0 in 14.5 s; step 14 (frozen install) exit 0 in 7.9 s; `inject` exit 0. - Compile: `TURBO_FORCE=true pnpm type-check --continue --concurrency=2` exited 0, with 81 of 81 tasks successful and 0 cached, in 16m16s on a shared box. `report --status 0` on that log exited 0 ("type-checks against"). Two registered retirements matched nothing and are listed as such. - Added CI time, measured locally on a shared box: 27 s for the six non-spec builds (spec already built), about 15 s for the pin pack, and a few seconds for the wider filtered install on a warm store. That is under a minute on a job sized at about 13 minutes with a 45-minute ceiling. ## Gates Run at `eb080715a`, the head of this PR. The 51 commands `node scripts/pm/dispatch-gates.mjs --commands` derives for this diff all exited 0, each exit code captured before any pipe. `--ran` reconciliation: "51 derived, 51 run, 0 NOT-MEASURED, 0 UNRUN". Selected verdict lines: - `pnpm check:spec-shape-gate`: `spec-shape-gate --self-test: 60/60 passed across 7 batteries` - `node scripts/check-self-test-wired.mjs --self-test`: exit 0 (red at `bfa828074` on the path spelling, see above) - `pnpm check:pnpm-filter-targets`, `pnpm check:workflow-step-name-quoting`, `pnpm check:nul-bytes`, `pnpm check:entry-guard`, `pnpm check:bash32-floor`: exit 0 - Outside the derived set: `pnpm check:pnpm-flag-spellings` exit 0. The gate's own `pull_request` run on this PR (path-filtered to this workflow and the script) is the CI end-to-end reading. ## Acceptance notes - `inject`'s tarball comes from `npm pack`; objectui's pinned build comes from `pnpm pack`. They differ in `package.json` (npm keeps `prepare`) and one nested README, and tsc reads neither. Observation only. - `check-self-test-wired`'s live corpus does not read a step's `working-directory`, so a `node PATH` run inside the objectui checkout reads as a path in this repository. This PR spells objectui's entry point by script name instead. Observation only, no carrier. - Triage reading 6259975511 on the card: #24168 merged as `725a5b1311` (this PR's BASE), and its Release run refused at the full-battery check because the newest schedule run at or before that version commit is red. ## 维护者速读(草稿) **改了什么** Spec Shape Gate 是每小时检查 objectui 能否在本仓当前提交上编译通过的门禁,这次改的是它安装 objectui 的方式:先在本仓构建 objectui 用到的全部 7 个 `@objectstack/*` 包,再调用 objectui 自己的 `objectstack:build` 脚本把它们打包进 objectui 期望的目录,最后照常冻结安装。以前只注入 spec 一个包,现在 objectui 编译时读到的 7 个包全部来自本次提交。 **为什么改** objectui 改为按提交消费 `@objectstack/*` 之后,门禁原来的裸安装每次都失败(找不到缓存目录),门禁一直是红的,而发版门禁读这条结果。triage 读数 6259975511:next.4 已无法挽救,可发版的路径是本卡落地、新 main 上出现一次绿色的定时 Spec Shape Gate 运行,然后发 next.5。 **风险与代价(含回滚)** 每次运行大约多一分钟(多构建 6 个小包,外加一次打包)。如果本仓某次提交给这 7 个包新增或删除了运行时依赖,objectui 的打包脚本会拒绝,门禁变红(读作"无读数",不算通过),要等 objectui 升级它的 pin 并提交 lockfile 才能转绿。回滚:revert 本 PR,门禁回到现在的红色状态。 **席位意见** **你要做的** 看一下本 PR 自己的 `Spec Shape Gate` pull_request 运行结论;同意的话批准合并(工作流文件属于 Tier H,需要你亲手批准)。 --- _Generated by [Claude Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fb0d85b commit 5eb5400

2 files changed

Lines changed: 46 additions & 12 deletions

File tree

‎.github/workflows/spec-shape-gate.yml‎

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
name: Spec Shape Gate
22

33
# Compile the downstream consumer -- objectstack-ai/objectui at `main` -- against
4-
# `@objectstack/spec` built from THIS commit, and name the objectui file, the
5-
# objectstack commit and the owning lane when it does not compile.
4+
# `@objectstack/spec` (and every other `@objectstack/*` package it installs)
5+
# built from THIS commit, and name the objectui file, the objectstack commit and
6+
# the owning lane when it does not compile.
67
#
78
# ## The ruling this executes
89
#
@@ -205,16 +206,30 @@ jobs:
205206
- name: Self-test the gate script
206207
run: pnpm check:spec-shape-gate
207208

208-
# ── Build the spec from THIS commit, and PACK it ──────────────────────
209-
# `--filter @objectstack/spec...` installs the spec and its dependencies
210-
# only; `inject` later reads the dependencies this install resolved
209+
# ── Build what objectui installs, from THIS commit, and PACK the spec ──
210+
# objectui consumes every `@objectstack/*` package BY COMMIT: its root
211+
# `package.json` overrides each to `file:node_modules/.cache/objectstack/
212+
# <name>`, which its `scripts/objectstack-pin.mjs build` fills (below, with
213+
# OBJECTSTACK_ROOT naming this checkout). The names are read from those
214+
# overrides, the list the pin script packs; `--filter-prod` keeps the
215+
# closure to runtime edges as that script's own build does (`...` alone
216+
# would pull in the client's dev closure). The install brings the root's
217+
# toolchain too, and `inject` later reads the dependencies it resolved
211218
# (`packages/spec/node_modules`) to re-point a store sibling the packed
212219
# manifest's range refuses.
213-
- name: Install the spec's dependency closure
214-
run: pnpm install --frozen-lockfile --filter @objectstack/spec...
220+
- name: Install the closure of the @objectstack/* packages objectui installs by commit
221+
run: |
222+
set -euo pipefail
223+
filters=$(node -e '
224+
const m = JSON.parse(require("fs").readFileSync("objectui/package.json", "utf8"));
225+
const names = Object.keys(m.pnpm?.overrides ?? {}).filter((n) => n.startsWith("@objectstack/")).sort();
226+
if (names.length === 0) { console.error("objectui/package.json overrides no @objectstack/* package"); process.exit(2); }
227+
process.stdout.write(names.map((n) => `--filter-prod=${n}...`).join(" "));')
228+
echo "PINNED_FILTERS=$filters" >> "$GITHUB_ENV"
229+
pnpm install --frozen-lockfile $filters
215230
216-
- name: Build @objectstack/spec from this commit
217-
run: pnpm --filter @objectstack/spec build
231+
- name: Build them from this commit
232+
run: pnpm $PINNED_FILTERS run build
218233

219234
# What gets injected is what a RELEASE from this commit would ship -- the
220235
# `files` field's file set, not a working tree. `--ignore-scripts`
@@ -238,6 +253,22 @@ jobs:
238253
working-directory: objectui
239254
run: pnpm --version
240255

256+
# objectui's `objectstack:build` script (its pin script's `build`) packs
257+
# every package built above into the directories its overrides name;
258+
# without it the frozen install dies on ENOENT there. Called by objectui's
259+
# script NAME: a path here would read as one in this repository. It
260+
# refuses (exit 1, its own `objectstack-pin` annotation) when a packed
261+
# manifest's dependency NAMES differ from what objectui's lockfile records:
262+
# the downstream owes a pin bump with its lockfile, and the report reads the
263+
# missing compile as exit 2. A changed peer, engines or dependency range is
264+
# not refused there -- a frozen install links it and exits 0 -- and the
265+
# spec's ranges stay `inject`'s to judge.
266+
- name: Pack this commit's @objectstack/* into objectui's pinned-build directories
267+
working-directory: objectui
268+
env:
269+
OBJECTSTACK_ROOT: ${{ github.workspace }}
270+
run: pnpm run objectstack:build
271+
241272
- name: Install objectui dependencies
242273
working-directory: objectui
243274
run: pnpm install --frozen-lockfile
@@ -254,7 +285,10 @@ jobs:
254285
# re-points a store sibling the packed manifest's range refuses at the
255286
# copy this tree's spec build resolved, and proves every workspace
256287
# consumer resolves the injected copy before returning. Nothing tracked
257-
# by git is touched in either checkout.
288+
# by git is touched in either checkout. The pinned build above already
289+
# installed this commit's spec (the same declaration files); what this
290+
# step still adds is that range judgement and the consumer proof, which
291+
# the pin script does not make (it compares dependency names only).
258292
- name: Inject the source-built spec into objectui's install
259293
env:
260294
UPSTREAM_SHA: ${{ steps.subjects.outputs.upstream }}

‎scripts/ci/spec-shape-gate.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,12 +1135,12 @@ export function renderSummary({ sha, downstreamSha = null, commit = null, event
11351135
const link = `https://github.com/${UPSTREAM_REPO}/commit/${sha}`;
11361136
const out = [];
11371137
out.push(`### Spec Shape Gate -- \`${UPSTREAM_REPO}@${short}\` against \`${DOWNSTREAM_REPO}@${String(downstreamSha ?? 'main').slice(0, 12)}\``, '');
1138-
out.push(`Compiled ${DOWNSTREAM_REPO} at \`main\`${downstreamSha ? ` (\`${String(downstreamSha).slice(0, 12)}\`)` : ''} against \`${SPEC_PACKAGE_NAME}\` built and packed from [${short}](${link})${event ? ` (\`${event}\` run)` : ''}.`);
1138+
out.push(`Compiled ${DOWNSTREAM_REPO} at \`main\`${downstreamSha ? ` (\`${String(downstreamSha).slice(0, 12)}\`)` : ''} against \`${SPEC_PACKAGE_NAME}\` and every other \`@objectstack/*\` package it installs, built and packed from [${short}](${link})${event ? ` (\`${event}\` run)` : ''}.`);
11391139
if (commit) out.push('', `> ${commit}`);
11401140
out.push('');
11411141

11421142
if (status === 0 && rows.length === 0) {
1143-
out.push(`✅ ${DOWNSTREAM_REPO} type-checks against \`${SPEC_PACKAGE_NAME}\` at that commit.`);
1143+
out.push(`✅ ${DOWNSTREAM_REPO} type-checks against \`@objectstack/*\` at that commit.`);
11441144
} else if (rows.length === 0) {
11451145
out.push(
11461146
`⛔ The typecheck failed (exit ${status}) and **no diagnostic could be attributed to a file**.`,

0 commit comments

Comments
 (0)