Uh oh!
There was an error while loading. Please reload this page.
fix(runtime): search slot reports handlerReady:false, not true (#7939) - #7965
Conversation
svcAvailable() defaulted an unmarked search occupant to handlerReady:
true, contradicting the services map's own contract ("handlerReady:
true means the dispatcher has a real, bound handler for this route")
— the dispatcher has no /search route or handler at all. Fixed with
the same svcInProcess() remedy #4318 used for cache/queue/job, with
search's own message (svcInProcess's shared 'Kernel-internal service'
wording is false for an external search engine).The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
hotlong
commented
Aug 12, 2026
PM review — The three things the dispatch was strictest about, all delivered
The second test — a Two judgement calls I want to endorse explicitly, since both could have gone wrong quietly: The message wording. The discretion granted at dispatch was used, and used correctly: Leaving Docs: verified clean — but the advisory above is not whyThe docs-drift comment lists 20 pages. Checked with a control:
That page is not in the advisory's list of 20. Read it anyway: every mention of The advisory being simultaneously noisy (20 mostly-irrelevant pages) and blind to the one that matters is a checker defect, not a defect in this PR. Filed separately as #7967 — root cause measured: the check maps docs to packages by textual mention, and Staying draft until the queued checks finish; enqueueing on green. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7939
What changed
packages/runtime/src/http-dispatcher.ts'sgetDiscoveryInfo()builtservices.searchwithsvcAvailable(undefined, undefined, searchSvc), whichdefaults an unmarked occupant to
handlerReady: true— contradicting the samemap's own stated contract a few lines above:
handlerReady: truemeans "thedispatcher has a real, bound handler for this route." The dispatcher has no
/searchroute or handler at all (noroute-ledger.tsentry, no branch inhttp-dispatcher.ts,routeis alwaysundefinedat this call site), so afilled slot reported "no route" and "handler is ready" in one breath.
This is the exact contradiction #4318 closed for
cache/queue/job, appliedto the one slot of that shape it did not reach.
searchnow uses the samesvcInProcess()remedy:capabilities.search:1403(PR #7937 / #7602) is untouched, per theboundary this card drew: that field describes the host's HTTP surface
(
enabled: false, already landed and correct); this field describes what isregistered, and stays presence-gated as before — only the
handlerReadyvalue for a filled slot changes.
Message wording choice
svcInProcess's sharedinProcessServiceMessage(name)opens "Kernel-internalservice — consumed in-process via the service registry" — true for
cache/queue/job(kernel-managed, in-process by construction) but falsefor
search: a registered search service is an external engine(Elasticsearch/Meilisearch, per
core-services.zod.ts's ownREMEDY_DETAIL['search']), not a kernel-internal one. Reusing the sharedsentence would misdescribe it, so
svcInProcessnow takes an optionalfallbackMessage(used bysearchalone;cache/queue/jobareunaffected — no third argument, same message as before) and
searchgets itsown wording:
Tests
Two new tests in
packages/runtime/src/http-dispatcher.test.ts, in thediscovery honest capabilities (D12)describe block, right after the #4318cache/queue/job tests:
reports an unmarked search occupant with no route and handlerReady false, not true (#7939)— registers a real, unmarked search-service-shapedoccupant (the assertion is vacuous otherwise, since
CORE_SERVICE_PROVIDER['search']isnulland nothing registers this slottoday) and asserts
enabled: true,status: 'available',handlerReady: false,route: undefined, and that the message is search's own wording(not the shared "Kernel-internal" sentence).
reports a self-describing search occupant (e.g. a dev stub) with its own status, still handlerReady false (#7939)— a__serviceInfo-markedoccupant keeps its own declared status/message,
handlerReadystays false.Reverse verification (commit-then-revert, never
git stash— seeAGENTS.md): committed the fix, then
git checkout HEAD~1 -- packages/runtime/src/http-dispatcher.tsto restore the pre-fixsvcAvailable(...)line while keeping the new tests, and re-ran just the#7939-tagged tests:Failed for exactly the right reason (
handlerReady: truefrom the oldsvcAvailableline), not a compile error or a vacuously-skipped assertion —the second test (the
__serviceInfo-marked stub) still passed, as expected,since
svcAvailablealready honors self-description. Then restored the fix(
git checkout claude/issue-7939-dispatcher-search-handler-ready -- packages/runtime/src/http-dispatcher.ts) and confirmed the working tree wasbyte-identical to the committed fix (
git status --shortempty).Full suite after restoring:
Type-check debt ratchet (
check:type-check-debt)packages/runtime'stsconfig.jsonexcludes*.test.ts, so the package's owntypecheckscript (clean) never compiled the new test code — the ratchet does,separately. My first draft of the new test read
info.services.search.routedirectly, which does not typecheck:
svcInProcess()'s return type carries noroutekey at all (route-less by construction), same ascache/queue/job.Fixed by casting the same way the existing
#4318tests already do(
(info.services as Record< string, any >).search). Re-measured@objectstack/runtime's TEST_DEBT by generating the same siblingtsconfig the gate itself generates (dropping the test exclude) and running
tsc --noEmitdirectly — 227 errors, matching the recorded ledger exactly,confirmed before and after the cast fix (228 → 227 with the cast fix,
i.e. it was a +1 regression from my own new code, now resolved to +0).
Local verification
pnpm --filter '@objectstack/runtime^...' build(dependency closure)pnpm --filter @objectstack/runtime exec vitest run src/http-dispatcher.test.ts --maxWorkers=2— 242/242 passpnpm --filter @objectstack/runtime typecheck— cleanpnpm exec turbo run typecheck --concurrency=2(repo-wide, per this card's Definition of done) — 126/126 tasks greennode scripts/check-nul-bytes.mjs— OKtsc --noEmit— 227, unchanged from the recorded ledgerOut of scope, left alone
capabilities.search(:1403) — PR fix(runtime): dispatchercapabilities.searchanswers a statedfalse, not bare slot presence (#7602) #7937 / finding: the runtime dispatcher'scapabilities.searchis still presence-gated, so it over-promises the moment the slot is filled #7602, untouched per thiscard's explicit boundary.
metadata-protocol's ownservices.search(a different producer,packages/metadata-protocol/src/protocol.ts) — it genuinely does mount areal
/api/v1/searchhandler viasearchAll(), so its presence-gatedroute/handlerReadyshape is legitimately different and alreadydeliberately pinned (
packages/objectql/src/protocol-discovery.test.ts,"should leave non-dispatcher-owned routes presence-gated"). No parity test
was added between the two producers for this field, for the same reason
the file's own comments give for not requiring one.
Generated by Claude Code