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`: ```js 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`: ```js 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 是同一类东西 —— 区别只是**前者连自报都没做到**。 ## 建议 1. `readServiceSelfInfo` 增加对 `_fallback: true` 的归一化(→ `status: 'degraded'`,配一句 message 指向对应的 ServicePlugin),与现有 `_dev` 分支同构; 2. `data:` 补上第三参; 3. `metadata:` 改为读实例(真 manager → available,内存兜底 → degraded),`protocol.ts:1393` 同步; 4. 补一条门:**遍历所有已知 fake(3 种标记)逐一注册进槽位,断言 discovery 不把任何一个报成 `available`** —— 这类洞会随着新增兜底不断复发,靠人肉 review 拦不住。 关联:#3878、#3891、ADR-0076 D12、#2462。 <sub>核对于 `origin/main` @ 93f267f81。</sub>
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…