Repository navigation
Commit d6c3791
fix(cli): a refused Console mount answers 503 naming the remedy, not a bare 404 (#22562)
Fixes #22420
Clause-②: no
A refused Console mount answers `503` naming the remedy instead of the
router's bare `404` on the same routes, as the not-built position
already does (PR #22419, `Clause-②: no`). No accepted input, key or
export changes.
## What changes
`os serve` sometimes decides not to serve the Console. On two of those
boots, the HTTP surface used to say nothing: `GET /_console/` and `GET
/` answered the router's bare `404 ENDPOINT_NOT_FOUND`, which looks the
same as a wrong URL. Both boots now mount a responder on the routes the
not-built answer (PR #22419) already uses:
- `GET /_console/*` answers `503`, `text/plain`, `no-store`. The body is
the remedy the boot message prints, rendered without absolute host
paths.
- `GET /` and `GET /_console` redirect to `/_console/`, as they do when
the Console is served.
| position | terminal | HTTP before | HTTP after |
|:--|:--|:--|:--|
| package resolves, no built `dist/` | `formatConsoleDistMissingWarning`
| `503` + redirects (PR #22419) | unchanged |
| dev, built `dist/.objectui-sha` is not the repo pin |
`formatConsoleShaDriftRefusal` (byte-identical) | bare `404` | `503`
naming `pnpm objectui:build`, both SHAs and `OS_ALLOW_CONSOLE_DRIFT=1`;
the pin file is named without its absolute path; `/` redirects |
| package does not resolve at all (`consolePath` null, Console enabled)
| nothing | bare `404` | the not-built warning's null arm (``Console
package `@objectstack/console` not found``, then the reinstall
sentence); `503` saying the Console is not installed; `/` redirects |
| `--no-ui` / `--no-console` | the operator's choice | bare `404` | bare
`404`, by design |
Files:
- `packages/cli/src/utils/console.ts`:
- `createConsoleUnavailablePlugin` (not exported) is now the ONE
route-mounting body. Before, that body was inline in
`createConsoleNotBuiltPlugin`.
- Two responders delegate to it.
`createConsoleNotBuiltPlugin(consolePath: string | null)` is unchanged
except that it now accepts `null`.
`createConsoleShaDriftRefusalPlugin(drift)` is new.
- `describeConsoleShaDrift(drift, hostPaths)` (not exported) is the
drift refusal's text below its headline. Both the terminal block and the
HTTP answer render it.
- `describeConsoleDistMissing` (the not-built picker) gains a `null`
arm.
- `packages/cli/src/commands/serve.ts`, Console mount branch only:
- the drift arm now mounts the drift responder;
- the not-built arm is now `else if (consoleEnabled)`, so it also covers
`consolePath === null`.
- Outside the branch, the import list gains one name.
- `packages/cli/src/utils/console-route-ledger.ts`: the `family` (now
`console-static | console-unavailable`) and the `note` of `GET /`, `GET
/_console` and `GET /_console/*` name every responder that mounts them.
- Tests:
- `console.sha-drift.test.ts`: the drift responder on a real Hono app,
from a fixture tree with a made-up pin and stamp.
- `console.not-built.test.ts`: the `null` position.
- `test/published-subpath-console.pin.test.ts`:
`INTERNAL_SINCE_RETIREMENT` records the new internal export. The
module's own partition pin requires this. The `./console` barrel still
publishes exactly three names.
- `.changeset/22420-console-drift-responder.md`: `@objectstack/cli`
patch.
## Mechanism hypotheses, measured
- **H1, confirmed.** The branch at `serve.ts:4961` (base `4638625e07`)
printed `formatConsoleShaDriftRefusal` on `refusedForDrift &&
consoleDrift` and mounted nothing. The no-dist arm mounted
`createConsoleNotBuiltPlugin(consolePath)`. Measured live (below): drift
answered `404 ENDPOINT_NOT_FOUND` at `/_console/`, `/` and `/_console`.
- **H2, done as "a sibling that shares its route mounting".** There is
one route-mounting body. Each position has one picker:
- not-built: `describeConsoleDistMissing`;
- drift: `describeConsoleShaDrift`.
The terminal and HTTP forms render the same picker output. The not-built
warning, the drift refusal and the drift advisory render byte-identical
to base: a script printed all three for a fixed drift from a BASE
worktree and from HEAD, and the two outputs have the same sha256,
`a9a832a6aa7d…`.
- **H3, confirmed.** The refusal names `drift.pinFile`, an absolute
path. The HTTP form writes `.objectui-sha at the checkout root` instead,
and the terminal keeps the path. The SHAs stay in both, because they are
commits of the public objectui repository. Ablation: making the HTTP
form name `drift.pinFile` turns exactly one pin red, "with only the
absolute pin-file path left out" (1 failed / 13 passed). The mutation
and its restore were both proven on disk by
`scripts/ablation-replace.mjs` (blob `e8dd212b0e75` → `5c5be9625144` →
back to `e8dd212b0e75`, `git diff HEAD` empty).
- **H4, covered in this PR, with no second remedy picker.** When
`consolePath` is `null`, the install is one step past an install with no
`dist/`: `@objectstack/console` is a hard dependency of
`@objectstack/cli`. The existing picker's install arm already names the
fix for that ("reinstall it at the same version as the CLI"). So
`describeConsoleDistMissing` gains a `null` arm. It changes only what it
says is missing (`Console package ... not found` instead of `Console
dist not found`), and its reinstall sentence is the same; a pin compares
the two tails. The responder is the same `createConsoleNotBuiltPlugin`,
with the headline "is not installed".
- Reach is **NOT MEASURED** live, because it cannot be reached inside
this repository. The CLI always resolves its own workspace
`packages/console`. Measured: a fixture with no console package in its
`node_modules` still resolved the workspace package, and the boot took
the not-built arm.
- The position is pinned in-process (`createConsoleNotBuiltPlugin(null)`
on a real Hono app).
## Live measurement: before and after
The harness boots `bin/run-dev.js serve --dev` through tsx, using an
`OS_ARTIFACT_PATH` artifact from a scratch project. That project's
`node_modules/@objectstack/console` is a fixture dist, and its own
`.objectui-sha` is a fixture pin (`aaaaaaaaaaaa…`, stamp
`bbbbbbbbbbbb…`). The live pin is never used. Each child was stopped by
PID.
| case | `GET /_console/` | `GET /` | `GET /_console` |
|:--|:--|:--|:--|
| drift, base `4638625e07` | `404` `ENDPOINT_NOT_FOUND` JSON | `404`
`ENDPOINT_NOT_FOUND` | `404` |
| drift, head `9cadb71c00` | `503` `text/plain` `no-store`, refusal
text, 0 occurrences of the scratch path | `302` to `/_console/` | `302`
to `/_console/` |
| matching sha (control), base and head | `200` SPA shell | `302` |
`302` |
| `--no-console` (control), base and head | `404` `ENDPOINT_NOT_FOUND` |
`404` | `404` |
| package without `dist/` (neighbour), head | `503` not-built answer |
`302` | `302` |
The drift answer on head:
```text
The ObjectStack Console here was built from a different objectui commit than this checkout pins, so this dev server refuses to serve it at /_console/.
pinned (.objectui-sha at the checkout root): objectui@aaaaaaaaaaaa
built (console/dist/.objectui-sha): objectui@bbbbbbbbbbbb
packages/console/dist is a gitignored local build that 'turbo run build' does NOT refresh,
...
pnpm objectui:build
...
To boot anyway with the stale bundle: OS_ALLOW_CONSOLE_DRIFT=1 — the API still serves either way.
Then restart the server.
```
## Tests and gates
All readings below are against head `9cadb71c00`, and every lock-held
run printed `VERDICT command-exit 0` unless stated otherwise.
- **Red against base.** `console.ts` was restored to `4638625e07` (blob
`0a8b6a55bd46` proven on disk, then restored by trap to the HEAD blob
`e8dd212b0e75`, with `git diff HEAD` empty). Then 2 files failed and 11
new pins never passed:
- the drift block failed with `createConsoleShaDriftRefusalPlugin is not
a function`;
- the `null` block failed with `The "path" argument must be of type
string. Received null`.
The 18 pre-existing tests stayed green.
- **New and touched pins:** `console.not-built.test.ts`,
`console.sha-drift.test.ts`, `console-route-ledger.conformance.test.ts`
and `test/console-resolve.test.ts` passed, 4 files and 62 tests.
`test/published-subpath-console.pin.test.ts` (whose partition check
reads the packed `.d.ts`), `test/vitest-tiers-partition.test.ts` and the
ledger conformance passed, 3 files and 54 tests.
- **cli:** `pnpm --filter @objectstack/cli typecheck` passed: `tsc
--noEmit`, and `check:test-typecheck` on `tsconfig.test.json` with its
debt unchanged (3 files, 28 errors held).
- **cli unit layer** (`vitest run --project unit`): 276 files, 4086
tests passed.
- **cli integration layer** (`--project integration`, run locally
because `serve.ts` is a spawn entry): 102 files, 945 passed and 2
skipped.
- **Dogfood.** The one dogfood file that reads `/_console`,
`showcase-public-form-redirect.dogfood.test.ts`, imports
`utils/console.ts` as source, so it ran locally: 9 of 9 passed. No other
file under `packages/qa/dogfood` names `_console`, per `git grep`.
- **Lint, a proven narrowing** (the full `pnpm lint` is CI's):
1. Population: eslint's own config. `--print-config` resolves all 6
touched `.ts` files into linted config objects with 5 or 6 rules each.
2. Count: the `--format json` output reports 6 files, 0 errors and 0
warnings.
3. Invariance: `eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, no typed rules), and its local plugins are
per-file AST rules. So this diff cannot change the verdict on any
untouched file.
- **Gates:**
- `node scripts/pm/dispatch-gates.mjs --commands`, re-derived at
`9cadb71c00` with no paths, lists 67 commands: the dispatched 66 plus
`pnpm check:cli-test-child-env`, because of the pin-test edit.
- All 67 ran.
- `--ran` reconciliation: "67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN".
- `check:i18n-coverage` first refused with exit 3 ("COULD NOT MEASURE",
because `@objectstack/connector-slack` was unbuilt). After the build it
named, it re-ran: "OK (13 config(s), 621 baselined untranslated
string(s), none new)".
- `check:type-check-debt` and `check:dual-build-cjs-loads` ran last,
after every locked suite, and both passed.
- **`main`:** `origin/main` (`6a3f82efa7`) has moved since the base, but
no new commit touches a file in this diff, and `git merge-tree` is
clean. So there is no merge (per the fence).
## Acceptance notes
- **Stale prose, outside this card's surface, not edited.**
- The header of `console-route-ledger.conformance.test.ts` says both
factories in `utils/console.ts` "return early … unless a resolvable HTTP
server AND a built `dist/` exist". That has been false for the not-built
factory since PR #22419, and it is false for the drift responder here.
The census reads source, so the claim does not affect any verdict.
- The ledger header's SCOPE paragraph ("four routes across two plugin
factories") is dated by its own commit, `2ba4329e`.
- Neither is a defect. Carrier: whoever next edits those files.
- **Banner.** On a refused boot, the ready banner shows no Console link:
it prints one only when `ConsoleUI` is loaded (`serve.ts`, outside the
fence). That matches the not-built position and is unchanged.
- **`GET /forms/:slug`.** It is still mounted only with a served
Console, so on any refused boot it falls through to `404`. This is PR
#22419's observation, unchanged.
- **No boot-level pin was added.** This follows PR #22419. The pins are
in-process on a real Hono app, and `serve.ts`'s wiring is proven by the
live before/after table above. A spawning pin would put one more `serve`
boot into the per-PR integration layer (or into the nightly tier, if it
were named `.e2e`).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01BmsuLyUeuG5CNpZFMH1jzS)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent f89b5f5 commit d6c3791
7 files changed
Lines changed: 349 additions & 72 deletions
File tree
- .changeset
- packages/cli
- src
- commands
- utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
174 | 174 | | |
175 | 175 | | |
176 | 176 | | |
| 177 | + | |
177 | 178 | | |
178 | 179 | | |
179 | 180 | | |
| |||
4954 | 4955 | | |
4955 | 4956 | | |
4956 | 4957 | | |
4957 | | - | |
4958 | | - | |
4959 | | - | |
4960 | | - | |
4961 | | - | |
4962 | | - | |
4963 | | - | |
4964 | | - | |
4965 | | - | |
4966 | | - | |
4967 | | - | |
4968 | | - | |
4969 | | - | |
4970 | | - | |
4971 | | - | |
4972 | | - | |
4973 | | - | |
4974 | | - | |
| 4958 | + | |
| 4959 | + | |
| 4960 | + | |
| 4961 | + | |
| 4962 | + | |
| 4963 | + | |
| 4964 | + | |
| 4965 | + | |
| 4966 | + | |
4975 | 4967 | | |
| 4968 | + | |
| 4969 | + | |
| 4970 | + | |
| 4971 | + | |
| 4972 | + | |
| 4973 | + | |
| 4974 | + | |
| 4975 | + | |
| 4976 | + | |
| 4977 | + | |
| 4978 | + | |
4976 | 4979 | | |
4977 | 4980 | | |
4978 | 4981 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
116 | | - | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
117 | 119 | | |
118 | 120 | | |
119 | | - | |
| 121 | + | |
120 | 122 | | |
121 | 123 | | |
122 | 124 | | |
123 | 125 | | |
124 | 126 | | |
125 | 127 | | |
126 | 128 | | |
127 | | - | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
128 | 134 | | |
129 | 135 | | |
130 | 136 | | |
131 | | - | |
| 137 | + | |
132 | 138 | | |
133 | 139 | | |
134 | 140 | | |
135 | 141 | | |
136 | 142 | | |
137 | | - | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
138 | 146 | | |
139 | 147 | | |
140 | 148 | | |
| |||
151 | 159 | | |
152 | 160 | | |
153 | 161 | | |
154 | | - | |
| 162 | + | |
155 | 163 | | |
156 | 164 | | |
157 | 165 | | |
158 | 166 | | |
159 | 167 | | |
160 | 168 | | |
161 | | - | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
162 | 174 | | |
163 | 175 | | |
164 | 176 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
16 | 20 | | |
17 | 21 | | |
18 | 22 | | |
| |||
38 | 42 | | |
39 | 43 | | |
40 | 44 | | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
41 | 49 | | |
42 | 50 | | |
43 | 51 | | |
| |||
176 | 184 | | |
177 | 185 | | |
178 | 186 | | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
179 | 225 | | |
180 | 226 | | |
181 | 227 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
18 | 26 | | |
19 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
20 | 32 | | |
| 33 | + | |
| 34 | + | |
21 | 35 | | |
| 36 | + | |
22 | 37 | | |
23 | 38 | | |
24 | 39 | | |
| 40 | + | |
25 | 41 | | |
26 | 42 | | |
27 | 43 | | |
| |||
111 | 127 | | |
112 | 128 | | |
113 | 129 | | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
0 commit comments