Skip to content

同源:另外三处 hook context 上的 doc / previousDoc 防御性 alias 读也没有生产者(#5671 全仓 grep 的剩余部分) #5906

Description

@hotlong

#5671(PR 见下)按必答项做「全仓 grep hook input 形状上的 doc 读者」时发现,PD #10 单独记录。观察类(finding):与 #5671 同一族,今天没有用户会撞上,三处都是永不命中的兜底分支。

#5671 的作业面被派发口径限定在 packages/triggers/trigger-record-change + 两处 fixture,所以下面三处没有在那个 PR 里一起清,按纪律另立此单。

事实(对 origin/main 核实)

真值源:packages/objectql/src/hook-input-shape-contract.test.ts(PR #5668)断言 insert 上下文「carries data — never doc」;packages/spec/src/data/hook.zod.tsHookContext 只声明 input / result / previous 三个键 —— 既没有顶层 doc,也没有 previousDoc

1. packages/services/service-storage/src/attachment-lifecycle.ts:174

const row: any = ctx?.result ?? ctx?.input?.doc ?? ctx?.input?.data;

注意这条的次序与 #5671 相反:doc 排在 data 前面。因为 doc 恒为 undefined,?? 每次都穿到 data,等价于 ctx?.result ?? ctx?.input?.data。纯死码。

2. packages/plugins/plugin-sharing/src/primary-bu-projection.ts:80

add((ctx?.input?.data ?? ctx?.input?.doc)?.user_id);

data 在前,doc 限支不可达。

3. packages/runtime/src/sandbox/body-runner.ts:308(不同的键,同一族)

const inputSnapshot = unwrapProxyToPlain(engineCtx?.input ?? engineCtx?.doc);
const previousRaw = engineCtx?.previous ?? engineCtx?.previousDoc;

这两条读的是 ctx.doc / ctx.previousDoc(顶层),不是 ctx.input.doc,所以严格说不属于「hook input 形状」那次 grep 的目标;但它同样是为一个 HookContext 从未声明、引擎从未构造的键留的兜底,列在一起便于一次性判。

分级说明

finding:三处都是纯休眠代码,行为上零影响,与 #5671 判法一致。论据也一样 —— AGENTS.md PD #12 点名的消费端 ?? 兜底,「a tolerant fallback fossilizes the wrong convention into a second de-facto contract」,而且是为从来不存在的生产者留的。严重程度请分诊轮判。

建议

三处各自只读引擎真值键(input.data / input / previous),删掉 alias 限支。做之前留意 fixture 三分法:#5671attachment-lifecycle.test.ts:159 的 insert fixture 已按引擎真值 re-spell 成 input: { data: … },但那条 fixture 同时给了 result,?? 链首位就命中,所以它对 1 号点的两条限支都不承重 —— 清 1 号点时若要留 pin,得另配一条不带 result 的用例。

关联

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions