Skip to content

fix(metadata-protocol,objectql): loadMetaFromDb 用返回值表达「没读到存储」,boot 侧不再把 outage 记成空库 (#5897) - #5998

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-5897-hydration-outage-diagnostic
Aug 6, 2026
Merged

fix(metadata-protocol,objectql): loadMetaFromDb 用返回值表达「没读到存储」,boot 侧不再把 outage 记成空库 (#5897)#5998
baozhoutao merged 2 commits into
mainfrom
claude/issue-5897-hydration-outage-diagnostic

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5897

前提复核(裁决要求,开工第一件事)

PM 裁 A 带两条前提,均在 origin/main 上复核成立:

前提 1 —— 返回值的生产消费方仍只有 restoreMetadataFromDb 成立。
git grep -n loadMetaFromDb 全仓命中中,读返回值的非测试代码只有
packages/objectql/src/plugin.ts:1140。其余源码命中全部是注释
(registry.ts:1444metadata-core/src/objects/index.ts:14
plugin-security/src/permission-set-projection.ts:264
driver-sql/src/sql-driver.ts:1796metadata-protocol/src/plugin.ts:39),
测试侧 5 个文件调用它但都是断言,不构成生产消费方。

前提 2 —— ProtocolWithDbRestore 不在 packages/spec 公开契约面。 成立。
git grep -ln ProtocolWithDbRestore 全仓只命中一个文件
packages/objectql/src/plugin.ts;packages/spec 零命中。该接口是消费侧自己
声明的结构化(鸭子)类型,不是 wire contract。

改了什么

loadMetaFromDb 的返回值 { loaded, errors, invalid } 没有任何字段能表达
「这次水合根本没读到存储」—— 读不到的数据库与真正的空库都答 loaded: 0
唯一消费方因此无从分支:它唯一的分支只是在两条日志之间做选择,而「什么都没
回来」那一侧是 debug 级的 No persisted metadata found in database。于是
一个字都没读到持久化元数据的 kernel,在 debug 级上宣称「本来就没有」,然后
照常报告 ready。

代价不是假设,写在 plugin.ts 自己的 Phase 2 注释里:registry 为空时
registry.getObject 把「读不到」答成「没声明」—— unknown-column 查询守卫、
hooks、relationships 静默降级,overlay 对象既不建表也不桥接。ADR-0110 D3
(outage ≠ miss)在 boot 侧的落地,继 #5108 / #5089 / #5532 / #5707 之后。

生产端(packages/metadata-protocol/src/protocol.ts):返回值加
storeUnavailable: boolean,只在已经打印 [Protocol] DB hydration skipped
的那条分支上置位 —— 即 isMissingTableError 判为非良性的读失败。#5841 刚落地的
「未建表」良性分支不置位,那里 loaded: 0 确是事实。

消费端(packages/objectql/src/plugin.ts):ProtocolWithDbRestore 声明该
字段并读它,outage 打 error 级,按 AGENTS「Degradation log levels」交付两样
东西 —— 后果(什么都没恢复、kernel 仍报健康、哪些能力静默降级)与修法(查
sys_metadata 背后的数据源:连接、凭据、表是否存在,然后重启)。可读的空库照旧
debug。分支顺序刻意把 outage 放最前:一次没发生的读,它的计数不该压过「读没成功」
这件事。

刻意没做

对鸭子类型实现者的影响:零破坏(取舍如实说明)

ObjectQLPlugin 用结构化匹配拿 protocol 服务,新字段在消费侧声明为
optional
(与既有的 invalid 同例),旧 shim 照常通过类型检查,并被读成「不是
outage」—— 正是它此前唯一能表达的判定,行为逐字不变(有专门用例钉住)。

取舍值得点名:optional 意味着无法强迫第三方 shim 开始上报 outage,这类 shim
会和今天一样沉默。改成 required 可以让这件事无法忽略,代价是为一个目前只有一个
仓内生产者会置位的位,破坏所有外部实现者。仓内生产者
(ObjectStackProtocolImplementation)自己声明并返回 required,所以每个真实
ObjectStack kernel 实际走的那条路是全覆盖的。

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

生产端扩展 #5841 的相邻文件 protocol.load-meta-hydration-benign.test.ts(而非
另开孤立文件):其中「事实 2 等价性」用例被翻面 —— 它原本断言 outage 与空库
不可分辨,并留了「契约长出表达方式时这条断言 EXPECTED 要翻,别删」的注记;现在断言
两者可分辨,且只由该位分辨(所有计数仍逐字相等,downstream 无法从
loaded/errors/invalid 重建这个区别、长出第二套弱问法)。

消费端新建 packages/objectql/src/plugin-restore-metadata-outage.test.ts(跨包,
无相邻文件可扩)。协议替身只声明 loadMetaFromDb —— 既是类型守卫探测的全部面,
也是该方法调用的全部面;不声明任何写动词,故没有 delete/update dispatch 需要
check:engine-double-contract 扫描,也没有守卫可手抄。

反向验证做了两肢,方向均在运行前写死在文件头,结果与预测逐条相符:

  • 肢 A —— 删生产端 storeUnavailable = true: 生产端 4 红(ECONNREFUSED、
    非 Error 拒绝、未识别措辞、outage-vs-空库),13 绿(全部良性措辞 + 工作存储
    对照 + stored-conversions)。
  • 肢 B —— 删消费端 if (storeUnavailable) 分支: 消费端 4 红,5 绿。

⚠️ 一条与模板预设不符、如实记下:肢 A 不会让消费端变红。消费端用例喂的是
协议替身(直接投喂返回契约),所以只有肢 B 能翻它。要证消费端读了这个位,
必须删消费端的读,而不是删生产端的写 —— 两肢各证一半,合起来才是完整链路。

命令与真实输出

pnpm --filter @objectstack/metadata-protocol test
  Test Files  49 passed (49)        Tests  487 passed (487)

pnpm --filter @objectstack/objectql test
  Test Files  129 passed (129)      Tests  2140 passed (2140)

pnpm --filter @objectstack/objectql typecheck
  tsc --noEmit → Done

pnpm --filter @objectstack/runtime test      (boot 路径联测)
  Test Files  102 passed (102)      Tests  1474 passed (1474)

node scripts/check-durability-degradation-log-level.mjs
  OK — 24 durability-critical catch seam(s), all loud, rethrowing or propagating
node scripts/check-startup-registry-verdict.mjs
  OK — 40 seam(s) across 1455 file(s), none recording a contradictable verdict
node scripts/check-nul-bytes.mjs
  OK (5754 tracked text files; no raw ASCII control bytes)
pnpm --filter @objectstack/spec check:generated
  All 10 generated artifacts are up to date.

typecheck 第一轮真红过一次,如实记:Logger 契约声明的是
error(message, error?: Error, meta?),两参写法只在 ObjectLogger 上过得去
(它额外容忍 meta 占 error 槽),对声明契约是越界。已按契约把计数放第三槽、
error 槽留空(这里确实没有 Error 可传:驱动原文已由生产端在 warn 上打过),
并在用例里按位置钉住。

推送前已 git merge origin/main(7 个提交,无冲突,⛔ 未 rebase),重装、重建
spec 与 objectql 依赖链后全部重跑 —— 以上数字即合并后的结果。


🤖 Generated with Claude Code

https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We


Generated by Claude Code

claude added 2 commits August 6, 2026 13:49
…再把 outage 记成空库 (#5897)

`loadMetaFromDb` 的返回值 `{ loaded, errors, invalid }` 没有任何字段能表达
「这次水合根本没读到存储」—— 读不到的数据库与真正的空库都答 `loaded: 0`。

其唯一生产消费方 `ObjectQLPlugin.restoreMetadataFromDb` 因此无从分支:它唯一
的分支只是在两条日志之间做选择,而「什么都没回来」那一侧是 debug 级的
`No persisted metadata found in database`。于是一个一个字都没读到持久化元数据
的 kernel,在 debug 级上宣称「本来就没有」,然后照常报告 ready。

代价写在 plugin.ts Phase 2 注释里:registry 为空时 `registry.getObject` 把
「读不到」答成「没声明」—— unknown-column 查询守卫、hooks、relationships 静默
降级,overlay 对象既不建表也不桥接。这是 ADR-0110 D3(outage ≠ miss)在 boot
侧的落地,继 #5108 / #5089 / #5532 / #5707 之后。

- 生产端:返回值加 `storeUnavailable: boolean`,只在已经打印
  `[Protocol] DB hydration skipped` 的那条分支上置位 —— 即 `isMissingTableError`
  判为非良性的读失败。未建表的首次启动(#5841)不置位,那里 `loaded: 0` 确是事实。
- 消费端:读该位并打 **error** 级日志,按 AGENTS「Degradation log levels」写清
  后果(什么都没恢复、kernel 仍报健康、哪些能力静默降级)与修法(查 sys_metadata
  背后的数据源:连接、凭据、表是否存在,然后重启)。可读的空库照旧 debug。

⛔ 不改控制流:boot 继续降级运行 —— 对着读不到的 overlay 存储拒绝启动,会把一次
瞬时故障变成彻底停机。变的只是「降级」不再被当成「健康」。

对 `ProtocolWithDbRestore` 鸭子类型实现者零破坏:新字段在消费侧声明为 optional
(与既有的 `invalid` 同例),旧 shim 照常通过类型检查并被读成「不是 outage」——
正是它此前唯一能表达的判定。

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 2:09pm

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, @objectstack/objectql)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

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.

@baozhoutao
baozhoutao marked this pull request as ready for review August 6, 2026 14:23
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit bbdbf28 Aug 6, 2026
24 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5897-hydration-outage-diagnostic branch August 6, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants