Repository navigation
Commit 8b7253c
fix(cli): os dev compiles the config before it boots, so served connector metadata matches dispatch (#26240)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X8JqqR7yTqyhz4Qfad5wYWK)_
Fixes #26130
Clause-②: no
Metadata-protocol: none
## What was wrong
A host config's `os dev` boot holds two copies of one stack. The config
module, evaluated in the `serve` child, feeds the AppPlugin wrap;
provider-bound connectors are materialized from that registry copy, so
it is what dispatch calls. The compiled `dist/objectstack.json` feeds
the dev artifact door, and its copy is what
`/api/v1/meta/connector/NAME` serves. `examples/app-showcase` builds its
connectors' `baseUrl` from `OS_PORT` at module evaluation, and `os dev`
booted whatever artifact was already on disk, so the two copies
disagreed whenever the artifact had been built in another environment.
Measured on `examples/app-showcase` at this branch's base (`c7aa9cde`),
`OS_PORT=41302`, the artifact compiled with no `OS_PORT` (`baseUrl:
http://127.0.0.1:3000`), nothing listening on 3000. "dispatch" is `POST
/api/v1/automation/showcase_declarative_connector_ping/trigger`, whose
one node calls `showcase_status_api.request` for `GET /api/v1/health`.
| boot | `GET /api/v1/meta/connector/showcase_status_api` →
`providerConfig.baseUrl` | dispatch |
|---|---|---|
| `os dev --no-compile` (= a bare `os dev` before this PR) |
`http://127.0.0.1:3000` | 200, `status: ok` (reached 41302) |
| `os dev` (this PR) | `http://127.0.0.1:41302` | 200, `status: ok` |
| `os start` (production, config joins the boot) |
`http://127.0.0.1:41302` | 200, `status: ok` |
| `os serve objectstack.config.ts` (production) |
`http://127.0.0.1:41302` | 200, `status: ok` |
| `os serve --dev objectstack.config.ts` | `http://127.0.0.1:3000` |
200, `status: ok` (reached 41302) |
`os start` and production `os serve` compose no artifact door, so they
serve the module's copy and already agree; they are unchanged. `os serve
--dev` has the same two copies; see Acceptance notes.
## The fix
Of the two routes the order named, this takes (b): the dev boot compiles
instead of reading the artifact as found. `--compile` is now on by
default, so `os dev` compiles `objectstack.config.ts` into the artifact
it serves, in its own environment, before the `serve` child starts. Both
copies are then one evaluation's answer, and nothing restates which
variables a config reads or in what order (no second copy of the
port/URL precedence). The watch loop's recompiles already ran in that
environment, so the two stay one answer for the session.
Route (a), re-resolving env-derived connector config at an artifact
boot, has nothing to re-resolve from: the artifact holds the value, not
the expression that produced it. Carrying the expression would need a
new `packages/spec` form for env-derived values, which the rulings put
out of reach, so it is not proposed.
- `packages/cli/src/commands/dev.ts`: the decision is one exported
function, `devCompilesAtBoot`. A pinned artifact (`--artifact`,
`OS_ARTIFACT_URL`, a remote `OS_ARTIFACT_PATH`) is never rebuilt.
`--no-compile` keeps the old shortcut and the startup staleness warning.
A missing artifact is still compiled. With no config, an existing
artifact boots as before.
- Cost: one compile per dev start. Measured on the showcase: 6.5 s for
`os compile` (737 KB artifact).
- `content/docs/deployment/cli.mdx`: the `--compile` row now says
default on and names `--no-compile`. This file is outside the claim's
file surface, but the docs row would be false without it.
- `scripts/check-cli-test-child-env.mjs`: one census entry for the new
test, which spawns the built entry (see Gates).
- `examples/app-showcase/src/system/self-url.ts`: header comment only.
Its `os dev` claim ("follows the live environment") is now true for the
served metadata too, and it names the `--no-compile` exception.
## Tests
At head `ac8dadabc` (the three dev test files rerun there: `Test Files 3
passed (3)`, `Tests 15 passed (15)`; the unit tier and typecheck ran at
`7f3d5bee`, whose only difference from the head is the census line in
`scripts/check-cli-test-child-env.mjs`):
- New `packages/cli/test/dev-connector-env-parity.integration.test.ts`
(integration tier, built entry). It uses a minimal host config with one
provider-bound connector whose `baseUrl` is built from `OS_PORT` the way
`self-url.ts` builds it, and a host plugin whose provider factory
records the `providerConfig` it was handed. A prebuilt artifact carries
`:3000`. One `os dev` boot with `OS_PORT=41302` checks that the served
`baseUrl` and the materialized one both read `http://127.0.0.1:41302`.
Result: `Tests 2 passed (2)`.
- New `packages/cli/test/dev-compile-at-boot.pin.test.ts` (unit tier)
checks oclif's parse plus `devCompilesAtBoot` for each case: bare,
`--no-compile`, pinned, missing artifact, no config. It passed with
`dev-no-watch.pin.test.ts`.
- `pnpm --filter @objectstack/cli exec vitest run --project unit`:
`Tests 1 failed | 4858 passed`. The one failure was
`boot-preparation-parity.test.ts`, which timed out at 5934 ms against
its 5000 ms limit while the gate sweep shared the box. Run alone it
answered `Tests 37 passed (37)`. It reads `serve.ts`, which this PR does
not touch.
- `pnpm --filter @objectstack/cli typecheck`: exit 0.
- Ablation, run once: `devCompilesAtBoot`'s default arm was mutated to
`false` through `scripts/ablation-replace.mjs`, the CLI was rebuilt, and
`ablation-dist-preflight` found the marker in
`packages/cli/dist/commands/dev.js`. The integration test then failed
with `expected 'http://127.0.0.1:3000' to be 'http://127.0.0.1:41302'`
on the served leg, while the materialized leg stayed green, which is the
card's defect exactly. The source was restored (blob equals HEAD, `git
diff HEAD` empty), the CLI rebuilt, and the preflight `--absent` check
passed.
## Gates
Local subset, `node scripts/pm/dispatch-gates.mjs --commands` at
`ac8dadabc`: 120 families derived. `--ran` reconciliation: 118 run with
exit 0, 2 NOT MEASURED, 0 unrun.
- NOT MEASURED, reason: prerequisite not met (exit 3), because both read
a full-repo build that this container did not make: `pnpm --filter
@objectstack/spec run check:skill-examples`, `pnpm
check:dual-build-cjs-loads`. CI runs both.
- `pnpm check:cli-test-child-env` went red on the first sweep because
its census pins the exact set of test files that spawn the built
`bin/run.js`, and the new integration test joins that set. The census in
`scripts/check-cli-test-child-env.mjs` now names the file with a
one-line reason, the same form as the other members. The gate is green,
and its self-test passed all 152 cases. The script's one other `git
grep` hit is a comment in
`serve-node-env-production-default.e2e.test.ts`, which no vitest project
selects.
- `node scripts/check-plugin-teardown-shape.mjs --self-test` exited 3 on
the shallow clone. After `git fetch origin 621a487…` (its positive
control), it passed 48 cases.
- `node scripts/check-nul-bytes.mjs`: exit 0. `pnpm exec eslint
--no-inline-config` on the four touched source files: exit 0. Neither
touched package has a `lint` script.
- Every family CI runs is cited from this PR's check-runs in the report,
not re-run here.
## Acceptance notes
- **`os serve --dev objectstack.config.ts` has the same disagreement**
(row 5 above): it composes the dev artifact door and does not compile.
This PR does not widen to it, per the ruling. It is reported to the
seat.
- **`os dev -p 41302` with no `OS_PORT` in the environment**: `os dev`
forwards the port to `serve` as `--port` and exports no `OS_PORT`, so
the showcase's config resolves `:3000` in both copies. They agree, but
dispatch fails with `connector_action(showcase_status_api.request)
failed: fetch failed` (measured, `FLOW_FAILED` 400). `self-url.ts` says
it follows "the same names, in the same order, that the CLI itself reads
to choose the listen port", and the flag is not one of them. This is
#7538's symptom on the flag path, not this card. It is reported to the
seat.
- `os dev` now rewrites `dist/objectstack.json` at every start. An `os
start` that later boots that file alone serves the values of the last
dev boot's environment. That was already true after any watch recompile,
and `self-url.ts` already says to set the environment for the build.
#26088 remains open; this PR does not address it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Cj99V2CpVvyWDjQu1ndoBS)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Cj99V2CpVvyWDjQu1ndoBS
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1eebbc0 commit 8b7253c
7 files changed
Lines changed: 448 additions & 10 deletions
File tree
- .changeset
- content/docs/deployment
- examples/app-showcase/src/system
- packages/cli
- src/commands
- test
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
192 | 192 | | |
193 | 193 | | |
194 | 194 | | |
195 | | - | |
| 195 | + | |
196 | 196 | | |
197 | 197 | | |
198 | 198 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
36 | 39 | | |
37 | 40 | | |
38 | 41 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
246 | 246 | | |
247 | 247 | | |
248 | 248 | | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
249 | 291 | | |
250 | 292 | | |
251 | 293 | | |
| |||
332 | 374 | | |
333 | 375 | | |
334 | 376 | | |
| 377 | + | |
| 378 | + | |
335 | 379 | | |
336 | | - | |
337 | | - | |
| 380 | + | |
| 381 | + | |
338 | 382 | | |
339 | 383 | | |
340 | 384 | | |
| |||
472 | 516 | | |
473 | 517 | | |
474 | 518 | | |
475 | | - | |
476 | | - | |
477 | | - | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
478 | 528 | | |
479 | 529 | | |
480 | 530 | | |
| |||
531 | 581 | | |
532 | 582 | | |
533 | 583 | | |
534 | | - | |
535 | | - | |
| 584 | + | |
| 585 | + | |
536 | 586 | | |
537 | 587 | | |
538 | 588 | | |
| |||
| 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 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
0 commit comments