#4347 / #4381 的后续。那次修掉了「元数据随嵌套深度而变」的转换 和谓词校验 两半:
mapFlowNodes 递归进 region,ADR-0087 转换表能到达嵌套节点;
新增 collectFlowGraphs,引擎的 validateFlowExpressions 和 lint 的 validateStackExpressions 都改成遍历它。
registerFlow 里另外三个校验器仍然只遍历 flow.nodes ,同一个形状的洞还留着:ADR-0031 容器把整张子图放在自己开放的 config 里,校验器停在容器边界,于是「校验了一部分、却按整个 flow 汇报」。
三个缺口
校验器
位置
失败方式
region 里的现状
validateControlFlow
spec/src/automation/control-flow.zod.ts
hard-fail
只验顶层容器。嵌套 容器(loop body 里再放一个 loop)畸形时注册照过,要到运行时 findRegionEntry 才抛 —— 违背 ADR-0031 的「reject the malformed before it can run」
validateNodeTypes
engine.ts:2732(flow.nodes.map(...))
soft-fail(warn)
region 里引用了没有执行器的 type,注册期一声不吭,运行时 NO_EXECUTOR
validateNodeConfigKeys
engine.ts:2803(for (const node of flow.nodes))
hard-fail
region 里的未声明 config 键完全不检查 —— #4277 要抓的 visibleIf 拼写错误,放进 loop body 就恢复成静默通过
三个都是 for (const node of flow.nodes) 这一行的问题,collectFlowGraphs(flow) 已经在 @objectstack/spec/automation 里,改动量各自都在个位数行。
为什么值得单开
不是没顺手做,是风险不对等 ,混在 #4381 里会让那个 PR 的 blast radius 说不清:
validateNodeTypes 是 warn,扩进 region 几乎零风险,可以直接做;
validateControlFlow 和 validateNodeConfigKeys 都是 hard-fail,扩大遍历范围 = 可能新拒一批现在能注册的 flow 。这是行为变更,不是 bugfix,需要先量一遍存量再决定。
建议的落地顺序
先量 :拿 collectFlowGraphs 写一个只读脚本,跑 examples/app-showcase、examples/app-crm、examples/app-todo 的全部 flow,统计三个校验器扩到 region 之后会新增多少 violation,分别是什么。没有这个数,后两步没法判断。
validateNodeTypes 直接扩 (warn,无风险)。
validateNodeConfigKeys / validateControlFlow :如果第 1 步显示存量为 0,直接收紧;如果非 0,先把 region 部分降级成 warn 走一个版本,再提到 error —— 就是 feat(automation,formula): warn on undeclared flow-node config keys (#4045) #4059 → 3b — wire the flow executors to parse() their config, and tighten the undeclared-key warning into an error #4277 那条 warn-then-error 的阶梯。
复现
// 顶层:注册期 hard-fail(#4277)
{ id : 'scr' , type : 'screen' , config : { visibleIf : '…' } }
// 同一个节点放进 loop body:注册期静默通过
{ id : 'loop' , type : 'loop' , config : {
collection : '{rows}' ,
body : { nodes : [ { id : 'scr' , type : 'screen' , config : { visibleIf : '…' } } ] , edges : [ ] } ,
} }
validateControlFlow 的那一条同理 —— 把一个双入口(畸形)region 从顶层 loop 挪进另一个 loop 的 body,注册就不再拒了。
不在范围内
evaluateCondition 的 legacy {var} 路径上剩下的静默 false(无运算符分支、以及未解析的 {token} 被当字符串比)是另一类问题,和遍历无关,建议另开。
#4347 / #4381 的后续。那次修掉了「元数据随嵌套深度而变」的转换和谓词校验两半:
mapFlowNodes递归进 region,ADR-0087 转换表能到达嵌套节点;collectFlowGraphs,引擎的validateFlowExpressions和 lint 的validateStackExpressions都改成遍历它。registerFlow里另外三个校验器仍然只遍历flow.nodes,同一个形状的洞还留着:ADR-0031 容器把整张子图放在自己开放的config里,校验器停在容器边界,于是「校验了一部分、却按整个 flow 汇报」。三个缺口
validateControlFlowspec/src/automation/control-flow.zod.tsfindRegionEntry才抛 —— 违背 ADR-0031 的「reject the malformed before it can run」validateNodeTypesengine.ts:2732(flow.nodes.map(...))NO_EXECUTORvalidateNodeConfigKeysengine.ts:2803(for (const node of flow.nodes))visibleIf拼写错误,放进 loop body 就恢复成静默通过三个都是
for (const node of flow.nodes)这一行的问题,collectFlowGraphs(flow)已经在@objectstack/spec/automation里,改动量各自都在个位数行。为什么值得单开
不是没顺手做,是风险不对等,混在 #4381 里会让那个 PR 的 blast radius 说不清:
validateNodeTypes是 warn,扩进 region 几乎零风险,可以直接做;validateControlFlow和validateNodeConfigKeys都是 hard-fail,扩大遍历范围 = 可能新拒一批现在能注册的 flow。这是行为变更,不是 bugfix,需要先量一遍存量再决定。建议的落地顺序
collectFlowGraphs写一个只读脚本,跑examples/app-showcase、examples/app-crm、examples/app-todo的全部 flow,统计三个校验器扩到 region 之后会新增多少 violation,分别是什么。没有这个数,后两步没法判断。validateNodeTypes直接扩(warn,无风险)。validateNodeConfigKeys/validateControlFlow:如果第 1 步显示存量为 0,直接收紧;如果非 0,先把 region 部分降级成 warn 走一个版本,再提到 error —— 就是 feat(automation,formula): warn on undeclared flow-node config keys (#4045) #4059 → 3b — wire the flow executors toparse()their config, and tighten the undeclared-key warning into an error #4277 那条 warn-then-error 的阶梯。复现
validateControlFlow的那一条同理 —— 把一个双入口(畸形)region 从顶层 loop 挪进另一个 loop 的 body,注册就不再拒了。不在范围内
evaluateCondition的 legacy{var}路径上剩下的静默false(无运算符分支、以及未解析的{token}被当字符串比)是另一类问题,和遍历无关,建议另开。