Repository navigation
Commit db4618b
test(cli): every bespoke CLI spawner takes a kernel-assigned port and reads it back; the released-port ledger keeps only deliberate draws (#24396)
Fixes #24342
Clause-②: no
Basis for the Clause-② line: every changed file is under
`packages/cli/test/**`, the CLI's test harness. `@objectstack/cli`
publishes `dist`, `README.md` and `CHANGELOG.md` (its `files[]`).
Measured after `pnpm --filter @objectstack/cli build`: the symbols this
PR spreads or edits (`KERNEL_ASSIGNED_PORT`, `readKernelAssignedPort`,
`RELEASED_DRAW_LEDGER`) occur in 0 of those files. The positive control
`childPortEnv`, which comes from `src/`, occurs in 2. Nothing published
moves, so this PR carries the `skip-changeset` label and no changeset.
This is slice 2 of #24342 and the card's last: the 25 files slice 1 (PR
#24386, `c76e84922`) left in `RELEASED_DRAW_LEDGER` with the reason
`slice 2 of #24342`. After it, no ledger entry carries a slice reason.
Two files stay ledgered with their real reasons, so the card's "Done
when" holds: every spawner takes the closed mechanism or is named exempt
with its reason.
## Size
921 changed lines (+349 / -572, 25 files), measured by
`dispatch-gates.mjs` against merge base `cf16a0d18`. That is under the
card's 1,000-line split line, so all six groups of slice 1's "What slice
2 converts" are in this PR and there is no slice 3.
## What changed, by slice 1's groups
The recipe is the same everywhere: spawn on `KERNEL_ASSIGNED_PORT`
(`--port 0` / `-p 0`), read the bound port back from the child's own
output, and build each boot's base URL from its own read-back. A file
that reuses one drawn port across several boots now reads each boot's
port separately.
- **Bespoke boot helpers that settled on `portDriftError()`** (14 files,
16 draws).
- Files: `artifact-flag-precedence`,
`package-install-local-{boot-steps,handlers,hooks,jobs,jobs-pull,jobs-shared-name,uninstall-cleanups}`,
`package-restart-acceptance`, `serve-config-boot-flat-docs`,
`serve-config-boot-residual` (its `bootRefused()` too),
`serve-host-config-metadata-door`, `serve-organization-record-page`,
`serve-view-container-read`.
- The settle step is now the read-back verdict, as slice 1's A4 did for
`runServe()`. It settles on `readKernelAssignedPort(out, what)`, which
resolves with the bound port, or rejects when a complete banner names no
non-zero `http://localhost` port.
- The exit handler drops `portContentionError()`. That helper says "⛔
Not for a child spawned on KERNEL_ASSIGNED_PORT", because such a child
was handed no number to lose. An early exit now rejects with the child's
own output.
- Files with a `LiveServe` that carried no base gain `port`, and the
caller builds `base` from it. `package-restart-acceptance` builds a
second base for boot 2. `package-install-local-uninstall-cleanups` reads
8 boots, and each one's `base` is its own.
- **Their own banner read-back** (2):
`serve-runtime-assets-branding-warning`, `start-signal-forwarding`.
`reservePort()` and `boundPortFromBanner()` become
`KERNEL_ASSIGNED_PORT` and `readKernelAssignedPort()`.
- **Copies of the `runServe()` shape** (3):
- `serve-app-anchored-optional-import` (`runServeFrom`): the contention
and drift checks become the read-back, taken only off a complete banner.
The undeclared leg dies before `listen()` and announces no port. The
header's port-apparatus section is rewritten to say so.
- `serve-process-child-env`: `probeOrigin()` defaults to
`KERNEL_ASSIGNED_PORT`. Its `readyVerdict()` already handled a requested
0 through `portDriftError()`'s 0 branch. The forced-drift arm still
hands over a held port on purpose.
- `serve-migrate-exit-schema-parity`: the one `--port` argument.
- **`bin/run.js --dev` / `os dev`** (4):
- `dev-boot-output-order`: the pty command line takes `-p 0`.
- `serve-mcp-capability-collision`, `serve-mcp-stdio-answers`,
`serve-stdio-stdout-purity`: the module-level drawn `port` is gone, and
so is each file's local `portDriftError()` with its docblock. The local
`boundPort()` gate stays: a boot resolves only once a port is named. It
resolves with that port, and rejects at once on a `0`, which would be
the request announced back. Each boot's HTTP base comes from boot 1's
read-back.
- **The drift notice's NEGATIVE arm** (`serve-port-drift-notice`):
measured, then **kept and ledgered, not converted** (see below).
- **Deliberate draws** (`serve-publishes-bound-port`, 2): re-ledgered
with their real reasons.
## The two entries left in `RELEASED_DRAW_LEDGER`
`SLICE_2` is deleted. Each remaining entry names why `--port 0` read
back afterwards would change what the file proves:
- `serve-port-drift-notice.e2e` (1). The NEGATIVE arm is a boot that
binds exactly the non-zero port it asked for and says nothing about
ports. Measured on this head, with the arm's own spawn (tsx `serve`,
bare config, `NODE_ENV=development`, default log level):
- control, a released non-zero port (36499): banner `API:
http://localhost:36499/`, the arm's `DRIFT_NOTICE` regex matched
nothing, and no stderr line named `Port`;
- `--port 0`: banner `API: http://localhost:43037/`, `DRIFT_NOTICE`
matched nothing, but stderr carried `WARN Port 0 is in use, using port
43037 instead`. That is #24387's line, from `plugin-hono-server`'s
`kernel:listening` hook.
- So the conversion would put a port line into the boot whose subject is
"says nothing about ports", and it would trade "requested equals bound"
for "requested is 0". Per the claim, it stays ledgered with that reason.
- `serve-publishes-bound-port.e2e` (2), as slice 1 named them.
`reservePort()` is the connect instrument's NEGATIVE arm: a port probed
free and left unbound, with nothing spawned on it. `randomPort()` is the
non-zero request that the ordinary-boot arm is about, and that arm reads
a lost race out of the child's own drift notice instead of failing on
it.
The pin's other tests are unchanged. The positive controls
`package-install-local-handlers` (harness `CLI`) and
`start-signal-forwarding` (spells `bin/run-dev.js`) are still in the
population. "Every ledger entry still names a file in the population"
still guards against a stale row.
## Helper docblocks (`test/helpers/serve-process.ts`, comments only)
`reservePort()`, `randomPort()` and `portDriftError()` now say what may
still draw:
- instruments that need a number nothing listens on, or that measure the
draw itself;
- a CLI spawn whose subject is a non-zero request, which the pin's
ledger names with its reason.
No helper code changed.
## Reverse verification (one-time, not kept)
The pin reads source through the TypeScript AST, so no build or `dist`
preflight applies.
- **Mutation**, through `scripts/ablation-replace.mjs` (WRAP mode). In
`serve-migrate-exit-schema-parity.integration`, `'--port',
KERNEL_ASSIGNED_PORT, ...extraArgs` became `'--port', randomPort(),
...extraArgs`, which is this file's line before the conversion.
- The tool reported `anchor 1 → 0`, `replace x0 → x1`, and blob
`9d7ef446bad7 → febdea4dc573`.
- **Result.** The pin went `Tests 1 failed | 3 passed (4)`, naming
`serve-migrate-exit-schema-parity.integration.test.ts: 1 released-port
draw(s) (line 96), ledger allows 0`. The file has no ledger entry any
more, so one draw is a red.
- **Restore.** Blob after restore
`9d7ef446bad71d7fd5aba91e23f3265d414925e4` == HEAD's blob. `git diff
HEAD` was empty, and `git status --porcelain` was empty, read separately
afterwards.
- **The negative-arm probe** above (control versus `--port 0`) was a
scratch script outside the repo. Both children were stopped and awaited,
and the tree is clean.
## Tests
All on head `4fb88734c`, base `cf16a0d18`. The dependency closure was
built first with `turbo run build --filter='@objectstack/cli^...'`
(60/60, `VERDICT command-exit 0`), then `pnpm --filter @objectstack/cli
build`. Every run went through `scripts/pm/os-verify-lock.sh` with a
slot. Each file ran in the tier it lives in, and the tier of every
changed file is unchanged from base (measured with `vitest-tiers.ts`'s
own predicate at base and head: 24 test files, 0 changed; the pin stays
`unit`).
- `unit`: `released-port-production-spawn.pin` 4/4.
- Nightly tier (`OS_TEST_TIERS=nightly`, `--project integration
--maxWorkers=2`):
- `serve-app-anchored-optional-import`,
`serve-organization-record-page`, `serve-process-child-env`,
`serve-runtime-assets-branding-warning`, `start-signal-forwarding`: 5
files, 23/23.
- `serve-mcp-capability-collision`, `serve-mcp-stdio-answers`,
`serve-stdio-stdout-purity`: 3 files, 5/5.
- Queue tier (`--project integration --maxWorkers=2`):
- `serve-config-boot-flat-docs`, `serve-config-boot-residual`,
`serve-host-config-metadata-door`, `serve-migrate-exit-schema-parity`,
`serve-view-container-read`, `package-restart-acceptance`: 6 files,
37/37.
- `artifact-flag-precedence`, `dev-boot-output-order`: 2 files, 13/13.
- `package-install-local-{boot-steps,handlers,hooks,jobs-pull}`: 4
files, 40/40.
- `package-install-local-{jobs-shared-name,jobs,uninstall-cleanups}`: 3
files, 37/37.
- In all: 23 converted files, 155/155, plus the pin's 4/4.
- `pnpm --filter @objectstack/cli typecheck`: exit 0. The test layer
holds its 3-file / 28-error ledger. `tsc --listFiles -p
tsconfig.test.json` puts all 25 changed files in the program, and the 28
errors are all in `data-model-rules` (23),
`i18n-extract-action-description` (4) and `i18n-declared-surface-gate`
(1), none in a changed file.
- `pnpm exec eslint --no-inline-config` over the 25 changed files: exit
0.
- Not merged with `origin/main`. Main has moved to `584b4225e` since the
base. Its 3 new commits touch `packages/cli/src` (organization
ownership, `verify.ts`) and none of the files here, and there is no
conflict. So, per the dev contract (merge only on a conflict, a
main-caused red or a dequeue), CI's merge ref is what reads the joint
tree.
## Gates
Local subset, head `4fb88734c`.
- `node scripts/pm/dispatch-gates.mjs --commands` derives 52 commands
(change set 25 paths vs merge base `cf16a0d18`, 921 changed lines). Each
ran with its exit code captured before any pipe.
- 51 exited 0 on the first pass.
- `check:dual-build-cjs-loads` first read `PREREQUISITE NOT MET` (exit
3: some packages outside the CLI closure had no `dist/`). Re-run once
those `dist/` trees existed, it read exit 0: `111 published require
entry point(s) across 69 package(s) load`.
- `--ran` reconciliation: `Run reconciliation — 52 derived, 52 run, 0
NOT-MEASURED, 0 UNRUN.`
- The 11 declared-wide families (`check:init-service-contract`,
`check:live-db-isolation`, `check:meta-type-normalized`,
`check:optional-error-sink`, `check:resume-authority-declared`,
`check:route-envelope`, `check:runner-env-posture`,
`check:settings-bind-window`, `check:startup-registry-verdict`,
`check:verify-stand-in`, `check:wildcard-fallthrough`): all 11 exited 0.
So did the three artifact-roster gates whose roster sits under
`packages/` (`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`).
- `check:cli-test-child-env`: `155 spawner source(s) among 305 under
packages/cli/test/**; no new bulk process.env copy reaches a spawned
child, all 189 spawn call(s) declare their child's env`, exit 0.
- `check:nul-bytes`: `no raw ASCII control bytes`, exit 0.
- Not run locally: `check-platform-checklist` (no exported symbol was
renamed or deleted; the deleted `portDriftError` copies and `SLICE_2`
were module-local). The repo-wide `pnpm lint` and the CI farm are CI's,
and are read from this PR's check-runs.
## Acceptance notes
- **Kernel-assigned boots now print #24387's warning.** `WARN Port 0 is
in use, using port N instead` now appears in the converted boots at any
log level that admits a warn. No assertion here reads it: every
converted file passed on this head. It is the one reason, of two, that
keeps `serve-port-drift-notice`'s negative arm drawn; the other
(requested 0 is not "the port it asked for") does not go away with it.
#24387 remains open and unclaimed, and this PR does not touch
`plugin-hono-server`.
- **The nightly-tier readings are local.** The 8 `.e2e` files run
nightly on `main`, not in this PR's `Test Core`.
- **`package-restart-acceptance`'s boot 2 is no longer on boot 1's
port.** The file's subject is the stored package surviving a restart on
the same database file. Nothing in it read the port as state, and both
boots pass on their own read-back ports.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1827571 commit db4618b
25 files changed
Lines changed: 349 additions & 572 deletions
File tree
- packages/cli/test
- helpers
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
84 | | - | |
85 | | - | |
| 84 | + | |
86 | 85 | | |
87 | | - | |
| 86 | + | |
88 | 87 | | |
89 | 88 | | |
90 | 89 | | |
| |||
125 | 124 | | |
126 | 125 | | |
127 | 126 | | |
128 | | - | |
| 127 | + | |
129 | 128 | | |
130 | 129 | | |
131 | 130 | | |
| |||
147 | 146 | | |
148 | 147 | | |
149 | 148 | | |
150 | | - | |
| 149 | + | |
151 | 150 | | |
152 | 151 | | |
153 | 152 | | |
154 | | - | |
155 | | - | |
| 153 | + | |
| 154 | + | |
156 | 155 | | |
157 | 156 | | |
158 | 157 | | |
159 | 158 | | |
160 | 159 | | |
161 | 160 | | |
162 | 161 | | |
163 | | - | |
| 162 | + | |
| 163 | + | |
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
167 | 167 | | |
168 | | - | |
169 | | - | |
| 168 | + | |
170 | 169 | | |
171 | 170 | | |
172 | 171 | | |
| |||
250 | 249 | | |
251 | 250 | | |
252 | 251 | | |
253 | | - | |
254 | 252 | | |
255 | 253 | | |
256 | | - | |
| 254 | + | |
257 | 255 | | |
258 | 256 | | |
259 | 257 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| |||
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
118 | | - | |
119 | 118 | | |
120 | 119 | | |
121 | | - | |
| 120 | + | |
122 | 121 | | |
123 | 122 | | |
124 | 123 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
186 | | - | |
| 185 | + | |
| 186 | + | |
187 | 187 | | |
188 | 188 | | |
189 | 189 | | |
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
227 | | - | |
| 227 | + | |
| 228 | + | |
228 | 229 | | |
229 | 230 | | |
230 | 231 | | |
231 | 232 | | |
232 | 233 | | |
233 | 234 | | |
234 | | - | |
235 | | - | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | 235 | | |
241 | | - | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
242 | 241 | | |
243 | 242 | | |
244 | 243 | | |
| |||
318 | 317 | | |
319 | 318 | | |
320 | 319 | | |
321 | | - | |
322 | | - | |
| 320 | + | |
| 321 | + | |
323 | 322 | | |
324 | 323 | | |
325 | 324 | | |
| |||
599 | 598 | | |
600 | 599 | | |
601 | 600 | | |
602 | | - | |
603 | | - | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
604 | 604 | | |
605 | 605 | | |
606 | 606 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
58 | | - | |
59 | | - | |
| 58 | + | |
60 | 59 | | |
61 | | - | |
| 60 | + | |
62 | 61 | | |
63 | 62 | | |
64 | 63 | | |
| |||
153 | 152 | | |
154 | 153 | | |
155 | 154 | | |
156 | | - | |
| 155 | + | |
157 | 156 | | |
158 | | - | |
| 157 | + | |
159 | 158 | | |
160 | 159 | | |
161 | 160 | | |
| |||
166 | 165 | | |
167 | 166 | | |
168 | 167 | | |
169 | | - | |
| 168 | + | |
170 | 169 | | |
171 | 170 | | |
172 | 171 | | |
173 | | - | |
174 | | - | |
| 172 | + | |
| 173 | + | |
175 | 174 | | |
176 | 175 | | |
177 | 176 | | |
178 | 177 | | |
179 | 178 | | |
180 | 179 | | |
181 | 180 | | |
182 | | - | |
183 | | - | |
| 181 | + | |
| 182 | + | |
184 | 183 | | |
185 | 184 | | |
186 | 185 | | |
187 | 186 | | |
188 | | - | |
189 | | - | |
| 187 | + | |
190 | 188 | | |
191 | 189 | | |
192 | 190 | | |
| |||
320 | 318 | | |
321 | 319 | | |
322 | 320 | | |
323 | | - | |
324 | 321 | | |
325 | 322 | | |
326 | | - | |
| 323 | + | |
327 | 324 | | |
328 | 325 | | |
329 | 326 | | |
330 | 327 | | |
331 | 328 | | |
332 | 329 | | |
333 | | - | |
| 330 | + | |
334 | 331 | | |
335 | 332 | | |
336 | 333 | | |
337 | 334 | | |
338 | 335 | | |
339 | 336 | | |
340 | | - | |
| 337 | + | |
341 | 338 | | |
342 | 339 | | |
343 | 340 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
44 | | - | |
45 | | - | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
64 | | - | |
| 63 | + | |
65 | 64 | | |
66 | | - | |
| 65 | + | |
67 | 66 | | |
68 | 67 | | |
69 | 68 | | |
| |||
142 | 141 | | |
143 | 142 | | |
144 | 143 | | |
145 | | - | |
| 144 | + | |
146 | 145 | | |
147 | | - | |
| 146 | + | |
148 | 147 | | |
149 | 148 | | |
150 | 149 | | |
| |||
155 | 154 | | |
156 | 155 | | |
157 | 156 | | |
158 | | - | |
| 157 | + | |
159 | 158 | | |
160 | 159 | | |
161 | 160 | | |
162 | | - | |
163 | | - | |
| 161 | + | |
| 162 | + | |
164 | 163 | | |
165 | 164 | | |
166 | 165 | | |
167 | 166 | | |
168 | 167 | | |
169 | 168 | | |
170 | 169 | | |
171 | | - | |
172 | | - | |
| 170 | + | |
| 171 | + | |
173 | 172 | | |
174 | 173 | | |
175 | 174 | | |
176 | 175 | | |
177 | | - | |
178 | | - | |
| 176 | + | |
179 | 177 | | |
180 | 178 | | |
181 | 179 | | |
| |||
327 | 325 | | |
328 | 326 | | |
329 | 327 | | |
330 | | - | |
331 | 328 | | |
332 | 329 | | |
333 | | - | |
| 330 | + | |
334 | 331 | | |
335 | 332 | | |
336 | 333 | | |
| |||
339 | 336 | | |
340 | 337 | | |
341 | 338 | | |
342 | | - | |
| 339 | + | |
343 | 340 | | |
344 | 341 | | |
345 | 342 | | |
346 | 343 | | |
347 | 344 | | |
348 | 345 | | |
349 | | - | |
| 346 | + | |
350 | 347 | | |
351 | 348 | | |
352 | 349 | | |
| |||
0 commit comments