在 #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.ts 的 HookContext 只声明 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 三分法:#5671 里 attachment-lifecycle.test.ts:159 的 insert fixture 已按引擎真值 re-spell 成 input: { data: … },但那条 fixture 同时给了 result,?? 链首位就命中,所以它对 1 号点的两条限支都不承重 —— 清 1 号点时若要留 pin,得另配一条不带 result 的用例。
关联
在 #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 上下文「carriesdata— neverdoc」;packages/spec/src/data/hook.zod.ts的HookContext只声明input/result/previous三个键 —— 既没有顶层doc,也没有previousDoc。1.
packages/services/service-storage/src/attachment-lifecycle.ts:174注意这条的次序与 #5671 相反:
doc排在data前面。因为doc恒为 undefined,??每次都穿到data,等价于ctx?.result ?? ctx?.input?.data。纯死码。2.
packages/plugins/plugin-sharing/src/primary-bu-projection.ts:80data在前,doc限支不可达。3.
packages/runtime/src/sandbox/body-runner.ts:308(不同的键,同一族)这两条读的是
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 三分法:#5671 里attachment-lifecycle.test.ts:159的 insert fixture 已按引擎真值 re-spell 成input: { data: … },但那条 fixture 同时给了result,??链首位就命中,所以它对 1 号点的两条限支都不承重 —— 清 1 号点时若要留 pin,得另配一条不带result的用例。关联
trigger-record-change的input.doc防御性 alias 读的是一个没有生产者的键(引擎只建input.data) #5671(trigger-record-change的同一条 alias,已修)HookContext.input的契约注释声明批量写携带input.ast,引擎从不设置它(AST 只在 opCtx 上);同一张表也未描述 #5038 后 after 事件的按行形状 #5273 / PR docs(spec): HookContext.input 契约表改成引擎真正构造的形状 (#5273) #5668input: { doc: … }—— 按引擎真值 re-spell(#5671 摘除死 alias 的 spec 半边) #5886