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-entries,read() 本身未被 #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 语义被三处调用方以不同方式消费,需要维护者拍板。

备注

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions