Skip to content

fix(runtime): search slot reports handlerReady:false, not true (#7939) - #7965

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7939-dispatcher-search-handler-ready
Aug 12, 2026
Merged

fix(runtime): search slot reports handlerReady:false, not true (#7939)#7965
hotlong merged 2 commits into
mainfrom
claude/issue-7939-dispatcher-search-handler-ready

Conversation

@hotlong

@hotlonghotlong commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Fixes#7939

What changed

packages/runtime/src/http-dispatcher.ts's getDiscoveryInfo() built
services.search with svcAvailable(undefined, undefined, searchSvc), which
defaults an unmarked occupant to handlerReady: true — contradicting the same
map's own stated contract a few lines above: handlerReady: true means "the
dispatcher has a real, bound handler for this route." The dispatcher has no
/search route or handler at all (no route-ledger.ts entry, no branch in
http-dispatcher.ts, route is always undefined at this call site), so a
filled slot reported "no route" and "handler is ready" in one breath.

This is the exact contradiction #4318 closed for cache/queue/job, applied
to the one slot of that shape it did not reach. search now uses the same
svcInProcess() remedy:

search: searchRegistered
? svcInProcess('search',searchSvc,SEARCH_IN_PROCESS_MESSAGE)
: svcUnavailable('search'),

capabilities.search:1403 (PR #7937 / #7602) is untouched, per the
boundary this card drew: that field describes the host's HTTP surface
(enabled: false, already landed and correct); this field describes what is
registered, and stays presence-gated as before — only the handlerReady
value for a filled slot changes.

Message wording choice

svcInProcess's shared inProcessServiceMessage(name) opens "Kernel-internal
service — consumed in-process via the service registry" — true for
cache/queue/job (kernel-managed, in-process by construction) but false
for search: a registered search service is an external engine
(Elasticsearch/Meilisearch, per core-services.zod.ts's own
REMEDY_DETAIL['search']), not a kernel-internal one. Reusing the shared
sentence would misdescribe it, so svcInProcess now takes an optional
fallbackMessage (used by search alone; cache/queue/job are
unaffected — no third argument, same message as before) and search gets its
own wording:

Search engine registered, but the dispatcher has no HTTP route for the
'search' slot — no dedicated search endpoint is mounted on its behalf.
Cross-object search, where a host serves it, is reported separately by
capabilities.search.

Tests

Two new tests in packages/runtime/src/http-dispatcher.test.ts, in the
discovery honest capabilities (D12) describe block, right after the #4318
cache/queue/job tests:

  • reports an unmarked search occupant with no route and handlerReady false, not true (#7939) — registers a real, unmarked search-service-shaped
    occupant (the assertion is vacuous otherwise, since
    CORE_SERVICE_PROVIDER['search'] is null and nothing registers this slot
    today) 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-marked
    occupant keeps its own declared status/message, handlerReady stays false.

Reverse verification (commit-then-revert, never git stash — see
AGENTS.md): committed the fix, then git checkout HEAD~1 -- packages/runtime/src/http-dispatcher.ts to restore the pre-fix
svcAvailable(...) line while keeping the new tests, and re-ran just the
#7939-tagged tests:

FAIL src/http-dispatcher.test.ts > HttpDispatcher > discovery honest capabilities (D12) > reports an unmarked search occupant with no route and handlerReady false, not true (#7939)
AssertionError: services.search.handlerReady: expected true to be false
- Expected: false
+ Received: true
Tests 1 failed | 1 passed | 240 skipped (242)

Failed for exactly the right reason (handlerReady: true from the old
svcAvailable line), not a compile error or a vacuously-skipped assertion —
the second test (the __serviceInfo-marked stub) still passed, as expected,
since svcAvailable already 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 was
byte-identical to the committed fix (git status --short empty).

Full suite after restoring:

pnpm --filter @objectstack/runtime exec vitest run src/http-dispatcher.test.ts --maxWorkers=2
Test Files 1 passed (1)
Tests 242 passed (242)

Type-check debt ratchet (check:type-check-debt)

packages/runtime's tsconfig.json excludes *.test.ts, so the package's own
typecheck script (clean) never compiled the new test code — the ratchet does,
separately. My first draft of the new test read info.services.search.route
directly, which does not typecheck: svcInProcess()'s return type carries no
route key at all (route-less by construction), same as cache/queue/job.
Fixed by casting the same way the existing #4318 tests already do
((info.services as Record< string, any >).search). Re-measured
@objectstack/runtime's TEST_DEBT by generating the same sibling
tsconfig the gate itself generates (dropping the test exclude) and running
tsc --noEmit directly — 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 pass
  • pnpm --filter @objectstack/runtime typecheck — clean
  • pnpm exec turbo run typecheck --concurrency=2 (repo-wide, per this card's Definition of done) — 126/126 tasks green
  • node scripts/check-nul-bytes.mjs — OK
  • Runtime TEST_DEBT re-measured directly against tsc --noEmit — 227, unchanged from the recorded ledger

Out of scope, left alone


Generated by Claude Code

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).
…et (#7939)
TS2339 on info.services.search.route: svcInProcess()'s return type carries
no route key at all, matching the same cast the #4318 cache/queue/job tests
already use. Re-measured @objectstack/runtime's TEST_DEBT at 227 (recorded),
unchanged.
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 9:03am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages/runtime)
  • content/docs/api/index.mdx(via @objectstack/runtime)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx(via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx(via packages/runtime)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx(via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/runtime)
  • content/docs/kernel/cluster.mdx(via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx(via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx(via packages/runtime)
  • content/docs/permissions/system-context.mdx(via packages/runtime)
  • content/docs/plugins/packages.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/runtime)

2 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/runtime)
  • content/docs/releases/v17.mdx(via @objectstack/runtime)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM review — domain:cli seat (#6024). Verdict: accept, pending the queued checks. No change requests.

The three things the dispatch was strictest about, all delivered

The second test — a __serviceInfo-marked stub keeping its own status/message while handlerReady stays false — was not asked for and is worth having: it pins that self-description wins for description but not for the route claim.

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: inProcessServiceMessage's "Kernel-internal service" is true for cache/queue/job and false for an external search engine. Threading an optional fallbackMessage — defaulting to the shared string so the other three slots are byte-identical — is the minimal way to say the true thing.

Leaving metadata-protocol's services.search alone. That is a different producer which genuinely mounts /api/v1/search via searchAll(), so its shape is legitimately different. "Make the two agree" would have been the plausible-looking wrong move.

Docs: verified clean — but the advisory above is not why

The docs-drift comment lists 20 pages. Checked with a control:

  • Target: handlerReady across content/docs/** → the page that actually specifies it is content/docs/kernel/services-checklist.mdx (9 hits: the default table at :192, the __serviceInfo contract at :187, "Only handlerReady is consulted, never status" at :197, and the search slot at :54/:79/:94/:488/:525).
  • Control: services. under content/docs/protocol/kernel/ → hits. Scanner works.

That page is not in the advisory's list of 20. Read it anyway: every mention of search there describes the empty-slot branch — "Nothing provides the slot at all — search. Discovery says exactly that" — which this PR leaves untouched (svcUnavailable('search')). No doc states what a filled search slot reports, because nothing has ever filled one. So no doc is made wrong, and no docs change is needed.

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 services-checklist.mdx never spells @objectstack/runtime (control: it names eight other @objectstack/* packages).

Staying draft until the queued checks finish; enqueueing on green.


Generated by Claude Code

@hotlong
hotlong marked this pull request as ready for review August 12, 2026 09:13
@hotlong
hotlong added this pull request to the merge queueAug 12, 2026
Merged via the queue into main with commit 0e4a7fbAug 12, 2026
27 checks passed
@hotlong
hotlong deleted the claude/issue-7939-dispatcher-search-handler-ready branch August 12, 2026 10:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: dispatcher services.search reports handlerReady: true for a slot with no mounted handler — the one slot #4318 did not reach

2 participants

@hotlong@claude