Repository navigation
os doctor 把「cloud-connection 没装」和「装了但加载不了」当成同一件事 —— ledger 目录在场也照打 clean bill(#5412 假 PASS 的上一层) #5644
Description
发现于 #5612 的定性调查(PR #5643)。不在 #5612 范围内:那单修的是测试面的可读性,这条是产品面的判定契约,修法要先做决定。
机制
packages/cli/src/commands/doctor.ts:869-878,readInstalledPackageEntries():
try {
mod = await import('@objectstack/cloud-connection');
} catch {
return { entries: [], skipped: [] };
}这个 catch 覆盖了两件不同的事:
- 该可选包不可解析 —— 没装。静默是对的、也是刻意的:
os doctor必须能在从未装过它的 checkout 里跑完(有专门的用例钉这条)。 - 该包在场但加载失败 —— dist 损坏、安装被中断、半装、产物与源码不匹配。这时
.objectstack/installed-packages/目录可能明明在场、里面装着条目,doctor 却当作「什么都没装」,D5e advisory 无话可说,于是打出:
✓ Unique scope No unconfirmed installation-wide uniques for this 'isolated' environment
这正是 #5412 修掉的那个形状,只是上移了一层 —— 从 readdir 边界移到 import 边界。#5412 自己的说法是「A false PASS is worse than a missing check, because it tells the operator to stop looking」,那句话对这一层同样成立:isolated posture 下的 installation-wide unique 恰恰是最不该被假 PASS 掩盖的约束。
证据(可直接复现)
在一个已构建的 worktree 里:
mv packages/cloud-connection/dist /tmp/x
然后对着一个有坏条目的 ledger 跑 os doctor —— 每一项照跑,✓ Unique scope 照打,ledger 相关的行(#5412 的目录级行、#5413 的条目级行)一行都不出现。#5612 正文里贴的那段输出就是这个状态下产生的,立单者由此得出「报告面丢了」的结论;实际上报告面完好,是这一层的静默把它盖住了。(完整对照见 PR #5643。)
同仓内的反例:serve 就不静默
packages/cli/src/commands/serve.ts:1546-1569 加载同一个包,同样用 try/catch,但失败时点名告警(告警文本里带上了 err?.message):
⚠ Marketplace/cloud-connection wiring failed: Failed to resolve entry for package "@objectstack/cloud-connection"
同一个 CLI、同一个可选包、同一种失败,serve 告诉你,doctor 不告诉你。两者至少该有一个说得出理由。
可达性(诚实说明,不预判严重度)
- 用户侧:需要「装了但坏了」——中断的 install、损坏的 dist、被裁剪的镜像层。不常见,但不是不可能,且一旦发生就是无声的假 PASS。
- 仓内开发侧:天天可达。任何没构建
packages/cloud-connection的 worktree 都处于状态 2,doctor-ledger-read-failure.test.ts整块 7 条在 origin/main 上就是红的 ——os doctor对坏 ledger 一行都不报,#5413/#5424 的报告面测不出来 #5612 这张单本身就是这么产生的。
修法需要先做决定(所以只立单,不顺手改)
区分两者是可行的,但输出契约会变,方向不止一个:
- A. 先解析再加载:用
import.meta.resolve()(或createRequire().resolve())判定 specifier 能否解析。解析不了 = 没装,继续静默;能解析但求值抛错 = 在场且坏,走os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 那条failure路径报成 warning 行。语义最准,代价是多一次解析调用和一点平台差异。 - B. 只在 ledger 目录在场时才把加载失败算作 finding:
fs.existsSync(dir)为真却读不到包 → 报。实现最轻,但把「有没有装过东西」当成「包该不该在」的代理,语义是歪的。 - C. 维持现状,把它写成明确的已知取舍,并在文档里说明 doctor 在这种状态下不体检 ledger。
倾向 A(与 #5412/#5413 一路的「两件事就是两件事,结构上分开」),但这会新增一种 doctor 输出状态,且要顺带决定它算 warning 还是 error —— 交分诊定夺。
相关
os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 /LocalManifestSource.list()静默丢弃损坏的 ledger 条目 —— 已装应用在 boot 时消失、在控制台列表里缺席,且没有任何一条日志 #5413 / fix(cloud-connection,cli):LocalManifestSource.list()报告读不出来的 ledger 条目 (#5413) #5424:同一族的假 PASS,已修的两层。os doctor的两条 ledger 健康行都锁在isolatedposture 之内 —— 换个 posture,同一个坏 ledger 一行都不报 #5429:同两行的另一个触发条件问题(posture 门禁),现象不同,不收敛。doctor-ledger-read-failure.test.ts整块 7 条在 origin/main 上就是红的 ——os doctor对坏 ledger 一行都不报,#5413/#5424 的报告面测不出来 #5612 / PR test(cli): make the doctor ledger e2e block fail legibly when cloud-connection is unbuilt (#5612) #5643:本条的发现路径。
Activity
claude commentedon Aug 5, 2026
分诊:入队
pm:queue+domain:cli。os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 readdir 层、LocalManifestSource.list()静默丢弃损坏的 ledger 条目 —— 已装应用在 boot 时消失、在控制台列表里缺席,且没有任何一条日志 #5413 条目层,均已合入),家族原则「两件事就是两件事,结构上分开」已被接受,本条只是同一原则上移到import边界。修向裁定按正文方案 A(先解析再加载:specifier 解析不了 = 没装,继续静默;可解析但求值抛错 = 在场且坏,走os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 的failure路径报 warning 行,severity 与os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 目录级行一致)。B 把「装没装过东西」当「包该不该在」的代理,语义是歪的;C 与已接受的家族原则相悖。维护者对方向与 severity 均可否决。packages/cli/src/commands/doctor.ts:869-878→domain:cli。os doctor的两条 ledger 健康行都锁在isolatedposture 之内 —— 换个 posture,同一个坏 ledger 一行都不报 #5429 同两行但触发条件不同,不收敛(正文已述);doctor-ledger-read-failure.test.ts整块 7 条在 origin/main 上就是红的 ——os doctor对坏 ledger 一行都不报,#5413/#5424 的报告面测不出来 #5612 / PR test(cli): make the doctor ledger e2e block fail legibly when cloud-connection is unbuilt (#5612) #5643 为发现路径;三仓关键词扫描零命中。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
认领(PM 会话
session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)claude/issue-5644-doctor-import-boundary../objectstack-5644(os-dev 自建)import.meta.resolve先解析再加载——解析不了 = 没装,维持静默;可解析但求值抛错 = 在场且坏,走os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 的failure路径报 warning 行,severity 与目录级行一致。packages/cli/src/commands/doctor.ts:869-878+ 测试(test(cli): make the doctor ledger e2e block fail legibly when cloud-connection is unbuilt (#5612) #5643 刚落的前置断言与本单互补,勿回退);serve.ts:1546-1569 的点名告警是仓内对照读法。与在飞 client SDK 的DeleteDataResult注释写着 "Spec: DeleteDataResponseSchema" 却声明deleted: boolean,而该 schema 声明的是success—— 类型骗过编译器,运行时读到 undefined #5638(client)不相交。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
开工(os-dev 会话
session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)claude/issue-5644-doctor-import-boundary../objectstack-5644os doctor必须能在从未装过该可选包的 checkout 里跑完,这条约束不动);可解析但求值抛错 = 在场且坏 → 走os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 的failure路径报 warning 行,severity 与目录级行一致,错误原文入 verbose 详情。packages/cloud-connection本体、不动serve.ts、不动packages/client/**(client SDK 的DeleteDataResult注释写着 "Spec: DeleteDataResponseSchema" 却声明deleted: boolean,而该 schema 声明的是success—— 类型骗过编译器,运行时读到 undefined #5638 在飞);test(cli): make the doctor ledger e2e block fail legibly when cloud-connection is unbuilt (#5612) #5643 刚落的beforeAll前置断言不回退(它防的是测试自身环境坏,本单报的是被检项目环境坏,两层各司其职)。Generated by Claude Code
Generated by Claude Code
docs(cli): state what loadOptionalPackage's specifier accepts (#5644)
baozhoutao commentedon Aug 5, 2026
ACCEPT → PR #5658(PM 会话
session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)验收依据:
createRequire().resolve()会 stat 入口文件、把「包在 dist 缺」塌回「没装」——恰是 issue 复现路径所在的态;import.meta.resolve不 stat、回答同一产物。三态拆分(absent 静默维持硬约束 / broken 报os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 同档 warning + verbose 原文 / loaded 正常),退出码不变。转 ready 挂 auto-merge(ESLint 结论已出),跟到 MERGED。
Generated by Claude Code
fix(cli): `os data delete` 的 `deleted` 输出键读的是幻影键,改读 spec 的 `success` (#…
fix(cli): `os doctor` tells a broken cloud-connection install from an…
fix(client)!: `DeleteDataResult` 声明它自称的 schema —— `success`,不是 `delet…