fix(cli): os doctor 不再在 installed-package ledger 读不到时打出 ✓ Unique scope (#5412) - #5414
Merged
Merged
Conversation
`readInstalledPackageEntries()` 用一个不带绑定的 `catch` 罩住了两件性质完全
不同的事,并对两者都返回空数组:
1. `@objectstack/cloud-connection` 解析不到 —— 可选包没装。这一条静默是
对的,保持不变:`os doctor` 必须能在没有该包的 checkout 里跑完。
2. ledger 目录**存在**(`fs.existsSync` 已经通过)但读取抛错。
第 2 种被当成第 1 种,于是它以「没有已装包」的身份抵达 ADR-0120 D5e 的
unique-scope 建议,建议无话可说,报告打出 `✓ Unique scope`。一个装了包、
ledger 却读不到的 isolated 环境拿到的是一张干净体检单 —— false PASS 比漏报
更糟,它让操作者停止查看。
两者现已拆开:`import()` 失败保留自己的静默 catch;读取失败带着 cause 回到
调用方,成功行被扣住(它是对建议**两半**的断言,只有两半都跑过才能打),
改为经 `renderHealthCheckResult()` 输出一条 warning 档 `HealthCheckResult`,
`--verbose` 展开随之免费获得。已跑过的那一半的 findings 仍照常上报。
不在本单范围:ledger 内**单个条目**损坏 —— `LocalManifestSource.list()` 在
自己的 per-file catch 里跳过它,调用成功且返回短列表,消费者无从分辨。
已另立 #5413,并由本 PR 的 SCOPE BOUNDARY 测试钉住。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 5, 2026 10:19
baozhoutao
enabled auto-merge
August 5, 2026 10:19
This was referenced Aug 5, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 6, 2026
…edger entries it could not read (objectstack-ai#5413) (objectstack-ai#5424) A truncated / unreadable / unparseable file under `.objectstack/installed-packages/` was dropped in an un-bound per-file `catch` and `list()` returned a bare array, so a short list was indistinguishable from a complete one: no difference in the return value, no log, no count. All three consumers gave a confidently wrong answer — `rehydrate()` left the installed app unregistered (gone from the app switcher, its objects nonexistent) with nothing in the log, `handleList()` served the console a list that looked whole with `success: true`, and `os doctor` printed a clean `✓ Unique scope` over manifests it had never parsed. Skipping a corrupt file stays correct — one bad manifest must not stop a runtime booting the packages that are fine. Skipping it SILENTLY was the defect. `list()` now returns `{ entries, skipped }` (option A of the issue's decision point): reporting is the caller's job, and "I read only half the ledger" becomes a fact in the type rather than an absence. Enumerating the DIRECTORY still throws — a different fact from "some files in it would not parse", and objectstack-ai#5412 already reports the two as separate rows. Wiring, per triage: - `rehydrate()` warns per skipped file, before the empty-entries early return (an all-corrupt ledger is the worst case, not the exempt one), naming the file, the consequence and the thrower's own words. - `handleList()` logs the same; the WIRE SHAPE is deliberately unchanged — putting the skip in the response body is a separate schema decision. - `os doctor` turns `skipped` into a `Unique scope` warning row and withholds the `✓` success line, alongside the directory-level row from objectstack-ai#5412. objectstack-ai#5414's `⚠ SCOPE BOUNDARY` test went red exactly as its own comment predicted and is rewritten as the positive assertion. Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh Co-authored-by: Claude <noreply@anthropic.com>
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 #5412
先说前提核验的结果:issue 的机制判定对了一半,复现路径是错的
按 origin/main (
b4872a868) 核验,必须先读这一段再看实现。issue 说
readInstalledPackageEntries()的 catch 覆盖了两件事,其中第 2 件是「目录存在但里面某个 manifest 损坏 / JSON 解析失败」。这条具体路径够不着那个 catch。
packages/cloud-connection/src/local-manifest-source.ts的LocalManifestSource.list()自己有一层 per-file catch:
跑真实实现(一好一坏两条目)实测:
也就是说:损坏条目在生产方就被丢掉了,
list()成功返回一个短列表,doctor 那边看到的是一次成功调用。issue 的复现路径(截断 JSON)按原样构造,
✓ Unique scope照样会打 —— 但原因不在 doctor 的 catch,而在上游。
issue 的核心判定仍然成立:那个 catch 确实把「可选包没装」和「ledger 读不到」
混为一谈,而且确实产出 false PASS。只是够得着它的是目录级失败(路径被文件占住 =
ENOTDIR、文件系统拒绝、读目录失败),不是条目级损坏。实测:
所以本 PR 修的是目录级那一半,并把条目级那一半明确钉成边界、另立 #5413,
而不是把测试改成能过的形状然后声称整单做完。PM 裁定的口径(拆两个 catch、warning
档、复用
renderHealthCheckResult()、✓不得打印)一字未改地适用于目录级路径;只有 PM 要求的测试 ① 换了触发方式(ENOTDIR 而非截断 JSON),因为截断 JSON 在这一层
根本不产生任何信号。
改了什么
packages/cli/src/commands/doctor.ts:readInstalledPackageEntries()拆成两个 try。import('@objectstack/cloud-connection')保留自己的静默 catch —— 这一条注释写得对,doctor 必须能在没有该包的 checkout 里
跑完。目录解析之后的读取单独一个 try,抛错时带着 cause 回到调用方。
返回类型从
any[]变为{ entries, failure? }:空数组本身是歧义的(「没装过东西」和「读不到」都产生它),第二种现在自带标记。
findUnscopedGlobalUniques()相应返回{ advisories, ledgerFailure? }。只有两半都跑过才允许打印。ledger 失败时改打一条常规
HealthCheckResult,经renderHealthCheckResult()(fix(cli):os doctor说出配置载入失败的原因,不再只说一句「载入不了」(#5403) #5410 刚落地的那一个,没有发明第二套渲染),--verbose展开免费获得。已跑过的那一半的 findings 照常上报 —— 一处静默省略不该换成另一处。
installedPackageLedgerFailureCheck(),warning 档,cause 按 fix(cli):os doctor指名道姓报告非法OS_TENANCY_POSTURE并非零退出 (#5382) #5390 体例引上游原话不改写(
ENOTDIR: not a directory, scandir '…'本身就指名了挡路的那个文件,doctor 编不出更好的句子)。
Unique scope名字列而非新起一个名字:重点是操作者扫报告时那一行在、且不是
✓;另起名字会让Unique scope干脆消失,那正是本单要治的沉默换了顶帽子。
configLoadHeadline/CONFIG_LOAD_HEADLINE_MAX现在有了第二个消费者(ledger 的 cause 走同一个折行),名字改为
reportRowHeadline/REPORT_ROW_HEADLINE_MAX。纯内部,无外部引用。反向验证(方向是先定后跑的)
预期方向:红。把那个吞掉的 catch 恢复(第二个 try 的 catch 改回
return { entries: [] }),预期只有钉缺陷的 3 条 e2e 转红,4 条无回归用例保持绿 —— 后者若也变绿/变红都说明它们
是因为「什么都没产出」而通过的。实跑结果与预期一致:
恢复后 15/15 全绿。
测试
新增
packages/cli/src/commands/doctor-ledger-read-failure.test.ts(15 条,e2e 沿用#5410
doctor-config-load-cause.test.ts的 harness:temp cwd +node_modules目录陷阱,每条
}, 60_000))。覆盖 PM 点名的四项:✓ Unique scope那行不出现;--verbose才展开cause:;vi.doMock+vi.resetModules()让模块求值抛错来模拟不可解析;该场景下故意让 ledger 目录存在,这样若两个 catch 还共用,反方向
的混淆会冒出一条假 warning);
✓,有 findings 照报installed package 'billing';另加「压根没有 ledger 目录」仍静默(不能把「从没装过」过度矫正成 warning)。
外加一条
⚠ SCOPE BOUNDARY用例,把「条目级损坏仍被生产方吸收」钉住并注明#5413 落地时它应当转红。
越界与另立单
packages/cli/src/commands/doctor.ts+ 该包测试,未动packages/cloud-connection。LocalManifestSource.list()静默丢弃损坏的 ledger 条目 —— 已装应用在 boot 时消失、在控制台列表里缺席,且没有任何一条日志 #5413(未在本 PR 修):运行时后果比 doctor 更重 ——marketplace-install-local-plugin.ts的rehydrate()会让损坏条目对应的已装应用在 boot 时不被注册且零日志,
handleList()的控制台已装列表也少一项。修法要动list()的公开返回契约(3 个消费者),已在LocalManifestSource.list()静默丢弃损坏的 ledger 条目 —— 已装应用在 boot 时消失、在控制台列表里缺席,且没有任何一条日志 #5413 里列了 A/B/C 三个方案与倾向,留给维护者拍板。
🤖 Generated with Claude Code
https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
Generated by Claude Code