You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A finding, filed as a latent declared-≠-enforced gap rather than a live user-facing defect. Surfaced while reviewing #7541 / PR #7577; the third-producer half was named in that PR's "deliberately left alone" section, and this card exists so it does not evaporate when that PR merges.
Observed
There are two producers of capabilities.search, and #7541 only moved one of them.
producer
predicate
file
getDiscovery() — the REST/protocol face
typeof this.searchAll === 'function', ANDed with api.enableSearch
packages/metadata-protocol/src/protocol.ts, packages/rest/src/rest-server.ts (both fixed by #7541)
getDiscoveryInfo() — the runtime dispatcher face
hasSearch = !!searchSvc, i.e. bare service-slot presence
Today this is not a live defect, and that is the only reason it is a finding rather than a bug. Nothing in either repository registers the search slot (CORE_SERVICE_PROVIDER records 'search': null, verified across both repos, ADR-0115 Evidence 5), so hasSearch is false on every host that exists. And the dispatcher mounts no /search route at all — there is no search entry in route-ledger.ts and no handler in http-dispatcher.ts — so false is the honest answer for that host. Declared and served agree, by coincidence of an empty slot.
The gap is what happens when the slot is filled. Register any ISearchService on a dispatcher-served host — precisely what the slot exists for, per CoreServiceName's "Search Engine (Elastic/Meili)" — and that producer flips capabilities.search.enabled to true while the host still serves no /search route. A conforming client reads the document, calls the endpoint, and gets the router's 404. That is Prime Directive #10 in its ordinary direction — an advertised endpoint that 404s — and it is the same defect class #7541 just closed on the other producer, merely waiting on a precondition nobody has met yet.
Touching it would have broken a green pin for no measured gain.packages/runtime/src/discovery-schema-conformance.test.ts:245 asserts info.capabilities.search.enabled === false, and that assertion is correct for that producer today. Changing the predicate without deciding what the dispatcher's honest answer is would have turned a true pin red.
What a fix would have to decide
Not obvious enough to prescribe here — the point of the card is that someone decides deliberately rather than discovering it from a 404:
Or mount the domain, at which point the guard applies and the question answers itself.
Option 1 is the smaller change and is consistent with websockets immediately below it, which is pinned false with the reasoning stated inline (ADR-0076 D12, #2462).
Also worth folding into whatever lands: #5672's Ruling A closed the capability vocabulary and requires every key from every producer, answered from that producer's own facts. Two producers answering one key by two unrelated predicates is legal under that ruling only while both answers happen to be honest — which is the situation this card is about.
Filed unassigned and without a domain:* label so routing stays the triage seat's call.
Related: #7541 (the same defect on the REST/protocol producer, fixed), #5672 (the vocabulary ruling and the serveability rule), #4000 / #4058 (the dispatcher's own same-predicate doctrine), #2462 / ADR-0076 D12 (websockets pinned false with reasoning).
What this is
A finding, filed as a latent declared-≠-enforced gap rather than a live user-facing defect. Surfaced while reviewing #7541 / PR #7577; the third-producer half was named in that PR's "deliberately left alone" section, and this card exists so it does not evaporate when that PR merges.
Observed
There are two producers of
capabilities.search, and #7541 only moved one of them.getDiscovery()— the REST/protocol facetypeof this.searchAll === 'function', ANDed withapi.enableSearchpackages/metadata-protocol/src/protocol.ts,packages/rest/src/rest-server.ts(both fixed by #7541)getDiscoveryInfo()— the runtime dispatcher facehasSearch = !!searchSvc, i.e. bare service-slot presencepackages/runtime/src/http-dispatcher.ts(~L1111, emitted ~L1372)Today this is not a live defect, and that is the only reason it is a finding rather than a bug. Nothing in either repository registers the
searchslot (CORE_SERVICE_PROVIDERrecords'search': null, verified across both repos, ADR-0115 Evidence 5), sohasSearchisfalseon every host that exists. And the dispatcher mounts no/searchroute at all — there is nosearchentry inroute-ledger.tsand no handler inhttp-dispatcher.ts— sofalseis the honest answer for that host. Declared and served agree, by coincidence of an empty slot.The gap is what happens when the slot is filled. Register any
ISearchServiceon a dispatcher-served host — precisely what the slot exists for, perCoreServiceName's "Search Engine (Elastic/Meili)" — and that producer flipscapabilities.search.enabledtotruewhile the host still serves no/searchroute. A conforming client reads the document, calls the endpoint, and gets the router's 404. That is Prime Directive #10 in its ordinary direction — an advertised endpoint that 404s — and it is the same defect class #7541 just closed on the other producer, merely waiting on a precondition nobody has met yet.Why it was correctly left out of #7541
Two reasons, both worth keeping:
capabilities.search = {enabled:false}whileGET /api/v1/searchserves 200 with real hits #7541 was measured against a liveGET /api/v1/searchanswering 200 while the document saidfalse— a symptom this producer cannot exhibit, since it serves no such route.packages/runtime/src/discovery-schema-conformance.test.ts:245assertsinfo.capabilities.search.enabled === false, and that assertion is correct for that producer today. Changing the predicate without deciding what the dispatcher's honest answer is would have turned a true pin red.What a fix would have to decide
Not obvious enough to prescribe here — the point of the card is that someone decides deliberately rather than discovering it from a 404:
http-dispatcher.ts~L1111 states that every service whose HTTP surface is a dispatcher domain mirrors that domain's own guard (isServiceServeable), "same predicate ⇒ same answer" (plugin-dev 的 analytics dev stub 仍会填满槽位,dispatcher 不检查 handlerReady —— #3891 退役 shim 后同形状的最后一处残留(dev-only) #4000 / dispatcher 其余服务域仍只判槽位占用、不读 handlerReady —— #4000 在 analytics 一域落地后剩下的类推面 #4058).searchis not currently a dispatcher domain, which is exactly why it was never brought onto that rule — so the honest answer may simply be a hardcodedfalsewith a comment, until the dispatcher serves search.Option 1 is the smaller change and is consistent with
websocketsimmediately below it, which is pinnedfalsewith the reasoning stated inline (ADR-0076 D12, #2462).Also worth folding into whatever lands: #5672's Ruling A closed the capability vocabulary and requires every key from every producer, answered from that producer's own facts. Two producers answering one key by two unrelated predicates is legal under that ruling only while both answers happen to be honest — which is the situation this card is about.
Filed unassigned and without a
domain:*label so routing stays the triage seat's call.Related: #7541 (the same defect on the REST/protocol producer, fixed), #5672 (the vocabulary ruling and the serveability rule), #4000 / #4058 (the dispatcher's own same-predicate doctrine), #2462 / ADR-0076 D12 (
websocketspinned false with reasoning).