Skip to content

Commit 8fcdf52

Browse files
fix(cli): the Console drift remedy names pnpm console:build, not the retired objectui:build (#26063)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DBj9PuBdWNyyNdW8Wq2GYFd)_ Part of #26035 (slice 3 only: the CLI stamp-drift remedy). Slice 2, the ungoverned docs, runs on its own branch; slice 1, the governed text, waits for the maintainer. Clause-②: no Metadata-protocol: none ## What changes The Console drift warning (`os serve`), the `os dev` drift refusal and the `503` body at `/_console/` that the refusal mounts told the reader to run `pnpm objectui:build`, and the refusal also mentioned `pnpm objectui:refresh`. Both scripts retired with the objectui pin in #25745. All three texts now name `pnpm console:build`, the root script that builds `@objectstack/console` from `ui/apps/console`. The refresh line is dropped. The SHAs, `OS_ALLOW_CONSOLE_DRIFT` and the status codes are unchanged. - `packages/cli/src/utils/console.ts`: the remedy text in `formatConsoleShaDriftWarning` and `describeConsoleShaDrift`; the docblocks of the drift guard, `formatConsoleShaDriftRefusal` and `decideConsoleMount`, and the header's resolution note, stop describing the pin, `scripts/build-console.sh` and `pnpm check:console-sha` as current. - `packages/cli/src/utils/console.sha-drift.test.ts` and `packages/cli/test/console-resolve.test.ts`: the pinned remedy strings move with the code in the same commit. The drift-message case now also asserts that neither message names any `objectui:(build|refresh|bump|clean)` script. No assertion is removed. - `packages/cli/src/commands/serve.ts` (comment only) and `packages/cli/src/console.ts` (the L66 reading record, comment only): the pin is described as retired; the dated reading keeps its sha. - `.changeset/26035-console-drift-remedy-console-build.md`: `@objectstack/cli` patch, written for the reader of the message. ## Re-measure on `main` (`39ca58787`) - **Is the drift path reachable?** Not in this repository. `findObjectuiPin` walks up from the resolved console package for a `.objectui-sha`; the repo has none since #25745. `packages/console/build-dist.sh` writes no `dist/.objectui-sha` stamp and replaces the whole dist (`rm -rf` then copy), so `detectConsoleShaDrift` returns `null` for every framework checkout built with `pnpm console:build`. A published install carries no pin either. - **Why it is kept, not removed.** The header of `console.ts` names an objectos/cloud Docker overlay that carries its own `cloud/.objectui-sha` above the console package, and the published `@objectstack/console@18.1.0` dist still ships a `dist/.objectui-sha` stamp (#25745's tarball comparison). Such a tree still reaches the guard. That overlay is **NOT MEASURED**: the `cloud` repository is not readable from this seat (the public `src/console.ts` docblock records the same gap). Removing the guard would also change the published `resolveConsolePath` options type (`onDrift`, pinned by `test/published-subpath-console.pin.test.ts`), which is beyond this slice. - **`scripts/check-console-sha.mjs` / `check:console-sha`.** Already deleted by #25745, with the root alias and the `pnpm dev*` wiring; no workflow or dev chain runs it. Nothing to change. - **Known remedy limit.** In an overlay tree that has no `console:build` script, the named remedy is the framework's script, not that tree's. The dispatch order set the remedy to `pnpm console:build`; the tree that would see it is the unmeasured one above. ## File surface The claim's surface plus `packages/cli/test/console-resolve.test.ts` (one line), added to the claim by the seat at 10:02Z: it pins the same warning string this PR re-points. ## Verification All on head `9aefb25e8`, local, under the verify lock. - Closure: `pnpm turbo run build --filter='@objectstack/cli^...' --concurrency=2` → VERDICT command-exit 0 (60/60 tasks). - The two touched files: `pnpm --filter @objectstack/cli exec vitest run --project unit src/utils/console.sha-drift.test.ts test/console-resolve.test.ts` → Test Files 2 passed, Tests 29 passed. - CLI unit layer: `pnpm --filter @objectstack/cli exec vitest run --project unit` → 331 files passed, 4825 tests passed, 29 skipped; 2 files failed only on their prerequisite (`packages/cli is not built (./dist/index.js is absent)`). After `pnpm --filter @objectstack/cli build`, those two (`test/published-subpath-console.pin.test.ts`, `test/published-subpath-hook-body.pin.test.ts`) → 2 passed, 29 tests passed. The integration layer is declared to CI (no spawn entry or integration file in the diff). - `pnpm --filter @objectstack/cli typecheck` → exit 0 (`check:test-typecheck: OK`). - Derived gates (`dispatch-gates --commands`, 71 families): 70 exit 0, `check:dual-build-cjs-loads` exit 3 = NOT MEASURED (needs every package's dist; CI owns it). The three `check:i18n*` families first exited 3 and were re-run green after the prerequisite build they print. `dispatch-gates --ran` → `71 derived famil(ies) accounted for — 70 run, 1 NOT-MEASURED`. - `check:commit-card-trailers --range origin/main..HEAD` ✓; `check-nul-bytes` exit 0. - No ablation: the diff changes message text that the tests pin by string, not a tested behaviour. ## Acceptance notes - Slice acceptance: after this PR, `git grep -nE "objectui:(build|refresh|bump|clean)" -- packages/cli ':!*CHANGELOG.md'` finds nothing. - Outside this slice and not named in the card's slicing: `scripts/check-dev-prereqs.mjs`, `scripts/check-pnpm-flag-spellings.mjs`, `scripts/check-sdui-manifest.mjs`, `scripts/gen-sdui-manifest-node.mjs`, `packages/client/src/envelope-caller-census.test.ts` and `packages/qa/dogfood/test/print-service-render.dogfood.test.ts` still name an `objectui:*` script (comments, or the pinned-mode branch of the SDUI manifest tools). Carrier: none; noted here for the seat. #26035 remains open for slices 1, 2 and 4. --- _Generated by [Claude Code](https://claude.ai/code/session_01CBGRCbYq9F1ceLwZ2YCbWE)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01CBGRCbYq9F1ceLwZ2YCbWE Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0a69482 commit 8fcdf52

6 files changed

Lines changed: 65 additions & 47 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): the Console drift warning and refusal name `pnpm console:build` instead of the retired `pnpm objectui:build`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
When the Console build being served was stamped from a different objectui commit than the `.objectui-sha` pin above it, `os serve` printed a warning and `os dev` refused to mount the Console (and answered `503` at `/_console/`). Both told you to run `pnpm objectui:build`, and the refusal also mentioned `pnpm objectui:refresh`. Neither script exists any more: the objectui pin and its scripts were retired when the Console moved into this workspace, and the Console is now built with `pnpm console:build`.
11+
12+
- **Now.** The warning ends with `rebuild it with 'pnpm console:build'.`, and the refusal (terminal and `503` body) says `Rebuild the console:` followed by `pnpm console:build`. The `objectui:refresh` line is gone. The SHAs, the `OS_ALLOW_CONSOLE_DRIFT=1` escape hatch and the status codes are unchanged.
13+
- **Who sees it.** Only a tree that still carries its own `.objectui-sha` above a stamped Console build. The framework repository carries no pin, and `pnpm console:build` writes no stamp, so a framework checkout never reaches this message. A published install carries no pin either.

