发现于 #5393 (给 flow update_record / delete_record 补 multi 批量意图键)的实现过程,PR 见该单。属 PD #10 的范围外发现,#5393 内刻意不修 —— 加这条守卫等于在该单里发明 issue 没要求的新语义,交 PM 定级。
事实
#5393 落地后:
config: { objectName: 'lead', multi: true }(没有 filter )会走到 data.delete(objectName, { where: {}, multi: true }),引擎按 resolveEngineDeleteDispatch 判为 multi → driver.deleteMany({}) → 整表。
这不是 bug,是声明 :引擎自己的派发表就把 { what: 'multi with no predicate at all', options: { multi: true }, expect: 'multi' } 列为合法(packages/objectql/src/engine-delete-dispatch.ts),平台层面「显式的整表删除」一直是可表达的。
Flow node filters silently blank date macros: the template engine consumes {…} before the query engine sees it #3810 的抹除守卫不会 拦它,而且是刻意的:packages/services/service-automation/src/builtin/crud-nodes.ts 的注释写明「deliberately keyed on 'a condition the author wrote is gone', not on 'the filter is empty'」,crud-filter-guard.test.ts 还有一条用例专门钉住「an intentionally empty filter is still allowed (nothing was erased)」。
在 #5393 之前这条路径不可达 (执行器从不传 options.multi,引擎一律拒绝),所以「空 filter + 批量」此前从来不是一个真实的授权面。现在它是了。
为什么值得看一眼
防 AI 轴上,#5393 把最常见的错误从「filter 字段名拼错 → 引擎点名拒绝」挡住了,但没有覆盖另一个形状:AI 写了 multi: true 却忘了写 filter 。此时:
没有 authoring 期诊断(schema 里 filter 本来就是 optional,multi 与它之间没有任何约束);
没有 lint 规则(packages/lint/src/lint-flow-patterns.ts 的 DATA_NODE_TYPES 只用于 runAs 相关判定,没有针对破坏性节点的约束检查);
运行期只会成功,acted 报出被删的行数 —— 也就是说,反馈只在删完之后 出现。
对照 #3810 的原始事故:那次是「一个拼错的字段名静默清空对象」,修法是让抹除拒绝执行 。这里是同一个后果,不同的入口。
可选方向(供 PM 定级,不预设结论)
lint 规则(倾向) :multi: true 且 filter 缺失/为空 → 告警(而非 fail),文案说明这是整表写,要么补约束,要么显式确认。放在 packages/lint,与 lint: a form section with a label but no name can never be translated and nothing warns — 70/70 HotCRM form sections are in that state #5417 那类「声明了但没有可寻址键」的规则同一层。注意会撞上 flow lint rules never descend into a loop body — the whole family is blind to nested nodes (8 real inert conditions shipped past flow-inert-node-condition) #5383 (flow lint 不进 loop 体)。
spec 层约束 :在两个 config schema 上加 refine —— multi: true 要求 filter 非空。最强,但会让「显式整表清理」这个引擎认可的合法意图在 flow 层不可表达,需要先确认没人要它。
什么都不做 :整表删除是显式声明的、可 grep 的,引擎层同样允许;把它留给 code review。
完成判据(若采纳 1)
关联:#5393 (引入这条路径的单)、#3810 (抹除守卫,以及它为什么不按「空」判定)、#5225 (现场)、#5383 (loop 体 lint 盲区)。
发现于 #5393(给 flow
update_record/delete_record补multi批量意图键)的实现过程,PR 见该单。属 PD #10 的范围外发现,#5393 内刻意不修 —— 加这条守卫等于在该单里发明 issue 没要求的新语义,交 PM 定级。事实
#5393 落地后:
config: { objectName: 'lead', multi: true }(没有filter)会走到data.delete(objectName, { where: {}, multi: true }),引擎按resolveEngineDeleteDispatch判为multi→driver.deleteMany({})→ 整表。{ what: 'multi with no predicate at all', options: { multi: true }, expect: 'multi' }列为合法(packages/objectql/src/engine-delete-dispatch.ts),平台层面「显式的整表删除」一直是可表达的。{…}before the query engine sees it #3810 的抹除守卫不会拦它,而且是刻意的:packages/services/service-automation/src/builtin/crud-nodes.ts的注释写明「deliberately keyed on 'a condition the author wrote is gone', not on 'the filter is empty'」,crud-filter-guard.test.ts还有一条用例专门钉住「an intentionally empty filter is still allowed (nothing was erased)」。在 #5393 之前这条路径不可达(执行器从不传
options.multi,引擎一律拒绝),所以「空 filter + 批量」此前从来不是一个真实的授权面。现在它是了。为什么值得看一眼
防 AI 轴上,#5393 把最常见的错误从「filter 字段名拼错 → 引擎点名拒绝」挡住了,但没有覆盖另一个形状:AI 写了
multi: true却忘了写filter。此时:filter本来就是 optional,multi与它之间没有任何约束);packages/lint/src/lint-flow-patterns.ts的DATA_NODE_TYPES只用于 runAs 相关判定,没有针对破坏性节点的约束检查);acted报出被删的行数 —— 也就是说,反馈只在删完之后出现。对照 #3810 的原始事故:那次是「一个拼错的字段名静默清空对象」,修法是让抹除拒绝执行。这里是同一个后果,不同的入口。
可选方向(供 PM 定级,不预设结论)
multi: true且filter缺失/为空 → 告警(而非 fail),文案说明这是整表写,要么补约束,要么显式确认。放在packages/lint,与 lint: a form section with alabelbut nonamecan never be translated and nothing warns — 70/70 HotCRM form sections are in that state #5417 那类「声明了但没有可寻址键」的规则同一层。注意会撞上 flow lint rules never descend into aloopbody — the whole family is blind to nested nodes (8 real inert conditions shipped pastflow-inert-node-condition) #5383(flow lint 不进 loop 体)。multi: true要求filter非空。最强,但会让「显式整表清理」这个引擎认可的合法意图在 flow 层不可表达,需要先确认没人要它。完成判据(若采纳 1)
delete_record/update_record且multi: true且filter无有效条件;{…}before the query engine sees it #3810 的抹除守卫(那条是运行期拒绝,这条是 authoring 期告警),两者的诊断文案互相点名;showcase_inquiry_purge清扫流从来没删掉过任何记录 —— delete_record 节点每次都失败(Delete requires an ID or options.multi=true) #5225 收尾后)必须保持零告警 —— 它是有 filter 的。关联:#5393(引入这条路径的单)、#3810(抹除守卫,以及它为什么不按「空」判定)、#5225(现场)、#5383(loop 体 lint 盲区)。