修 #4414 时发现的相邻缺口,独立于那三条路由机制,所以单独开。
现状
FLOW_NODE_EXPRESSION_PATHS(packages/spec/src/automation/flow-node-expression-paths.ts)是 #4027 立的账本:一个节点 config 里声明的表达式槽位必须登记在这里,registerFlow 和 objectstack validate 才会去走它。棘轮由 config-expression-ledger.test.ts 双向咬死:
问题在于账本的唯一真值来源是 descriptor 的 configSchema。而 decision / script / subflow / wait / connector_action 这一类是刻意不发 configSchema 的 —— schemaless-node-config.zod.ts 的模块注释写得很明确:这些类型的 config 契约发布在那个文件里,"must NOT grow into descriptor configSchemas"(表单在 objectui 手写)。
两条规则一叠,结果是:凡是 schemaless 类的节点,它声明的表达式槽位在结构上无法进账本,因此永远没有 build-time 校验器。
具体的一个
DecisionConditionSchema.expression(schemaless-node-config.zod.ts:193)自己写着 'Bare CEL predicate deciding this branch',注释也点名 {…} 是 #1491 的陷阱。但:
- 它不在
FLOW_NODE_EXPRESSION_PATHS 里 —— 也加不进去,加了 config-expression-ledger.test.ts 的「无 stale 条目」那条会红; - 所以
registerFlow 不校验它,objectstack validate 也不校验它。
对比 edge.condition:那条是结构性槽位(每条边都有),两个校验器都硬编码走它,所以 {lead_record.status} == 'converted' 写在边上会在注册时就炸,并带修正提示。写在 decision 的 conditions[].expression 里则一路绿灯到运行时。
#4414 已经把运行时那半补上了 —— 执行器现在按声明的 bare CEL 求值({ dialect: 'cel', source }),所以带花括号的谓词会响亮地失败而不是静默判 false。但作者是在跑起来才知道,不是在 os build 时知道,这正是 #4027 建账本要消除的那段延迟。
建议
账本的「反向」棘轮应该按槽位的声明处分档,而不是一律要求 descriptor 声明:
- 有
configSchema 的节点 → 维持现状(双向咬死 xExpression); - schemaless 类节点 → 允许账本条目由
schemaless-node-config.zod.ts 里的 Zod schema 认领。棘轮改成「每条账本条目要么对应一个 descriptor 的 xExpression,要么对应一个 schemaless config schema 里标注过的字段」,两边都不许有孤儿。
落地之后把 decision.conditions[].expression 登记为 role: 'predicate',两个校验器就自动覆盖它了。顺带该扫一遍 script / subflow / wait / connector_action 的 config 契约,看还有几个同样形状的槽位 —— 我没有逐个数。
相关: #4027(账本本身)、#3528(它要防的那个缺陷)、#4414(decision 路由,运行时那半已修)、#4336。
修 #4414 时发现的相邻缺口,独立于那三条路由机制,所以单独开。
现状
FLOW_NODE_EXPRESSION_PATHS(packages/spec/src/automation/flow-node-expression-paths.ts)是 #4027 立的账本:一个节点 config 里声明的表达式槽位必须登记在这里,registerFlow和objectstack validate才会去走它。棘轮由config-expression-ledger.test.ts双向咬死:configSchema里出现新的xExpression而账本没登记 → 失败(Console: screen-flow Submit never calls the resume endpoint — every screen flow is un-completable from the UI #3528 的形状);问题在于账本的唯一真值来源是 descriptor 的
configSchema。而decision/script/subflow/wait/connector_action这一类是刻意不发configSchema的 ——schemaless-node-config.zod.ts的模块注释写得很明确:这些类型的 config 契约发布在那个文件里,"must NOT grow into descriptorconfigSchemas"(表单在 objectui 手写)。两条规则一叠,结果是:凡是 schemaless 类的节点,它声明的表达式槽位在结构上无法进账本,因此永远没有 build-time 校验器。
具体的一个
DecisionConditionSchema.expression(schemaless-node-config.zod.ts:193)自己写着'Bare CEL predicate deciding this branch',注释也点名{…}是 #1491 的陷阱。但:FLOW_NODE_EXPRESSION_PATHS里 —— 也加不进去,加了config-expression-ledger.test.ts的「无 stale 条目」那条会红;registerFlow不校验它,objectstack validate也不校验它。对比
edge.condition:那条是结构性槽位(每条边都有),两个校验器都硬编码走它,所以{lead_record.status} == 'converted'写在边上会在注册时就炸,并带修正提示。写在 decision 的conditions[].expression里则一路绿灯到运行时。#4414 已经把运行时那半补上了 —— 执行器现在按声明的 bare CEL 求值(
{ dialect: 'cel', source }),所以带花括号的谓词会响亮地失败而不是静默判 false。但作者是在跑起来才知道,不是在os build时知道,这正是 #4027 建账本要消除的那段延迟。建议
账本的「反向」棘轮应该按槽位的声明处分档,而不是一律要求 descriptor 声明:
configSchema的节点 → 维持现状(双向咬死xExpression);schemaless-node-config.zod.ts里的 Zod schema 认领。棘轮改成「每条账本条目要么对应一个 descriptor 的xExpression,要么对应一个 schemaless config schema 里标注过的字段」,两边都不许有孤儿。落地之后把
decision.conditions[].expression登记为role: 'predicate',两个校验器就自动覆盖它了。顺带该扫一遍script/subflow/wait/connector_action的 config 契约,看还有几个同样形状的槽位 —— 我没有逐个数。相关: #4027(账本本身)、#3528(它要防的那个缺陷)、#4414(decision 路由,运行时那半已修)、#4336。