fix(metadata-protocol,objectql): loadMetaFromDb 用返回值表达「没读到存储」,boot 侧不再把 outage 记成空库 (#5897) - #5998
Merged
Merged
Conversation
…再把 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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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 #5897
前提复核(裁决要求,开工第一件事)
PM 裁 A 带两条前提,均在
origin/main上复核成立:前提 1 —— 返回值的生产消费方仍只有
restoreMetadataFromDb。 成立。git grep -n loadMetaFromDb全仓命中中,读返回值的非测试代码只有packages/objectql/src/plugin.ts:1140。其余源码命中全部是注释(
registry.ts:1444、metadata-core/src/objects/index.ts:14、plugin-security/src/permission-set-projection.ts:264、driver-sql/src/sql-driver.ts:1796、metadata-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 放最前:一次没发生的读,它的计数不该压过「读没成功」
这件事。
刻意没做
一次瞬时故障变成彻底停机。变的只是「降级」不再被当成「健康」。
errors的超集。errors数的是「行到了、水合失败」,这个位说的是「行集根本没到」。命名取
storeUnavailable而非裸degraded,正是为了不被读成「哪里出了点事」;新增了一条用例钉住「坏行不置位」这一面。
loadMetaFromDbobject 分支的packageId登记行(loadMetaFromDb object branch readsrecord.packageIdfrom a snake_case row — always undefined, every object overlay registers under the 'sys_metadata' sentinel at boot #4636 决策箱在途),不碰
metadata-manager.ts(MetadataManager.get() 丢弃 loadDiagnosed 的 degraded 判定:loader 读不到与「这一项没声明」在 6 个消费点上不可分辨 #5840 的面),不改生产端那条warn:它和消费端的
error是同一事件的两个高度 —— 前者报驱动原文(调试用),后者报代价与修法。留住前者,才让后者能是唯一那句「后果」陈述。
对鸭子类型实现者的影响:零破坏(取舍如实说明)
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/updatedispatch 需要check:engine-double-contract扫描,也没有守卫可手抄。反向验证做了两肢,方向均在运行前写死在文件头,结果与预测逐条相符:
storeUnavailable = true: 生产端 4 红(ECONNREFUSED、非 Error 拒绝、未识别措辞、outage-vs-空库),13 绿(全部良性措辞 + 工作存储
对照 +
stored-conversions)。if (storeUnavailable)分支: 消费端 4 红,5 绿。协议替身(直接投喂返回契约),所以只有肢 B 能翻它。要证消费端读了这个位,
必须删消费端的读,而不是删生产端的写 —— 两肢各证一半,合起来才是完整链路。
命令与真实输出
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