‎packages/cli/src/commands/serve.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5285,12 +5285,13 @@ export default class Serve extends Command {
52855285

52865286
// ── Console portal ──────────────────────────────────────────
52875287
// The opinionated, fork-ready runtime console (`@objectstack/console`,
5288-
// the build of objectstack-ai/objectui's console app this framework
5289-
// vendors at its `.objectui-sha` pin) mounts under `/_console/`. When
5288+
// the build of this workspace's `ui/apps/console` that
5289+
// `pnpm console:build` vendors) mounts under `/_console/`. When
52905290
// present, it owns the root `/` redirect (preferred default UI). It is
52915291
// optional — we only mount it when the package resolves and a
52925292
// pre-built `dist/` is present, and — in dev — only when that build
5293-
// matches the repo's objectui pin (#7752). Every other boot that wants
5293+
// matches any objectui pin found above it (#7752; the framework repo
5294+
// itself carries none since #25745). Every other boot that wants
52945295
// a Console gets a responder on those same routes instead, so
52955296
// `/_console/` answers `503` naming the remedy its boot message prints
52965297
// rather than a bare 404 (#22420): the drift refusal; a package with no

‎packages/cli/src/console.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@
6363
* answers 1,640 hits from the same index in the same session, so objectui
6464
* is genuinely indexed and its zero is a measurement rather than a silence.
6565
* 5. The PINNED sibling checkout — the reading AGENTS.md prescribes before a
66-
* removal ships, taken at `.objectui-sha` (`a472b07167a3`) rather than at
67-
* whatever that checkout's HEAD happens to be. The specifier
66+
* removal ships, taken at the objectui pin this repository then committed
67+
* (`.objectui-sha`, `a472b07167a3`; the pin retired with #25745) rather
68+
* than at whatever that checkout's HEAD happens to be. The specifier
6869
* `cli/console` does not occur in objectui at that sha, and none of the
6970
* ten occurs as an identifier, except `CONSOLE_PATH` twice — both inside
7071
* comment prose in one browser test, neither an import. Its control: 545
@@ -73,7 +74,7 @@
7374
* earlier draft of this docblock justified the exclusion on "the one
7475
* sibling checkout reachable from the implementing container", and that
7576
* was retired for being unverifiable from anywhere else. A sha this
76-
* repository itself pins is re-runnable by anyone who checks objectui out.
77+
* repository itself pinned is re-runnable by anyone who checks objectui out.
7778
*
7879
* ⚠️ `cloud` is NOT MEASURED, which is a different thing from zero. The same
7980
* control run against it — `repo:objectstack-ai/cloud objectstack` — answers 0

‎packages/cli/src/utils/console.sha-drift.test.ts‎

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
/**
44
* objectui-SHA drift guard — the seat on the boot path itself (#7752).
55
*
6-
* `pnpm check:console-sha` guards the root `pnpm dev` / `dev:showcase` /
7-
* `dev:crm` / `dev:todo` scripts. Every other way to boot reaches the server
8-
* without passing it: `objectstack dev` run inside an example dir, an
6+
* `pnpm check:console-sha` guarded the root `pnpm dev` / `dev:showcase` /
7+
* `dev:crm` / `dev:todo` scripts until it retired with the framework's objectui
8+
* pin (#25745). Every other way to boot reached the server without passing it: `objectstack dev` run inside an example dir, an
99
* example's own `dev` script (`objectstack dev --seed-admin`), a
1010
* `.claude/launch.json` config driving `pnpm exec objectstack dev`. A QA sweep
1111
* booted that way and spent the run measuring a console two days behind the
@@ -97,7 +97,7 @@ describe('decideConsoleMount', () => {
9797

9898
it('keeps "no dist at all" distinct from "refused for drift"', () => {
9999
// Both leave the Console unmounted, but they need different messages —
100-
// "run objectui:build" vs the existing "console dist not found" hint.
100+
// "rebuild, the dist is not the pin" vs the "console dist not found" hint.
101101
expect(decideConsoleMount({ hasDist: false, drift: null, isDev: true, env: {} })).toEqual({
102102
mount: false,
103103
refusedForDrift: false,
@@ -106,17 +106,16 @@ describe('decideConsoleMount', () => {
106106
});
107107

108108
describe('drift messages', () => {
109-
it('names the rebuild, never the pin bump — refresh would move the pin instead', () => {
109+
it('names the rebuild script that exists, never a retired pin script', () => {
110110
for (const message of [
111111
formatConsoleShaDriftWarning(drift),
112112
formatConsoleShaDriftRefusal(drift),
113113
]) {
114-
expect(message).toContain('pnpm objectui:build');
114+
expect(message).toContain('pnpm console:build');
115+
// The `objectui:*` pin scripts retired with the pin (#25745); a remedy
116+
// naming one sends the reader to a script `pnpm` cannot find.
117+
expect(message).not.toMatch(/objectui:(build|refresh|bump|clean)/);
115118
}
116-
// `objectui:refresh` appears only as the explicitly-labelled wrong turn.
117-
expect(formatConsoleShaDriftRefusal(drift)).toContain(
118-
"(Use 'pnpm objectui:refresh' only when you intend to move the pin",
119-
);
120119
});
121120

122121
it('shows both SHAs and the escape hatch, so the boot log is self-explaining', () => {
@@ -189,14 +188,14 @@ describe('what a dev server that refused a drifted Console answers', () => {
189188
request = await serve(createConsoleShaDriftRefusalPlugin(drift));
190189
});
191190

192-
it.each(CONSOLE_REQUESTS)('GET %s answers 503 naming `pnpm objectui:build`, not the SPA', async (p) => {
191+
it.each(CONSOLE_REQUESTS)('GET %s answers 503 naming `pnpm console:build`, not the SPA', async (p) => {
193192
const res = await request(p);
194193
expect(res.status).toBe(503);
195194
expect(res.headers.get('content-type')).toMatch(/^text\/plain/);
196195
expect(res.headers.get('cache-control')).toBe('no-store');
197196
const body = await res.text();
198197
expect(body.split('\n')[0]).toBe(HEADLINE);
199-
expect(body).toContain('pnpm objectui:build');
198+
expect(body).toContain('pnpm console:build');
200199
expect(body).toContain(PIN.slice(0, 12));
201200
expect(body).toContain(STAMP.slice(0, 12));
202201
expect(body).toContain(DRIFT_OVERRIDE_ENV);

‎packages/cli/src/utils/console.ts‎

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
* Resolution strategy, in priority order:
2222
*
2323
* 1. `@objectstack/console` — the framework-vendored, version-locked
24-
* build. Shipped as a dist-only npm package frozen at the objectui
25-
* SHA recorded in `<framework>/.objectui-sha`. This is what a
24+
* build. Shipped as a dist-only npm package built from this
25+
* workspace's `ui/apps/console` (`pnpm console:build`). This is what a
2626
* fresh `@objectstack/cli` install gets: the CLI declares this
2727
* package as a dependency and both ship at one version from the
2828
* Changesets `fixed` group, so no app installs it by hand. Cloud /
@@ -408,20 +408,26 @@ export function formatConsoleDistMissingWarning(consolePath: string | null): str
408408

409409
/**
410410
* The vendored console `dist/` is a locally-built, gitignored artifact
411-
* rebuilt only by `scripts/build-console.sh` (pnpm objectui:build /
412-
* objectui:refresh / release) — never by `turbo run build`. When a
413-
* developer pulls a branch that bumps the framework's `.objectui-sha` pin
414-
* without re-running `pnpm objectui:build`, the dist stays frozen at the
415-
* previous objectui commit and would be served silently. The npm-major
416-
* version guard above can't catch this: the objectui SHA moves underneath a
417-
* single `@objectstack/console` version.
411+
* rebuilt only by `pnpm console:build` (and release) — never by
412+
* `turbo run build`. The guard below was built for the two-repo era: the
413+
* framework pinned objectui by SHA in a committed `.objectui-sha`, the old
414+
* build script stamped the built SHA into `dist/.objectui-sha`, and a pull
415+
* that moved the pin without a rebuild left the dist frozen at the previous
416+
* objectui commit, served silently. The npm-major version guard above can't
417+
* catch that: the objectui SHA moves underneath a single
418+
* `@objectstack/console` version.
418419
*
419-
* build-console.sh stamps the built SHA into `dist/.objectui-sha`. Here we
420-
* compare it against the framework's committed `.objectui-sha` pin, located
421-
* by walking up from the resolved console package. This is inherently a
422-
* monorepo/dev concern: a published install ships no `.objectui-sha` pin
423-
* (so the stamped dist is authoritative), and the sibling-repo dev fallback
424-
* writes no stamp — both cases are skipped silently.
420+
* Since #25745 the framework repo carries no pin (the Console builds from
421+
* `ui/apps/console` in this workspace) and `packages/console/build-dist.sh`
422+
* writes no stamp and replaces the whole dist, so this guard can no longer
423+
* fire there. It is kept because a tree that still carries its own
424+
* `.objectui-sha` above the console package (the objectos overlay named in
425+
* this file's header) with a stamped dist reaches it, and that tree is not
426+
* readable from this repository. Here we compare the stamp against the
427+
* nearest `.objectui-sha`, found by walking up from the resolved console
428+
* package. A published install ships no pin (so the stamped dist is
429+
* authoritative), and the sibling-repo dev fallback writes no stamp — both
430+
* cases are skipped silently.
425431
*/
426432
function findObjectuiPin(startDir: string): { pin: string; file: string } | null {
427433
let dir = startDir;
@@ -469,7 +475,7 @@ export function detectConsoleShaDrift(consoleDir: string): ConsoleShaDrift | nul
469475
} catch {
470476
return null; // unreadable stamp — fail open
471477
}
472-
// Unstamped dist: can't prove drift; `pnpm check:console-sha` surfaces it.
478+
// Unstamped dist (every `pnpm console:build` since #25745): can't prove drift.
473479
if (!stamp || stamp === found.pin) return null;
474480

475481
return { stamp, pin: found.pin, pinFile: found.file };
@@ -481,7 +487,7 @@ export function formatConsoleShaDriftWarning(drift: ConsoleShaDrift): string {
481487
` ⚠ Console version drift: serving @objectstack/console built from objectui@${drift.stamp.slice(0, 12)}, ` +
482488
`but ${drift.pinFile} pins objectui@${drift.pin.slice(0, 12)}. ` +
483489
`packages/console/dist is a gitignored local build that 'turbo run build' does not refresh — ` +
484-
`rebuild it with 'pnpm objectui:build'.`
490+
`rebuild it with 'pnpm console:build'.`
485491
);
486492
}
487493

@@ -506,18 +512,16 @@ function describeConsoleShaDrift(drift: ConsoleShaDrift, hostPaths: boolean): st
506512
` packages/console/dist is a gitignored local build that 'turbo run build' does NOT refresh,\n` +
507513
` so this server would serve a Console SPA the repo no longer pins — and anything you\n` +
508514
` observed in it would describe a different objectui commit.\n\n` +
509-
` Rebuild the console at the pinned SHA:\n\n` +
510-
` pnpm objectui:build\n\n` +
511-
` (Use 'pnpm objectui:refresh' only when you intend to move the pin to your local ../objectui HEAD.)\n` +
515+
` Rebuild the console:\n\n` +
516+
` pnpm console:build\n\n` +
512517
` To boot anyway with the stale bundle: ${DRIFT_OVERRIDE_ENV}=1 — the API still serves either way.\n`
513518
);
514519
}
515520

516521
/**
517-
* The refusal block — `os dev` declines to mount a drifted console. Mirrors
518-
* the remediation of `scripts/check-console-sha.mjs` verbatim (rebuild at the
519-
* *pinned* SHA with `pnpm objectui:build`; `objectui:refresh` would re-bump
520-
* the pin to the local ../objectui HEAD, which is the opposite of the fix).
522+
* The refusal block — `os dev` declines to mount a drifted console. The remedy
523+
* is the rebuild, `pnpm console:build`, the same script the not-built remedy
524+
* names ({@link describeConsoleDistMissing}).
521525
*/
522526
export function formatConsoleShaDriftRefusal(drift: ConsoleShaDrift): string {
523527
return `\n ✗ Console version drift — refusing to serve /_console in dev.\n\n${describeConsoleShaDrift(drift, true)}`;
@@ -534,10 +538,10 @@ function driftOverridden(env: NodeJS.ProcessEnv): boolean {
534538
/**
535539
* Decide whether the Console SPA may mount, given what resolution found.
536540
*
537-
* The drift guard has two seats and this is the second one. The first —
538-
* `pnpm check:console-sha` — is wired into the root `pnpm dev` /
539-
* `dev:showcase` / `dev:crm` / `dev:todo` scripts, and every boot that does
540-
* not go through those scripts misses it: `objectstack dev` run directly in
541+
* The drift guard had two seats and this is the one that remains. The other —
542+
* `pnpm check:console-sha`, wired into the root `pnpm dev` scripts — retired
543+
* with the framework's pin (#25745). Even while it ran, every boot that did
544+
* not go through those scripts missed it: `objectstack dev` run directly in
541545
* an example dir, an example's own `dev` script, a `.claude/launch.json`
542546
* config, `pnpm exec objectstack dev`. That is how a QA sweep came to measure
543547
* a console two days behind the pin (#7752) — the pin had moved, the local

‎packages/cli/test/console-resolve.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ describe('detectConsoleShaDrift', () => {
196196
expect(warnings).toHaveLength(1);
197197
expect(warnings[0]).toContain(SHA_A.slice(0, 12));
198198
expect(warnings[0]).toContain(SHA_B.slice(0, 12));
199-
expect(warnings[0]).toContain('objectui:build');
199+
expect(warnings[0]).toContain('pnpm console:build');
200200
});
201201

202202
it('is silent when the dist stamp matches the pin', () => {

0 commit comments

Comments
 (0)