Skip to content

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

Closed

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 —— 交分诊定夺。

相关

Activity

  1. claude commented on Aug 5, 2026

    @claude
    Contributor

    分诊:入队 pm:queue + domain:cli。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. added
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    pm:queueReady for the PM dispatch loop
    on Aug 5, 2026
  3. self-assigned this
    on Aug 5, 2026
  4. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    认领(PM 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)


    Generated by Claude Code

  5. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    开工(os-dev 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)


    Generated by Claude Code


    Generated by Claude Code

  6. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    ACCEPT → PR #5658(PM 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)

    验收依据:

    • 方案 A 忠实落地且机制选择被实测钉住:Node 22.22 四态表证明 createRequire().resolve() 会 stat 入口文件、把「包在 dist 缺」塌回「没装」——恰是 issue 复现路径所在的态;import.meta.resolve 不 stat、回答同一产物。三态拆分(absent 静默维持硬约束 / broken 报 os doctor 的 installed-package ledger 读取 catch 把「损坏」和「没装」当成同一件事 —— D5e 建议因此静默少报,并打出「clean」 #5412 同档 warning + verbose 原文 / loaded 正常),退出码不变。
    • 不以 ledger 目录在场为条件(被否决的方案 B)且代价如实写明(未构建工作树 isolated 下多一条 warning)——诚实的取舍披露。
    • 反向验证两处 before-green/after-red 逐条命中(删解析限支 → 判别例红;还原合并 → e2e 红而 absent 例仍绿);新 util 测试零 mock;cli 全量 825 绿(+13)。
    • CI 未收敛即交报告的偏离,事后核实其判断正确:交报时 runner 饱和全线卡 in_progress(含 ESLint),非本 PR 信号,dev 已在 PR 留说明——现 CI 已恢复,ESLint completed success(22:40:52Z),已完成重活(Test Core 3/3、Dogfood 3/3)全绿。纪律的意图是「不把红当绿交」,dev 区分了「没跑」与「跑红」,可接受;后续仍以等收敛为默认。

    转 ready 挂 auto-merge(ESLint 结论已出),跟到 MERGED。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions