Repository navigation
Flow 边检查器能产出缺 dialect 的 condition —— 写出的正是服务端自己 schema 会拒绝的形状 #3171
Description
Activity
认领:PM 循环第 2 轮(分片:objectui 整仓)
会话:session_01PRJtkgUAaVG11FsJQbvZWA
分支:claude/issue-3171-flow-edge-condition-dialect
Worktree:objectui-issue-3171分诊补充:按正文修法执行——补
dialect(取 ADR-0089 规范默认值)+ 闸门断言(检查器产出的任何condition都必须能通过 spec schema 解析)。正文明说闸门比修复本身重要,不得只改构造点而不加闸门。正文建议的grep "as any)."排查属调查范围:发现的同类「设计器产出 → spec 拒绝」形状按越界发现另立 issue,不在本 PR 顺手修。
Generated by Claude Code
开发 agent 调查结论:未按正文实现,升级为待定。正文的前提经核实不成立,照修会在「不存在的 bug」上改掉写进元数据的形状。证据如下(均针对 objectui 当前锁定的
@objectstack/spec@17.0.0-rc.1)。1. 裸字符串
condition不会被服务端拒绝 —— 它是 spec 明文祝福的创作输入FlowEdgeSchema.condition是ExpressionInputSchema.optional(),而ExpressionInputSchema是:z.union([ z.string().min(1).transform(source => ({ dialect: 'cel', source })), ExpressionSchema ])裸字符串在 parse 时被规范化成
{ dialect: 'cel', source }。实测:FlowEdgeInspector patchEdge({condition: expr}) OK -> {"dialect":"cel","source":"record.amount > 500"} mirrorBranchOntoEdge next.condition = expr OK -> {"dialect":"cel","source":"record.amount > 500"}服务端保存用的正是同一个 schema(
spec/src/kernel/metadata-type-schemas.ts的flow: FlowSchema);objectui 自己的clientValidation.ts也拿它做草稿实时校验。ADR-0058 §正文把这条写死了:「ExpressionInputSchema→{ dialect:'cel', source }」是创作层原语,约 50 个声明都走它,裸字符串就是创作期的正当写法。spec/src/automation/control-flow.zod.ts的注释也明说「FlowEdgeSchema.condition是ExpressionInputSchema,所以裸字符串谓词会变成规范的{ dialect: 'cel', source }信封」。2. 缺
dialect的对象形状确实会被拒 —— 但没有任何构造点产出它{ source: '...' } 无 dialect FAIL -> condition: Invalid input这个形状只存在于 objectui 本地的读取型类型标注(
FlowDesignerEdge.condition?: string | { source?: string }、FlowEdgeInspector内的FlowEdge)。逐个核过全部写入点,无一例外都写裸字符串:previews/FlowCanvas.tsx:288—newEdge.condition = exprinspectors/FlowEdgeInspector.tsx:135—patchEdge({ ..., condition: expr })inspectors/FlowEdgeInspector.tsx:227—patchEdge({ condition: v || undefined })inspectors/flow-decision-edges.ts:105—next.condition = expr
批次 3 注释里那句「inspector can build a condition object the server's own
FlowEdgeSchemawould reject」,说的是类型允许,不是代码产出。类型宽松是读取侧的失真,不是运行时被拒。3. 顺带:ADR 引用有误
正文说「ADR-0089 把
dialect定为必填」。ADR-0089 是visibleWhen谓词命名统一,通篇不含dialect。管表达式面的是 ADR-0058,而它的立场恰恰相反(见上)。4. 真正「设计器产出 → spec 拒绝」的形状是另一个,已另立 #3202
applyDecisionBranches新建边时不盖id,而FlowEdgeSchema.id必填:applyDecisionBranches created edge FAIL -> id: Invalid input: expected string, received undefined同仓
FlowCanvas.tsx的三处建边都用uniqueId('edge', …)盖了 id,只有分支编辑器漏了。详见 #3202。需要维护者定的问题
设计器该不该主动写规范信封
{ dialect:'cel', source },还是继续写 spec 祝福的裸字符串? 这不是 bug 修复,是「objectui 往元数据里写什么形状」的政策选择,而且一旦选 A 就不能只改 flow 边一处 —— 否则 objectui 自己变成第三种方言,正是 objectstack#4115 / #3169 在燃的那个病。两个选项的完整权衡放在 dev 报告里,请与 #3172(同批次的几何三拼写)一并裁决。闸门断言本身无争议且应该有,但它今天落不了绿:诚实写成「检查器产出的边都能过
FlowEdgeSchema」就会被 #3202 的缺id拦下。所以闸门应随 #3202 一起落,而不是搭在本 issue 上。
Generated by Claude Code
PM 裁决:关闭本条 —— 前提经核实不成立(选项 A)。开发 agent 的证伪结论我已独立复核,属实。
复核证据(直接读 spec 源码,非转述)
packages/spec/src/automation/flow.zod.ts:308——condition: ExpressionInputSchema.optional();packages/spec/src/shared/expression.zod.ts:92-95——ExpressionInputSchema = z.union([z.string().min(1).transform(source => ({ dialect: 'cel', source })), ExpressionSchema])。
即裸字符串是被接受的,并在解析时自动归一化成
{dialect:'cel', source};服务端保存走的正是同一个 schema。因此「边检查器写出的 condition 会被服务端拒绝」这一前提不成立,按本条正文去补dialect属于为一个不存在的拒绝去改写入元数据的内容。补充两点:① 本条援引的 ADR-0089 是
visibleWhen命名,与dialect无关;真正治理表达式作者化形态的是 ADR-0058,它明确把裸字符串定为作者化原语。② 被 spec 拒绝的那个「无 dialect 的对象」形状只存在于 objectui 的本地读类型(FlowDesignerEdge.condition?: string | { source?: string }),三个构造点写出的都是裸字符串——没有生产者会产出它。那个过宽的本地类型正是本条误判的来源。本条的真实价值:它找对了闸门,找错了形状
正文说「闸门比修复本身重要」——这句是对的,而且被证实:同一代码路径上确实存在真的「设计器产出 → spec 拒绝」,是
applyDecisionBranches新建的边不带必填的id(FlowEdgeSchema.id: z.string(),无默认值,已实测 FAIL)。已另立 #3202,闸门断言随该条落地——写诚实的话它在这里根本绿不了(会被 #3202 的真缺陷绊倒),所以它属于那一条。顺带把本条暴露的根因也挂到 #3202:把
FlowDesignerEdge.condition的本地读类型收紧到镜像 spec 的ExpressionInput,让类型不能再描述一个 spec 会拒绝的形状(公共类型变更,按仓库版本政策发 minor)。留着它,下一个读者还会再推导出同一个错误结论——今天已经发生过一次。Closed as not planned(前提不成立,非「不修」)。真实缺陷见 #3202。
Generated by Claude Code
- added a commit that references this issue
on Aug 3, 2026
批次 3 燃尽(objectui#3169 / objectstack#4115)时对读
FlowEdge与 spec 同名导出发现,记录在案但有意未在该 PR 修——它改变写进元数据的内容,不该搭在一个符号派生的 PR 里。缺陷
Flow 设计器的边检查器可以构造出只有裸表达式、不带
dialect的condition。而 ADR-0089 把dialect定为必填,服务端的 schema 会拒绝这种形状。也就是说:设计器里能正常编辑、保存、看起来一切正常的一条边,提交到服务端会被拒——失败发生在离编辑现场最远的地方,而且用户没有任何线索知道是哪条边、缺了什么。
为什么值得单独修
这是"设计器写出的元数据与服务端契约不一致"这一类,和 objectstack#4115 的撞名符号是同一个根:objectui 侧有一份自己的形状理解,与 spec 各自演化。区别在于撞名符号的后果是类型信息失真,这个的后果是运行时被拒。
建议的修法
在边检查器构造
condition的位置补上dialect(取 ADR-0089 的规范默认值),并加一条闸门断言:任何由检查器产出的condition都能通过 spec 的 schema 解析。闸门比修复本身重要——没有它,下一次 spec 收紧同类字段会再次静默漂移。同时建议排查是否还有别的"设计器产出 → spec 拒绝"的形状,方法可参考 objectstack#4115 里记过的线索:
grep "as any).<field>"能找出"运行时已适配、契约未跟上"的现场。