现象
对象不声明 fields(external 对象、数据源自省 schema —— 列在运行时解析)时,hook-body-write-unknown-field 和 action-body-write-unknown-field 会把 body 里每一个字段写标成「对象没有这个字段」。
实测(已构建的 @objectstack/lint dist,对象为 { name: 'legacy_deal', external: true },不带 fields):
hook : ["hook-body-write-unknown-field / warning"] ← 误报
action: ["action-body-write-unknown-field / warning"] ← 误报
flow : [] ← 正确跳过
三条同族规则,两条误报、一条正确。
根因
indexObjectFields(validate-hook-body-writes.ts:228)对零字段对象返回一个空 Set,而不是 undefined。下游两处都只判断「有没有这个对象」,不判断「这个对象有没有可判断的字段表」:
validate-hook-body-writes.ts:421 —— targetSets.every((s) => s !== undefined),空 Set 不是 undefined,于是 inputJudgeable 为 true,随后 targetSets.some((s) => s.has(field)) 必然为 false → 报;
validate-hook-body-writes.ts:458 / validate-action-body-writes.ts:255 —— if (!known) continue,空 Set 是 truthy,于是 known.has(field) 必然为 false → 报。
为什么这是 bug 而不是保守取舍
同一个跳过在这个家族里已经存在两处,而且都写了理由:
四条规则里两条有、两条没有,正是 #3583 / #4330 在收拾的那种漂移。而且它直接违反那两条规则自己写在头注释里的原则:
Everything statically unknowable is skipped SILENTLY, asymmetrically favouring missed findings over false ones — a false positive kills an advisory lint
零字段对象恰恰是「statically unknowable」的教科书情形。
建议
两处各加一道 guard,并把理由注释对齐 validate-searchable-fields 的 skip #2:
- hook 规则:
inputJudgeable 追加 targetSets.every((s) => s.size > 0)(多目标 hook 里只要有一个目标的字段表为空,ctx.input 写就无法判断);
- hook + action 的
ctx.api 分支:if (!known || known.size === 0) continue;。
各配一条测试。改动很小,但要注意多目标 hook 那一支不是简单照抄——需要决定「部分目标可判断」时的语义(建议与现有 some(has) 的宽松取向一致:任一目标不可判断即整体跳过)。
版本
@objectstack/*@17.0.0-rc.0,main @ 38182ffb8。发现自 #4369 / #4374 的收尾核查——写 flow 规则时为 gating 加了这道 guard,回头核对才发现两个 advisory 兄弟没有。
相关:#4380(同一批核查发现的嵌套 region 盲区)
现象
对象不声明
fields(external 对象、数据源自省 schema —— 列在运行时解析)时,hook-body-write-unknown-field和action-body-write-unknown-field会把 body 里每一个字段写标成「对象没有这个字段」。实测(已构建的
@objectstack/lintdist,对象为{ name: 'legacy_deal', external: true },不带fields):三条同族规则,两条误报、一条正确。
根因
indexObjectFields(validate-hook-body-writes.ts:228)对零字段对象返回一个空 Set,而不是undefined。下游两处都只判断「有没有这个对象」,不判断「这个对象有没有可判断的字段表」:validate-hook-body-writes.ts:421——targetSets.every((s) => s !== undefined),空 Set 不是undefined,于是inputJudgeable为 true,随后targetSets.some((s) => s.has(field))必然为 false → 报;validate-hook-body-writes.ts:458/validate-action-body-writes.ts:255——if (!known) continue,空 Set 是 truthy,于是known.has(field)必然为 false → 报。为什么这是 bug 而不是保守取舍
同一个跳过在这个家族里已经存在两处,而且都写了理由:
validate-searchable-fields.ts的 skip ✨ Set up Copilot instructions #2 —— 「An object that declares no field map at all — external objects and datasource-introspected schemas whose columns are resolved at runtime」;validate-flow-node-writes.ts:210(feat(lint): flow update_record 写不存在字段从盲区变为 gating error (#4271) #4369)—— 加了if (known.size === 0) return;,理由是该规则 gating,不能对着看不见的字段表开火。四条规则里两条有、两条没有,正是 #3583 / #4330 在收拾的那种漂移。而且它直接违反那两条规则自己写在头注释里的原则:
零字段对象恰恰是「statically unknowable」的教科书情形。
建议
两处各加一道 guard,并把理由注释对齐
validate-searchable-fields的 skip #2:inputJudgeable追加targetSets.every((s) => s.size > 0)(多目标 hook 里只要有一个目标的字段表为空,ctx.input写就无法判断);ctx.api分支:if (!known || known.size === 0) continue;。各配一条测试。改动很小,但要注意多目标 hook 那一支不是简单照抄——需要决定「部分目标可判断」时的语义(建议与现有
some(has)的宽松取向一致:任一目标不可判断即整体跳过)。版本
@objectstack/*@17.0.0-rc.0,main@38182ffb8。发现自 #4369 / #4374 的收尾核查——写 flow 规则时为 gating 加了这道 guard,回头核对才发现两个 advisory 兄弟没有。相关:#4380(同一批核查发现的嵌套 region 盲区)