Skip to content

discovery 的 cache/queue/job 槽位声明了三条不存在的 route —— 同一条记录里 handlerReady:false 与 route 互相矛盾 #4318

Description

@os-zhuang

按 Prime Directive #10 记录,#3898 / PR #4306 收尾时核对出的同类残留。当前无害(没有已知消费者读 services.*.route),记下来是因为它是构造上的谎,且与 #4114 / #4141 刚修掉的形态同款。

现象

packages/metadata-protocol/src/protocol.ts:1104-1106SERVICE_CONFIG 给这三个服务声明了 route:

cache:        { route: '/api/v1/cache' },
queue:        { route: '/api/v1/queue' },
job:          { route: '/api/v1/jobs' },

全仓提到这三条路径的地方,就是这三行本身 —— 没有 dispatcher 域(packages/runtime/src/domains/ 下没有 cache/queue/jobs)、没有 adapter 挂载、没有插件注册。

kernel 在默认 boot 时会给这三个槽位自动注入内存兜底(preInjectCoreFallbacks,三者在 core-services.zod.ts:154-156 都是 core 级),而这些兜底自报 handlerReady: false。于是每个默认部署的 discovery 都输出:

cache: status=degraded handlerReady=false route=/api/v1/cache
queue: status=degraded handlerReady=false route=/api/v1/queue
job:   status=degraded handlerReady=false route=/api/v1/jobs
routes map keys: data,metadata,i18n

(实证:CORE_FALLBACK_FACTORIES 全量注册进 ObjectStackProtocolImplementationgetDiscovery() 的真实输出。)

同一条 ServiceInfo 里,handlerReady: false 说"没有 HTTP handler",route 说"去这里调用"。比 #4089/#4130 那种"两个 builder 互相矛盾"更尖锐 —— 这是一条记录内部的自相矛盾。

顺带,两个 builder 也确实不一致:dispatcher 侧同一槽位是 svcAvailable(undefined, undefined, cacheSvc),route 为 undefined(http-dispatcher.ts:1145-1147)。

范围界定(为什么现在无害)

  • ApiRoutesSchema 没有 cache/queue/job 键,所以 discovery.routes 不带它们 —— 泄漏面仅限 services.<slot>.route;
  • objectui 不读 services.*.route(核对了 packages/react/src 全量);
  • 所以今天没有消费者会照着这条路由发请求。

但 D12 的动机是 AI agent 读这张表决定"我能不能用这个能力" —— 一条声明了路径的记录,正是给 agent 看的。

不是"暂时没实现",是"永远不会有"

CORE_SERVICE_PROVIDER 给这三个槽位指向 @objectstack/service-cache / service-queue / service-job。这三个包都存在,且都不挂任何 HTTP 路由 —— 它们是内部服务,没有 HTTP 语义。所以这三条 route 不是"等插件补上",是不会有人挂。

这与 realtime 的处境完全同构,而 realtime 早就按 D12 处理成 realtime: {} 了,注释就写着"nothing mounts /api/v1/realtime, so no route is advertised"。

两种修法,代价不同(需要拍板)

A. 摘掉 route(cache: {},与 realtime 同构)

最贴合"没人挂 ⇒ 不声明"的 D12 规则。代价是会走进 noHttpSurface 分支,连带两个语义变化:

  1. 一个真实的 service-cache(不带 marker)会从 available 变成 degraded。是否可接受取决于 available 在这里到底是什么意思 —— realtime 的先例说"没有 HTTP 面就不能说 available",但 cache 压根不是 HTTP 能力,消费者不会去调它;
  2. message 会变成 'In-process service only — no HTTP surface is mounted',对一个跨进程的 Redis cache 不准确(这句话的主语本来是 realtime)。要么接受,要么把这句 message 按槽位拆开。

B. 把三者纳入 advertisedRoute 的抑制条件(handlerReady === false ⇒ 不声明 route)

更保守:只抑制自报无 handler 的实现,真实插件的 status/message 一律不变。理由与 file-storage 留在 DISPATCHER_GATED_SERVICES 里完全同构(注释:"对该槽位 handlerReady 不是代理,它就是事实本身")。代价是集合名不再准确(不全是 dispatcher-gated,得改名),且一个不自报的假实现仍会拿到假 route。

倾向 A —— 它修的是根因(这条路由本就不该存在),B 只是让当前占槽者不触发。但 A 的第 1 条是对外可见的语义变化,值得先定。

关联

#3898#4089(#4114)、#4130(#4141)、#3891、ADR-0076 D12、#2462

核对于 origin/main @ 366105c

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions