Repository navigation
fix(cli): a --no-server boot takes the datasource admin routes' own degrade line, not an ordering fault - #24132
Conversation
…ering fault
The datasource admin routes plugin read the HTTP server with
getService?.('http.server') ?? getService?.('http-server'). The kernel's
getService throws on a name nothing registered, so on a boot with no server
the ?? never ran and the throw landed in the registration catch, which
printed "route registration failed" with the kernel's
"composition/ordering fault" text when nothing was at fault.
The plugin moves to an exported factory, createDatasourceAdminRoutesPlugin,
and reads the server through readComposedHttpServer (http.server only, in a
try). A boot with no server now takes the plugin's own degrade line; a
composed boot whose registration genuinely throws still warns "route
registration failed".
Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp
Co-authored-by: Claude <noreply@anthropic.com>
…boots The production and --dev --no-server boots of serve-no-server-banner carry exactly one [datasource-admin] line and none reporting a failed registration or an ordering fault; the default production boot (the control) still mounts /api/v1/datasources (not 404, while an unmounted sibling path is) and prints no [datasource-admin] line. Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
…ce-admin line Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
The datasource admin routes plugin no longer reads the server through the untyped getService?.() ?? getService?.() chain. Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
…tasource-admin-no-server
…tasource-admin-no-server
… under --no-server The changeset says a --no-server boot whose config mounts its own HonoServerPlugin still mounts /api/v1/datasources. Shape 4 of the banner integration file now measures it: the sibling path answers 404, the routes path does not, and no [datasource-admin] line prints. The changeset's alias sentence now says the old http-server fallback never ran, so no boot that mounted the routes before stops mounting them. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVm1pAXVXQ3PjC42j1kPFf
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 29 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin feccac428fa0f192aedc6b65f3c4c4188bfeddbc && git checkout feccac428fa0f192aedc6b65f3c4c4188bfeddbc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c1c0a90942ff702ee6a5b40d0382808f6b2b154c 6c33626a8df327db1be7cdf94c11ca8508fc5c87 && git checkout -B drift-repro c1c0a90942ff702ee6a5b40d0382808f6b2b154c && git merge --no-ff 6c33626a8df327db1be7cdf94c11ca8508fc5c87
node scripts/docs-audit/affected-docs.mjs --json c1c0a90942ff702ee6a5b40d0382808f6b2b154c
|
…ard ordering fault
The unknown-hostname guard read the HTTP server with
getService?.('http.server') ?? getService?.('http-server'), the same trap as
the datasource admin routes: the kernel's getService throws on a name nothing
registered, so on a boot with no server (OS_ROOT_DOMAIN set, --no-server) the
throw landed in the install catch and printed "install failed" with the
kernel's "composition/ordering fault" text. init now reads the server through
readComposedHttpServer, so that boot takes the guard's own degrade line, and
an install that throws on a composed server still warns "install failed".
Pins in serve-unknown-hostname-guard.test.ts: a real ObjectKernel boot with no
server (the degrade line once, no "install failed", no "ordering fault"), its
composed control (installed, and refusing an unmapped host), and an install
failure on a usable server still warning "install failed". The throwing
service-resolution case now expects the degrade line. slot-lookup: serve.ts
7 -> 6. The changeset names the guard's line too.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVm1pAXVXQ3PjC42j1kPFf
席位核对 — ACCEPT ·
|
Fixes #24022
Clause-②: no
What was wrong
On a boot that composes no HTTP server, the boot diagnostics printed:
Nothing was at fault.
--no-servercomposes no server on purpose. The plugin read the server withctx.getService?.('http.server') ?? ctx.getService?.('http-server'). The kernel'sgetServicethrows on a name nothing registered (packages/core/src/kernel.ts, the[Kernel] Service '...' not foundthrow, with the init-order suffix fromdescribeInitOrderFaultinplugin-order.ts). So the??never ran. The throw landed in the registrationcatch, and the plugin's own degrade line was unreachable.The fix
packages/cli/src/commands/serve.tsonly, in two plugins:com.objectstack.cli.datasource-admin-routes, and its siblingcom.objectstack.cli.unknown-hostname-guard:run()into an exported factory,createDatasourceAdminRoutesPlugin(registerRoutes), next tocreateUnknownHostnameGuardPlugin. That makes itsinitreachable from a test against a real kernel.run()now callskernel.use(createDatasourceAdminRoutesPlugin(registerDatasourceAdminRoutes)). Name, version andoptionalDependenciesare unchanged.initreads the server through the existingreadComposedHttpServer(reused, not changed):http.serveronly, inside atry. No server: the plugin's own line,[datasource-admin] http.server unavailable; REST routes not installed. A server composed and registration throws:route registration failedwith the error, as before.http-serveralias was never reached: the canonical read threw first. Every in-repo producer ofhttp-server(the Hono server plugin and the http-conformance node plugin) also registershttp.server, the same instance. So every boot that mounted these routes before still mounts them.createUnknownHostnameGuardPlugin, composed whenOS_ROOT_DOMAINis set) had the same trap, so itsinitnow reads the server throughreadComposedHttpServertoo. It is a one-line hunk. The read stays inside the existingtry, andreadComposedHttpServernever throws. With no server, the boot now takes[unknown-hostname-guard] http.server unavailable; guard not installed. On a composed server whose install throws, it still warnsinstall failedwith the error. The claim's file surface was widened for this in comment6228552643.scripts/slot-lookup-baseline.json:serve.ts9 to 6, because both untyped two-lookup chains are gone (check:slot-lookupholds at 101 sites in 25 files).Pins
packages/cli/src/commands/serve-datasource-admin-routes.test.ts(unit tier, new). The plugin runs inside a realObjectKernel, so the throw it has to survive is the kernel's own:http.server;route registration failed, noordering fault, exactly one[datasource-admin]line;http.serveris asked for, never the alias;/api/v1/datasources, and no[datasource-admin]line;route registration failed, naming the error;run()mounts through the factory, and no second inline copy exists.packages/cli/src/commands/serve-no-server-banner.integration.test.ts(integration tier), against the built CLI:--no-server --preset minimal, and--dev --no-server) carry exactly one[datasource-admin]line, and it does not report a failed registration or an ordering fault;/api/v1/datasourcesis not 404, while an unmounted sibling path is 404, and no[datasource-admin]line prints;--no-serverwith a config-mountedHonoServerPlugin) has the same control. It backs the changeset's claim that the routes still mount on that shape. This round adds it.packages/cli/src/commands/serve-unknown-hostname-guard.test.ts(unit tier, the guard's existing file):ObjectKernelboot with no server: noinstall failed, noordering fault, and exactly one[unknown-hostname-guard]line, the degrade line;optionalDependencies, logsinstalled, prints no[unknown-hostname-guard]warning, and refuses an unmapped host with 404ENVIRONMENT_NOT_FOUND;install failed, naming the error;install failed. The kernel throws on an unregistered name, so a throwing read IS the no-server boot.Verification, all at head
6c33626a8pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2reads 309 files, 4591 tests, all passed, exit 0.pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 src/commands/serve-no-server-banner.integration.test.tsreads 4 of 4 passed, exit 0. The other integration-tier files are declared to CI.pnpm --filter @objectstack/cli typecheckexits 0,check:test-typecheckincluded.tsc --noEmit --listFilescompiles all three touched test files.pnpm exec eslint --no-inline-configon the four changed.tsfiles exits 0 with no output. The.mdand.jsonfiles are outside eslint's configured file set.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 75 commands on6c33626a8, and all 75 exit 0.--ranreads75 derived, 75 run, 0 NOT-MEASURED, 0 UNRUN.origin/mainc1c0a9094, where 3 family files changed:check-route-envelope.mjs,engine-double-contract.pinned.jsonand onescripts/adr-anchorsfile. Those families ran the branch's copies. CI's merge-ref run is the reading on main's copies.check:startup-registry-verdict(exit 0, 44 seams, none recording a verdict the boot can contradict).OS_ROOT_DOMAIN=example.test:os serve objectstack.config.ts --no-server --preset minimalprintsWARN [unknown-hostname-guard] http.server unavailable; guard not installedandWARN [datasource-admin] http.server unavailable; REST routes not installed, and no ordering fault;--no-serverboot whose config mounts its ownHonoServerPlugin, each logINFO [unknown-hostname-guard] installed, with no guard warning.Ablation, round 1 (the datasource admin routes, at
a12bf2f9b)The fix was committed first. The mutation, done with
scripts/ablation-replace.mjs, puts the pre-fix read back: the throwinggetServicechain, inside thetry. The anchor count went from 1 to 0, and the blob went froma48fc3da93e4to95b1a98c5f93.ablation-dist-preflight @objectstack/cli 'readComposedHttpServer(ctx)' --absentpassed.takes its own degrade line.route registration failed ... a composition/ordering faultline. The two controls stayed green, as they should.git diff HEADempty,git status --porcelainempty. The CLI was rebuilt, and the preflight found the marker present again. Unit 6 of 6 and integration 4 of 4 passed.http.serverasked) stays green under the mutation, because the old chain also never reached the alias once the canonical read threw. It guards against an alias read coming back. It does not discriminate this card's defect.Ablation, round 2 (the guard)
The fix was committed first (
6c33626a8).scripts/ablation-replace.mjsput the guard's pre-fix line back. The anchor count went from 1 to 0, and the blob went froma080f01a90c5toa48fc3da93e4, which is byte-identical to the previous head'sserve.ts. The test imports its subject from source (./serve.js), so no rebuild sits in the path.[unknown-hostname-guard] install failed ... a composition/ordering faultline, and the throwing-resolution case. Both controls stayed green.git diff HEADempty,git status --porcelainempty. 38 of 38 passed.Handover
This resumes the released claim's branch at
4b0a631575. Every commit was re-read hunk by hunk:7177b4e8c(the fix, and the new unit file, which landed in this commit, not in2e52e010a),2e52e010a(the integration cases) and16be28cdd(the baseline row, re-measured after this round's merge oforigin/main50ccfa669);acd285a2b: the changeset's alias sentence now says the fallback never ran, instead of reading like a narrowing;All verification above was re-run in this round.
Acceptance notes
serve.ts, and on this shared box that import costs about 18.8 s (tests 14 ms). A sibling that imports the same module (serve-unknown-hostname-guard.test.ts) measured the same in one run, 36.6 s of import across the two files. No budget line is declared, since none of the three budget classes fits. This is a reading, not a gate.origin/mainmoved toc1c0a9094after this branch's merge. The 3 commits touchpackages/rest,plugin-email,qa/dogfood,runtimetests andspec/liveness, none of them underpackages/cli.git merge-treeis clean, so they were not merged.packages/runtime/README.mdshows a server plugin registering only the deprecatedhttp-serveralias. Docs drift, noted here, not filed.Generated by Claude Code