发现于 #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 不告诉你。两者至少该有一个说得出理由。
可达性(诚实说明,不预判严重度)
修法需要先做决定(所以只立单,不顺手改)
区分两者是可行的,但输出契约会变,方向不止一个:
倾向 A(与 #5412/#5413 一路的「两件事就是两件事,结构上分开」),但这会新增一种 doctor 输出状态,且要顺带决定它算 warning 还是 error —— 交分诊定夺。
相关
发现于 #5612 的定性调查(PR #5643)。不在 #5612 范围内:那单修的是测试面的可读性,这条是产品面的判定契约,修法要先做决定。
机制
packages/cli/src/commands/doctor.ts:869-878,readInstalledPackageEntries():这个 catch 覆盖了两件不同的事:
os doctor必须能在从未装过它的 checkout 里跑完(有专门的用例钉这条)。.objectstack/installed-packages/目录可能明明在场、里面装着条目,doctor 却当作「什么都没装」,D5e advisory 无话可说,于是打出:这正是 #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 里:
然后对着一个有坏条目的 ledger 跑
os doctor—— 每一项照跑,✓ Unique scope照打,ledger 相关的行(#5412 的目录级行、#5413 的条目级行)一行都不出现。#5612 正文里贴的那段输出就是这个状态下产生的,立单者由此得出「报告面丢了」的结论;实际上报告面完好,是这一层的静默把它盖住了。(完整对照见 PR #5643。)同仓内的反例:serve 就不静默
packages/cli/src/commands/serve.ts:1546-1569加载同一个包,同样用 try/catch,但失败时点名告警(告警文本里带上了err?.message):同一个 CLI、同一个可选包、同一种失败,serve 告诉你,doctor 不告诉你。两者至少该有一个说得出理由。
可达性(诚实说明,不预判严重度)
packages/cloud-connection的 worktree 都处于状态 2,doctor-ledger-read-failure.test.ts整块 7 条在 origin/main 上就是红的 ——os doctor对坏 ledger 一行都不报,#5413/#5424 的报告面测不出来 #5612 这张单本身就是这么产生的。修法需要先做决定(所以只立单,不顺手改)
区分两者是可行的,但输出契约会变,方向不止一个:
import.meta.resolve()(或createRequire().resolve())判定 specifier 能否解析。解析不了 = 没装,继续静默;能解析但求值抛错 = 在场且坏,走os doctor的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 那条failure路径报成 warning 行。语义最准,代价是多一次解析调用和一点平台差异。fs.existsSync(dir)为真却读不到包 → 报。实现最轻,但把「有没有装过东西」当成「包该不该在」的代理,语义是歪的。倾向 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:本条的发现路径。