Repository navigation
Description
ADR-0076 D12 / #2462 立的规矩是:自认是 stub / dev fake / degraded 的服务,discovery 必须照实报,永远不报 available,免得 AI agent 和 Console 把假能力当真能力。这个机制本身是对的(#3878 里的 analytics shim 就是靠它自报 degraded 的),但它目前有三个洞。
洞 1 —— 标记有三种,诚实层只认两种
packages/spec/src/api/discovery.zod.ts:129 的 readServiceSelfInfo 识别:
svc[SERVICE_SELF_INFO_KEY](__serviceInfo)svc[SERVICE_DEV_MARKER_KEY](plugin-dev 的_dev: true)
但 packages/core/src/fallbacks/* 的内存兜底用的是第三种:_fallback: true
(memory-cache.ts:15、memory-i18n.ts:126、memory-job.ts:13、memory-metadata.ts:24、memory-queue.ts:14)。
readServiceSelfInfo 不认识它 → 返回 undefined → http-dispatcher.ts:888 的 svcAvailable 落到默认分支 { status: 'available', handlerReady: true }。
目前真正上线的是 i18n:packages/runtime/src/app-plugin.ts:1173 在 stack 声明了 translations 但没装 I18nServicePlugin 时自动 registerService('i18n', createMemoryI18n())。于是 discovery 对外说"i18n 完全可用",实际是个进程内存实现。
(cache / queue / job 的 _fallback 版本目前只被 plugin-dev 消费,且它在外面套了 _dev: true,所以那三个槽位没有暴露;createMemoryMetadata 我没找到运行时注册点。)
全仓唯一消费 _fallback 的地方是一行日志:app-plugin.ts:1227。
洞 2 —— data 槽位根本没把实例传进去
packages/runtime/src/http-dispatcher.ts:943:
data: svcAvailable(routes.data, 'kernel'),svcAvailable(route?, provider?, svc?) 的第三参是被检查的对象。这里省略了 → readServiceSelfInfo 从不被调用 → 无论槽里装的是谁(包括 plugin-dev 的 data stub)永远报 available / handlerReady: true。
对比同一段里其它槽位都规规矩矩传了实例(auth/analytics/i18n/cache/...),这看起来是漏了一个参数,不是有意的。
洞 3 —— metadata 硬编码 degraded,方向反了
http-dispatcher.ts:942:
metadata: { enabled: true, status: 'degraded' as const, handlerReady: true,
route: routes.metadata, provider: 'kernel',
message: 'In-memory registry; DB persistence pending' },不看实例、恒为 degraded。所以装了真正的 MetadataManager(有 DB 持久化)的部署,discovery 依然对外说"内存注册表,DB 持久化待办"。前两个洞是"假的报成真的",这个是"真的报成假的" —— 同一张表里两个方向都不可信。
packages/metadata-protocol/src/protocol.ts:1393-1394 有一处对称问题:metadata / data 被硬编码成 status: 'available', provider: 'objectql'。
为什么值得修
这张表不是装饰:
objectui/packages/react/src/hooks/useDiscovery.ts:190按status === 'available' || 'degraded'决定一个能力是否可用(它正确地拒绝了stub,是唯一做了区分的消费者);objectui/.../ConditionalAuthWrapper.tsx:114直接读services.auth.status === 'degraded'改变渲染;- D12 的原始动机就是 AI agent 会读这张表来决定"我能不能用这个能力"。
一个报 available 的内存 i18n,和 #3878 里那个报 degraded 的 analytics shim 是同一类东西 —— 区别只是前者连自报都没做到。
建议
readServiceSelfInfo增加对_fallback: true的归一化(→status: 'degraded',配一句 message 指向对应的 ServicePlugin),与现有_dev分支同构;data:补上第三参;metadata:改为读实例(真 manager → available,内存兜底 → degraded),protocol.ts:1393同步;- 补一条门:遍历所有已知 fake(3 种标记)逐一注册进槽位,断言 discovery 不把任何一个报成
available—— 这类洞会随着新增兜底不断复发,靠人肉 review 拦不住。
关联:#3878、#3891、ADR-0076 D12、#2462。
核对于 origin/main @ 93f267f。
Activity
os-zhuang commentedon Jul 31, 2026
核对当前 main,三个洞已全部由已合并的 PR 修复,处置如下:
_fallback不被识别)→ fix(core,plugin-dev,runtime): every in-memory fallback and dev stub self-describes honestly (#4058 step 1) #4082(dispatcher 其余服务域仍只判槽位占用、不读 handlerReady —— #4000 在 analytics 一域落地后剩下的类推面 #4058 step 1,即 core 的内存 fallback 带 _fallback: true 而非 __serviceInfo,readServiceSelfInfo 不识别 —— AppPlugin 注册的 i18n fallback 在 discovery 里报 available #4089):走了与建议 1 相反的路线 —— 不教readServiceSelfInfo认第三种标记,而是五个内存兜底全部改带标准__serviceInfo(status=degraded,message 逐个点名缺什么),_fallback从生产代码中消失。上线路径(app-plugin.ts自动注册的 i18n fallback)现在如实报 degraded。data不传实例)→ fix(runtime,metadata-protocol):data槽位也改为计算,硬编块就此清空 (#4130) #4141(discovery 的data槽位仍是硬编available—— 目前只靠 plugin-dev 的加载顺序约定兜住,不是构造上的保证 #4130):第三参补上,并加了跨 builder 一致性 pin。metadata硬编码 degraded;protocol.ts硬编码 available)→ fix(runtime,metadata-protocol): 两个 discovery builder 计算metadata槽位,不再互相矛盾 (#4089) #4114:两个 builder 都改为读实现自描述,两个方向相反的硬编码一并消灭。剩余的建议 4(防复发门)由 #4306 补齐:遍历
CORE_FALLBACK_FACTORIES(stub 表退役、_fallback消灭后,这就是完整的已知 fake 清单)逐槽注册,断言两个 discovery builder 都绝不报available。补门时发现cache/queue/job三个槽位此前在 discovery 层没有任何 pin —— 删掉第三参不会有测试变红,门的负向验证证实了这一点。该 PR 合并后本 issue 关闭。os-zhuang commentedon Jul 31, 2026
收尾核对时又划出两条同类残留,已按 Prime Directive #10 分别记录:#4318(cache/queue/job 槽位声明了三条无人挂载的 route)、#4319(
_dev遗留标记生产者已清零,可按 enforce-or-remove 收敛到唯一的__serviceInfo)。test(runtime,objectql): the D12 honesty gate iterates the fake invent…