Skip to content

fix(metadata,metadata-protocol,objectql): getDiagnosed —— get() 不再把 loader outage 答成「这一项没声明」 (#5840) - #6051

Draft
baozhoutao wants to merge 4 commits into
mainfrom
claude/issue-5840-metadata-get-degraded
Draft

fix(metadata,metadata-protocol,objectql): getDiagnosed —— get() 不再把 loader outage 答成「这一项没声明」 (#5840)#6051
baozhoutao wants to merge 4 commits into
mainfrom
claude/issue-5840-metadata-get-degraded

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5840

前提复核(开工第一件事,origin/main

单上「判定被算出来、然后在两跳之内被丢掉」的链条原样成立,行号已漂、内容定位:

  • metadata-manager.ts async 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,正文已引),而它算出的 degradedget() 的调用方那里已不可达。

第一步:6 个消费点逐点定性(裁决要求,实测)

单上表格的行号全部漂了;按内容重新定位,并多测出一个单上没列的(第 7 行)。

消费点 现址 定性 本 PR 处置
metadata-protocol getMetaItem 第 2 步(MetadataService 层) protocol.ts :3719 一带 gating —— 落空即 404「不存在」 切诊断读法,degraded 且 registry 也无 → 503
metadata-protocol getMetaItemLayered code 层 protocol.ts :3932 一带 gating(最重) —— code: null 派生 lockSource 同上,与它 overlay 层对称
metadata-protocol restoreArtifactRegistryView protocol.ts :7349 一带 非 gating —— 返回 void,不对任何调用方作断言 刻意不改,就地留注记
objectql subscribeToMetadataEvents plugin.ts :712 一带 非 gating,但误述 —— debug 说「没有新 body」 切诊断读法,degraded → warn(含后果与修法)
plugin-security syncEvaluatorRegistry permission-set-projection.ts :398 对该区别不敏感(见下) ⛔ 跨车道,只测不改
mcp agent prompt mcp-server-runtime.ts :443 fail-closed 误述 ⛔ 跨车道,只测不改
(单上未列) service-datasourcegetDatasource/getObject plugin.ts :82 展示/内省,非 gating ⛔ 跨车道,只测不改

permission-set-projection:398 的定性值得单独说,因为它与单上的预判相反、且结论更省事:它读的是自己刚写进内存 registry 的 projection echoisProjectionEcho(current))。而 get() 是 registry 优先 —— echo 若在,根本走不到 loader,degraded 不可能为真;degraded 为真则意味着 registry 里没有 echo,此时「提前 return、什么都不治」本就是正确动作。所以这一点对 outage/miss 之分结构上不敏感,不需要跟着改。单上说它「最值得先测」,测下来是这个结果,如实记。

mcp:443service-datasource:82 都是真误述(outage 被答成 "Agent X not found" / 数据源不存在),但都 fail-closed、不放宽任何访问,且分属 cli / services 车道 —— 按跨座位协议交 PM 转席。

生产端:getDiagnosed

packages/metadata/src/metadata-manager.ts 新增

async getDiagnosed(type, name): Promise< { data: unknown | undefined; degraded: boolean; errors: string[] } >

loadDiagnosedregistry-first 对应物。这个「registry 优先」不是细节,而是消费点不能直接改用 loadDiagnosed 的原因:后者只走 loader,换过去会跳过内存 registry、解析出不同的条目。registry 命中一律 degraded: false(它没问过任何 loader);干净 miss 也一律 degraded: false

措辞用既有的 degraded,⛔ 未抄 #5897storeUnavailable —— 后者是刻意收窄的单存储读措辞,这里是 loader 集合语义("至少一个 loader 抛了且没人答出这一项"),两者不是一回事。#5897 的 ACCEPT 评论已把这条区分记在案。

同时在 packages/spec/src/contracts/metadata-service.tsIMetadataService 上把它声明为可选成员,与 loadDiagnosed 同例(#4127 batch 4 的先例:调用点与实现早已一致,缺的只是契约)。

get() 本体零破坏 —— 而且是被一条用例真实拦下来才做对的

get() 逐字未改,签名未改。最初的实现是 return (await this.getDiagnosed(type, name)).data; —— 语义完全等价、更整洁 —— 它让 register-notifies-watchers.test.ts 的「订阅者重读看得见新 body」变红:notifyWatchersLocalvoid callback(event) 派发、从不 await,于是那条用例能成立靠的是 get() 内部的 microtask 跳数;多一个 async 帧就翻。

处置:放弃委托写法,保留三行重复,把原因常驻进 get() 的 TSDoc;重复的风险从另一侧钉住(新用例断言 get()getDiagnosed().data 在每个 case 上一致)。那条用例本身的脆弱性是本 PR 范围外的发现,已按 PD #10 单独立案 #6043finding,未入队)。

⛔ 方向 (b)(get() 在 degraded 时抛)未走:改公共契约,且 boot 侧禁抄的理由已由 #5897 常驻在 objectql/plugin.ts 的 TSDoc 里。诊断是提供给调用方的,不是强加的。

消费端处置:⛔ 不一刀切,逐点论证

(1) getMetaItem / getMetaItemCached → 503。 这半边有一句 main 上自己写下、但当时只有四分之三为真的注释:getMetaItemCached 的 404 旁写着「reaching here now means a real miss —— getMetaItem throws 503 rather than answering undefined when the store could not be read」。那对 overlay 读成立(失败以 throw 到达),对 MetadataService 读不成立(失败被 MetadataManager warn 掉、以普通 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 这一处没有照抄旁边 restoreMetadataFromDberror#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无冲突,两份改动在同一文件里各占其位:

测试与反向验证(方向先预测,后运行)

生产端新建 packages/metadata/src/metadata-manager-get-diagnosed.test.ts;消费端扩展相邻文件 protocol.metadata-store-outage.test.ts#5532/#5707 的同一份覆盖,第三处读加入同一条规矩);objectql 侧新建 plugin-metadata-event-outage.test.ts,与 #5998plugin-restore-metadata-outage.test.ts 并列,并在文件头写明为什么级别不同。测试替身只声明 subscribe/get/getDiagnosed,不含任何引擎写动词,故无 delete/update dispatch 需要 check:engine-double-contract 扫描、也无守卫可手抄。

三肢反向验证,方向均在运行前写死,结果逐条相符:

⚠️ 一条与模板预设不符、如实记下:肢 A 翻不红任何消费端用例。消费端喂的是服务替身(直接投喂返回契约),所以只有肢 B/C 能翻它们。要证消费端读了这个判定,必须删消费端的读 —— 三肢各证一段,合起来才是整条链路。(与 #5998 记录的同一条性质,此处是三肢版。)

命令与真实输出(合并后重跑的结果)

pnpm --filter @objectstack/metadata test
  Test Files  26 passed (26)        Tests  517 passed (517)
pnpm --filter @objectstack/metadata-protocol test
  Test Files  49 passed (49)        Tests  495 passed (495)
pnpm --filter @objectstack/objectql test
  Test Files  131 passed (131)      Tests  2153 passed (2153)
pnpm --filter @objectstack/runtime test          (boot 路径联测)
  Test Files  102 passed (102)      Tests  1476 passed (1476)

pnpm --filter @objectstack/spec typecheck        Done(含 check:test-typecheck OK)
pnpm --filter @objectstack/objectql typecheck    Done
  ⚠️ metadata / metadata-protocol 两包**没有** typecheck 脚本(在 #4311 DEBT 台账内),
     故此处无法交付它们的 tsc 输出 —— 不编造,如实记;
     check:type-check-coverage 全绿(62/77 + root,15 在台账)。

pnpm --filter @objectstack/spec check:generated  All 10 generated artifacts are up to date.
node scripts/check-nul-bytes.mjs                 OK(5783 个受跟踪文本文件,无裸控制字节)
node scripts/check-durability-degradation-log-level.mjs
  ✓ 24 seam(s) all loud;✓ read-seam invention (#5186) 64 seam(s),none invents an unreported empty answer
node scripts/check-startup-registry-verdict.mjs  ✓ 40 seam(s),none recording a contradictable verdict
node scripts/check-engine-double-contract.mjs    OK — 73 pinned / 133 DEBT / 2 exempt

推送前 git merge origin/main 做了两次(⛔ 全程未 rebase);两侧 packages/spec 都动过,故按 AGENTS §10 重装、重建 spec 与三条依赖链后完整重跑,上列数字即最终合并态的结果。

必答项


Generated by Claude Code

claude added 4 commits August 6, 2026 15:11
…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
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 3:44pm

Request Review

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/metadata-protocol, @objectstack/metadata, @objectstack/objectql, @objectstack/spec.

116 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, @objectstack/metadata, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via packages/metadata, @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/metadata, @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/metadata, @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/metadata-service.mdx (via @objectstack/metadata)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/metadata, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @objectstack/metadata, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MetadataManager.get() 丢弃 loadDiagnosed 的 degraded 判定:loader 读不到与「这一项没声明」在 6 个消费点上不可分辨

2 participants