fix(metadata,metadata-protocol,objectql): getDiagnosed —— get() 不再把 loader outage 答成「这一项没声明」 (#5840) - #6051
Draft
baozhoutao wants to merge 4 commits into
Draft
fix(metadata,metadata-protocol,objectql): getDiagnosed —— get() 不再把 loader outage 答成「这一项没声明」 (#5840)#6051baozhoutao wants to merge 4 commits into
baozhoutao wants to merge 4 commits into
Conversation
…adata-get-degraded
…oader outage 答成「没声明」
MetadataManager.loadDiagnosed 算出的 ADR-0110 D3 判定,在两跳内被丢掉:
load() 只取 .data,get() 再把 null 变 undefined。六个消费点因此对
「读不到」与「这一项没声明」拿到同一个 undefined。
新增 getDiagnosed(type, name) -> { data, degraded, errors },即 loadDiagnosed
的 registry-first 对应物,并在 IMetadataService 上声明为可选成员。get() 本体
逐字不变(含 register() 观察者依赖的 microtask 时序),零破坏。
本车道内被测量为 gating 的消费点按各自语境处置:
- getMetaItem / getMetaItemCached:degraded 且 registry 也无 -> 503,不再落到 404
- getMetaItemLayered 的 code 层:与它 overlay 层同规矩(code: null 会派生
lockSource,outage 可把 _lock:'full' 渲染成 editable)
- ObjectQLPlugin 的 object 事件重读:warn(写已落地、只是重读失败)而非 error
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
…adata-get-degraded
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 116 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 6, 2026
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5840
前提复核(开工第一件事,
origin/main)单上「判定被算出来、然后在两跳之内被丢掉」的链条原样成立,行号已漂、内容定位:
metadata-manager.tsasync get(type, name)—— registry 命中直返,否则const result = await this.load(type, name); return result ?? undefined;load()——return (await this.loadDiagnosed< T >(type, name, options)).data;loadDiagnosed()——return { data: null, degraded: errors.length > 0, errors };loadDiagnosed的 TSDoc 正是为这件事写的(ADR-0110 D3,正文已引),而它算出的degraded到get()的调用方那里已不可达。第一步:6 个消费点逐点定性(裁决要求,实测)
单上表格的行号全部漂了;按内容重新定位,并多测出一个单上没列的(第 7 行)。
metadata-protocolgetMetaItem第 2 步(MetadataService 层)protocol.ts:3719 一带metadata-protocolgetMetaItemLayeredcode 层protocol.ts:3932 一带code: null派生lockSourcemetadata-protocolrestoreArtifactRegistryViewprotocol.ts:7349 一带objectqlsubscribeToMetadataEventsplugin.ts:712 一带warn(含后果与修法)plugin-securitysyncEvaluatorRegistrypermission-set-projection.ts:398mcpagent promptmcp-server-runtime.ts:443service-datasource的getDatasource/getObjectplugin.ts:82permission-set-projection:398的定性值得单独说,因为它与单上的预判相反、且结论更省事:它读的是自己刚写进内存 registry 的 projection echo(isProjectionEcho(current))。而get()是 registry 优先 —— echo 若在,根本走不到 loader,degraded不可能为真;degraded为真则意味着 registry 里没有 echo,此时「提前 return、什么都不治」本就是正确动作。所以这一点对 outage/miss 之分结构上不敏感,不需要跟着改。单上说它「最值得先测」,测下来是这个结果,如实记。mcp:443与service-datasource:82都是真误述(outage 被答成 "Agent X not found" / 数据源不存在),但都 fail-closed、不放宽任何访问,且分属 cli / services 车道 —— 按跨座位协议交 PM 转席。生产端:
getDiagnosedpackages/metadata/src/metadata-manager.ts新增即
loadDiagnosed的 registry-first 对应物。这个「registry 优先」不是细节,而是消费点不能直接改用loadDiagnosed的原因:后者只走 loader,换过去会跳过内存 registry、解析出不同的条目。registry 命中一律degraded: false(它没问过任何 loader);干净 miss 也一律degraded: false。措辞用既有的
degraded,⛔ 未抄 #5897 的storeUnavailable—— 后者是刻意收窄的单存储读措辞,这里是 loader 集合语义("至少一个 loader 抛了且没人答出这一项"),两者不是一回事。#5897 的 ACCEPT 评论已把这条区分记在案。同时在
packages/spec/src/contracts/metadata-service.ts的IMetadataService上把它声明为可选成员,与loadDiagnosed同例(#4127 batch 4 的先例:调用点与实现早已一致,缺的只是契约)。get()本体零破坏 —— 而且是被一条用例真实拦下来才做对的get()逐字未改,签名未改。最初的实现是return (await this.getDiagnosed(type, name)).data;—— 语义完全等价、更整洁 —— 它让register-notifies-watchers.test.ts的「订阅者重读看得见新 body」变红:notifyWatchersLocal用void callback(event)派发、从不 await,于是那条用例能成立靠的是get()内部的 microtask 跳数;多一个 async 帧就翻。处置:放弃委托写法,保留三行重复,把原因常驻进
get()的 TSDoc;重复的风险从另一侧钉住(新用例断言get()与getDiagnosed().data在每个 case 上一致)。那条用例本身的脆弱性是本 PR 范围外的发现,已按 PD #10 单独立案 #6043(finding,未入队)。⛔ 方向 (b)(
get()在 degraded 时抛)未走:改公共契约,且 boot 侧禁抄的理由已由 #5897 常驻在objectql/plugin.ts的 TSDoc 里。诊断是提供给调用方的,不是强加的。消费端处置:⛔ 不一刀切,逐点论证
(1)
getMetaItem/getMetaItemCached→ 503。 这半边有一句 main 上自己写下、但当时只有四分之三为真的注释:getMetaItemCached的 404 旁写着「reaching here now means a real miss ——getMetaItemthrows 503 rather than answeringundefinedwhen the store could not be read」。那对 overlay 读成立(失败以 throw 到达),对 MetadataService 读不成立(失败被MetadataManagerwarn 掉、以普通undefined到达)。本 PR 让这句话成为真的,并把这段因果写进了那条注释。刻意窄:放在 registry 兜底之后才判。registry 命中是一份真实声明,答案里没有任何无依据的断言,照旧原样服务;只有整条链什么都没解析出来、即答案本会是「这个不存在」时,degraded 才改变结果。
(2)
getMetaItemLayered的 code 层 → 503,与它 overlay 层对称。 这是最锋利的一处:code: null不是耸肩,是这个方法正面声明「不存在打包/代码层定义」,并且响应从它派生 ——lockSource = code ?? overlay ?? {}喂给resolveLockState,于是 code 层声明了_lock: 'full'的条目,在「本该找到那个锁的读失败了」时被渲染成editable: true, deletable: true。可用性故障放宽授权面,正是 ADR-0110 D3 点名要禁的事,而这个方法的 overlay 半边早已拒绝这么做(#5707)—— 两半不对称,只是因为 loader 失败在这一侧看不见。(3)
restoreArtifactRegistryView→ 不改。 返回 void,undefined不产生任何面向调用方的答案,方法自身的契约就写着 best-effort、下次 reload 自愈。给它加日志只会往一条已声明为静默的路径上添噪 —— 那是 AGENTS「Degradation log levels」明确警告的过度套用。就地留了注记,免得下一位读者把「没改」当成「漏了」。(4)
objectql事件重读 →warn,不是error。 这一处没有照抄旁边restoreMetadataFromDb的error(#5897),级别是按 AGENTS 那一节自己的判定问句诚实问出来的:「有没有本代码声称已持久化的东西没落地,而系统看起来照常?」没有 —— 写入早已落进 metadata store(事件正是它的通告),失败的是一次重读,registry 继续服务它已持有的定义。这是功能性降级(本 kernel 的副本落后),不是持久性降级;升成 error 就是那条规则点名的镜像错误,而且它会在 outage 期间每个事件打一次,而 boot 那条每进程只打一次。级别归级别,代价不能不说:新的
warn交付后果(registry 保留上一版定义、无人重试、读取继续服务陈旧 schema 直到后续事件成功或进程重启)与修法,这两样它替换掉的那句 debug 一样都没有。与 #5998(#5897)的关系:互补,非取边
派发时 #5998 尚在合并队列,故先做 metadata-manager 半边与 metadata-protocol 消费点;它合入后
git merge origin/main(⛔ 未 rebase,两次,main中途又动过一次)再动objectql/plugin.ts。无冲突,两份改动在同一文件里各占其位:storeUnavailable—— boot 期loadMetaFromDb的单一存储读(protocol.ts:10581 /plugin.ts:1171)degraded—— 运行期 metadata 服务的 loader 集合读测试与反向验证(方向先预测,后运行)
生产端新建
packages/metadata/src/metadata-manager-get-diagnosed.test.ts;消费端扩展相邻文件protocol.metadata-store-outage.test.ts(#5532/#5707 的同一份覆盖,第三处读加入同一条规矩);objectql 侧新建plugin-metadata-event-outage.test.ts,与 #5998 的plugin-restore-metadata-outage.test.ts并列,并在文件头写明为什么级别不同。测试替身只声明subscribe/get/getDiagnosed,不含任何引擎写动词,故无delete/updatedispatch 需要check:engine-double-contract扫描、也无守卫可手抄。三肢反向验证,方向均在运行前写死,结果逐条相符:
getDiagnosed改回走load()丢判定:预测 4 红 / 5 绿,实测 4 红 / 5 绿,且正是点名的那四条。绿的五条全是关于 miss 的断言 —— miss 从来不是坏掉的那半边,这正是 ADR-0110 D3 的形状。if (… degraded)分支:预测 5 红 / 3 绿,实测 5 红 / 3 绿,上方 18 条 getMetaItem 的 overlay 读用裸 catch 把「sys_metadata 不可达」吞成「该项不存在」—— GET /meta/:type/:name 在存储故障时回一个无 code 的 400「not found」 #5532/getMetaItemLayered 的 overlay 读用裸 catch:sys_metadata 读失败时三层视图把「读不到」画成「没有 overlay」 #5707 用例全绿。metadataService.get(...):预测 3 红 / 3 绿,实测 3 红 / 3 绿。命令与真实输出(合并后重跑的结果)
推送前
git merge origin/main做了两次(⛔ 全程未 rebase);两侧packages/spec都动过,故按 AGENTS §10 重装、重建 spec 与三条依赖链后完整重跑,上列数字即最终合并态的结果。必答项
record.packageIdfrom a snake_case row — always undefined, every object overlay registers under the 'sys_metadata' sentinel at boot #4636(决策箱在途,loadMetaFromDb的 object 分支packageId登记):无交叠、无影响。本 PR 一行未碰loadMetaFromDb;getMetaItemLayered的 code 层与 loadMetaFromDb object branch readsrecord.packageIdfrom a snake_case row — always undefined, every object overlay registers under the 'sys_metadata' sentinel at boot #4636 的 snake_case 行读是两个不同轴(前者是「读没读到」,后者是「读到的行怎么取 packageId」)。loadMetaFromDb object branch readsrecord.packageIdfrom a snake_case row — always undefined, every object overlay registers under the 'sys_metadata' sentinel at boot #4636 若落地,落点在loadMetaFromDb内部,与本 PR 的诊断读法不相邻。check:durability-log-level结构性看不见「读接缝把故障答成空值」这一类 —— #4825 / #5108 全家都在闸门盲区里 #5186(check:durability-log-level对这一类盲区的门禁侧登记):变简单,且已部分被它接住。派发时它还是needs-user-decision;本 PR 开工期间其门禁侧已随 feat(scripts): durability 闸门新增「读接缝编造空值」规则 —— #4728/#4825/#5108 这一族终于有闸门了 (#5186) #5986 落地为check-durability-degradation-log-level.mjs里的 read-seam invention 规则,本 PR 在它下面跑绿(64 seam,none invents an unreported empty answer)。变简单的地方是定价:这一类盲区的两端(生产端要能表达判定、消费端要读它)本 PR 各给了一个可照抄的样本,且 objectql 那一处示范了「同一族、不同级别」的判定怎么论证 —— 门禁若要把get()这类registry-first 读纳入扫描面,现在有了一个getDiagnosed形状可以对齐,不必先发明一个。⛔ 未实现它(只读参照)。Generated by Claude Code