Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

Flow 模拟器把 { dialect, source } 信封形式的边守卫当成「没有条件」 #3216

Closed

Description

@os-zhuang

做 #3202(收紧设计器边类型)时越界发现,未在该 PR 里顺手改 —— 它是行为变更,不是类型收紧。

缺陷

packages/app-shell/src/views/metadata-admin/previews/simulator/ 里有两个读边守卫的地方,都只认裸字符串,信封形式一律返回 undefined:

// flow-simulator.ts:46
const condStr = (c: SimEdge['condition']): string | undefined => (typeof c === 'string' ? c : undefined);

// flow-sim-validate.ts:18
const edgeCondString = (c: SimEdge['condition']): string | undefined =>
  typeof c === 'string' ? c : undefined;

于是 flow-simulator.ts:281 处,一条守卫写成 { dialect: 'cel', source: 'amount > 10' } 的决策出边,在模拟时间线上报的是 Branch has no condition.,并被跳过 —— 模拟结果会走到 default 分支或直接停住,而引擎在真实运行时会正常求值这条守卫。flow-sim-validate.ts:154 的诊断也会因此少判一类。

为什么是真 bug 而不是设计

仓内其它每一个读同一个字段的地方都接受两种拼写:

  • previews/flow-canvas-layout.ts 的 conditionText —— 字符串或 { source } 都读(画布标签、FlowEdgeInspector 输入框、flow-decision-edges 的分支匹配都走它);
  • inspectors/expression-validate.ts 的 validateExpressionClient —— 注释就写着「Accept a bare string or an Expression envelope」;
  • inspectors/flow-decision-edges.test.ts 里已有一条用例把 { dialect: 'cel', source } 的边条件当作正常存量形状匹配。

而 @objectstack/spec 的 ExpressionInputSchema 是一个 ZodPipe:裸字符串在 parse 时就被规范化成 { dialect: 'cel', source } 信封,信封才是 spec 眼里的规范持久化形式(FlowEdgeSchema.condition 用的正是它)。也就是说,模拟器读不了的那种拼写,恰恰是平台自己产出的那种。

所以这不是「模拟器故意只支持简写」,而是两个读函数漏了一条分支 —— 与 #3202 同一族:一个过窄/过宽的本地读法与 spec 不一致。

建议修法

两处都改为走同一个读法(conditionText 已经是仓内的既有实现,或抽一个共享的 expressionSource(c)),不要各自再写第三、第四份 typeof c === 'string'。

顺带:flow-sim-types.ts:30 的 SimEdge.condition 仍写作 string | { source?: string } —— 那是 #3202 收紧掉的、spec 会拒绝(缺 dialect)的形状的最后一份复制。修这条时一并镜像 spec 的 ExpressionInput(import type 即可,别再重述一遍)。

验证建议

一条决策节点的出边,守卫存成 { dialect: 'cel', source: 'amount > 10' },变量给 amount = 20:期望模拟器选中该分支;当前会得到 Branch has no condition.。

相关

Activity

  1. self-assigned this
    on Aug 2, 2026
  2. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    认领:PM 循环第 4 轮(分片:objectui 整仓)
    会话:session_01PRJtkgUAaVG11FsJQbvZWA
    分支:claude/issue-3216-simulator-reads-expression-envelope
    Worktree:objectui-issue-3216

    与在途的 #3160(台账批次 6)并行:两者文件宇宙不相交(本条在 previews/simulator/,那条在 data-objectstack / plugin-chatbot / plugin-list + 账本文件)。

    分诊裁决(PM 可决):按正文修法——两处读法收敛到同一个读函数(用既有的 conditionText,或抽一个共享的 expressionSource(c)),不得再写第三、第四份 typeof c === 'string';SimEdge.condition 一并镜像 spec 的 ExpressionInput(import type,别重述——#3202 已证实重述会比 spec 更窄)。这是本族过宽读法的最后一处,收敛掉之后"边守卫怎么读"在本仓只剩一个答案。

    边界:不要碰 scripts/check-spec-symbol-derivation.mjs(#3160 正在改那个文件)。


    Generated by Claude Code

  3. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    验收:ACCEPT → PR #3217(CI 15 项全绿,进入合并)。

    交付内容:① 两处手写读法(condStr / edgeCondString)删除,双双改走 conditionText——没有新增第三份,并把"只有一个读法"写进该函数的 JSDoc;② 决策路由现在求值信封守卫,只带 ast 无 source 的信封仍如实报 "no condition"(没有可求值的东西就说没有,不伪造结果);③ validateFlowDraft 的"缺 default 分支"告警不再静默豁免信封守卫的决策;④ SimEdge.condition 用 import type 镜像 ExpressionInput,并加编译期断言(含 z.infer<typeof FlowEdgeSchema> extends SimEdge)登记进 tsconfig.typetests.json。

    方法值得记两条:测试先写且改前逐条失败(4 条断言,行为变更就该这样证明);测试固件不手写信封,而是把作者输入喂给 FlowEdgeSchema.parse 再断言——固件因此不可能与 spec 漂移。

    至此"边守卫怎么读"在本仓只剩一个答案,#3171 → #3202 → 本条这条线闭合。

    越界发现 #3218 已立案,严重度高于本条:HookDefaultInspector 同样只认裸字符串,而 HookSchema.condition 是同一个 pipe。后果不是显示问题而是数据丢失——存成信封的 hook 条件在检查器里显示为空,作者据此以为没有条件,而下一次编辑提交会用新值替换掉那条他从没见过的原守卫。已入 pm:queue,建议下一轮优先。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions