Skip to content

os doctor 把「cloud-connection 没装」和「装了但加载不了」当成同一件事 —— ledger 目录在场也照打 clean bill(#5412 假 PASS 的上一层) #5644

Description

@baozhoutao

发现于 #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 覆盖了两件不同的事:

  1. 该可选包不可解析 —— 没装。静默是对的、也是刻意的:os doctor 必须能在从未装过它的 checkout 里跑完(有专门的用例钉这条)。
  2. 该包在场但加载失败 —— 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 —— 交分诊定夺。

相关

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions