Skip to content

LocalManifestSource.read() 把「条目不存在」和「条目损坏」都答成 null,两个 HTTP 调用方因此只能回一句无因由的 500 #5426

Description

@baozhoutao

实施 #5413(给 list() 加上 skipped 返回契约)时在同一个文件里核验发现;未在该 PR 里改 —— #5413 的判定面被 PM 限定在 list() 的返回契约与它的三个消费者,本条是 read() 的返回契约,另一组调用方。

事实

packages/cloud-connection/src/local-manifest-source.ts(分支 claude/issue-5413-manifest-source-skipped-entriesread() 本身未被 #5413 改动):

    /** Read one entry; null when absent or unreadable. */
    read(manifestId: string): InstalledManifestEntry | null {
        const file = this.fileFor(manifestId);
        if (!existsSync(file)) return null;
        try {
            return JSON.parse(readFileSync(file, 'utf8'));
        } catch {
            return null;
        }
    }

#5413 修掉的 list() 是同一个形状的另一半:不带绑定的 catch,抛出的对象在原地丢弃。null 同时表示两个事实 —— 「这个 manifest 没装过」和「装过,但那个文件读不出来」。注释 null when absent or unreadable 说明这是有意的合并。

list() 的区别在于:read()null 至少一个信号(调用方会分支),不像 list() 的短列表那样完全不可察觉。所以这条比 #5413 轻。

后果

三个调用方(都在 packages/cloud-connection/src/marketplace-install-local-plugin.ts):

  1. :586 安装路径的 ADR-0120 D5e posture gate —— previousEntry = this.ledger.read(manifestId) 喂给 evaluateGlobalUniqueGate()。损坏的既有条目读成 null = 「没有既往 attestation」= gate 重新发问。这个方向是安全的(宁可重复一次一次性仪式,也不会跳过它),列在这里只为说明它已核验、不是隐患。

  2. :929 reseed:1009 purge —— 两处都是先 has()existsSync,为 true)再 read()(因损坏得到 null),于是回:

500 { code: 'MARKETPLACE_STORAGE_FAILED', message: 'Failed to read manifest cache.' }

has() 刚说文件在,read() 就说读不出来,而为什么读不出来(Unexpected end of JSON input / EACCES / EISDIR)在 catch 处已经被丢掉了。操作者拿到的是一句自己指向自己的话,没有任何一条能指向那个文件的线索;服务端日志里也没有补一行。属 #5403 / #5412 / #5413「catch 吞掉唯一能解释状况的那个对象」家族。

复现路径

.objectstack/installed-packages/com.acme.crm.json   # 截断的 JSON
POST /api/v1/marketplace/install-local/com.acme.crm/reseed-sample-data
→ 500 "Failed to read manifest cache."(无因由;日志无补充)

决策点(不预设,留给分诊)

read() 的返回契约怎么改,和 #5413 是同一类决策,但结论不见得相同 —— read()null 已经是一个可分支的信号,破坏性改造的收益比 list() 小:

倾向 A(与 #5413 同一形状,两个方法的契约不至于分叉),但 read()null 语义被三处调用方以不同方式消费,需要维护者拍板。

备注

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions