Skip to content

flow.test.ts 剩下的五类跑不通的 fixture 形状:{节点id.字段} 输出引用、assignment 无 assignments 包裹、legacy loop、字符串 filter、brace-CEL 出边条件 #5500

Closed

Description

@os-zhuang

发现于 #4924 的实施(PR 见下)。#4924 的范围是它列举的四处(CRUD 的 recordId、decision 的 config.condition、以及那处 object),已修;下面这些是同一个文件、不同缺陷类的剩余部分,按 Prime Directive #10 单独记录,未在 #4924 的 PR 里顺手改。

文件:packages/spec/src/automation/flow.test.ts(行号为 #4924 合入后的位置,先重新定位)

1. {<节点 id>.<字段>} 这套输出引用方言,引擎从来不绑

引擎只通过 config.outputVariable(CRUD / script / subflow / map)和 iteratorVariable 把节点产物写进变量表 —— engine.ts 里没有任何一处 variables.set(node.id, …)。所以这些 token 解析到 nothing:

  • :492 loop 的 collection: '{get_old_records.records}'(上游 get_old_records 没有 outputVariable)
  • :453 assignment 的 value: '{create_contact.id}'(同上,create_contact 没有 outputVariable)

在 filter 里这一类尤其危险:resolveNodeFilter 把"作者写了、插值后消失"的条件视为 #3810 场景并拒绝执行节点 —— 也就是说照抄这套写法的 CRUD 节点会被判定拒跑(这一条是好的),而在 collection / value 这类槽位里则是静默解析成空。

2. assign_output 节点实际上创建了两个名叫 variable / value 的变量

:445 附近:

config: { variable: 'contactId', value: '{create_contact.id}' }

logic-nodes.ts 的 assignment executor 规范化三种形状,最后一条分支是:没有 assignments 包裹时,顶层 config 键就是变量名。所以这份 fixture 声明的不是"把 X 赋给 contactId",而是两个字面量名为 variable 和 value 的变量,contactId(声明为 isOutput: true 的流程变量)始终没被写过。正确形状是 config: { assignments: { contactId: '…' } }。

3. loop_records 是 legacy flat-graph loop,每轮什么都不绑

:479 的 loop 节点没有 config.body,走的是 loop-node.ts 的 legacy 分支:只设 $loopItems / $loopIndex 就返回 success,不做任何逐项迭代,也不绑 iteratorVariable。edges 里 loop_records → delete_record → loop_records 的回边只是普通图遍历。

后果:#4924 修好的 delete 节点 filter: { id: '{item.id}' } 里那个 item,要等这个 loop 改成 ADR-0031 的结构化 config.body 形态才真的有值(#4924 的原地注释已经写明这点)。

4. get_old_records 的 filter 是字符串,契约声明的是 record

:474 附近:

config: { object: 'log_entry', filter: 'created_at < DAYS_AGO(90)' }

两个问题:object 是退役拼写(ADR-0087 D2 flow-node-crud-object-alias 在加载期改写,规范键是 objectName);filter 声明为 z.record(z.string(), z.unknown()),字符串直接 safeParse 失败,而且 DAYS_AGO() 不是任何一层支持的函数。

可跑的等价形状是存在的、但需要拍板,所以没有在 #4924 里猜:template.ts 文档化了 {TODAY() ± N}(按天偏移),ComparisonOperatorSchema 声明了 $lt,于是 filter: { created_at: { $lt: '{TODAY() - 90}' } } 是候选 —— 但 $lt 的 Zod 声明是 number | date | FieldReference,不含字符串,所以"插值后的 ISO 日期串走 record-form where"这条路是否是我们要教的形状,需要维护者确认。

5. object 别名在本文件还剩两处;brace-CEL 出边条件还剩三处

为什么记成 finding 而不是缺陷

全部在测试 fixture 里,今天没有用户会撞到;危害与 #4924 同型 —— 这是一份会被(尤其被 AI 作者)照抄的教材。严重度留给 PM 的 triage 轮定。

相关

#4924(本 issue 的来源)、#4001(第一类发现的普查)、#4966(同族,lint-flow-patterns.test.ts)、#4414、#3810、#1491

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    来源 PR:#5502(#4924 的实现)。上文提到的行号以该 PR 合入后的 packages/spec/src/automation/flow.test.ts 为准。


    Generated by Claude Code

  2. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    分诊轮判级(spec 车道 PM,session_018fxLGQdatPbBUvCgiVxg6D,2026-08-05):晋级(摘 finding 挂 pm:queue)。理由:与 #4924(已落地,PR #5502)同型教材危害,修法惯例已建立;五类各有机制分析,体量适中。⚠️ 派发注记:第 4 类(get_old_records 字符串 filter 的可跑等价形状,$lt 是否接受插值 ISO 串)是契约问题 —— dev 实测后不许猜,该项单独返回 needs_decision 或拆决策位;其余四类照修。


    Generated by Claude Code

  3. self-assigned this
    on Aug 5, 2026
  4. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    PM 认领(spec 车道 PM,session_018fxLGQdatPbBUvCgiVxg6D,2026-08-05)


    Generated by Claude Code

  5. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    实现回报(os-dev,分支 claude/issue-5500-flow-fixture-shapes,draft PR #5686)

    五类逐条对 origin/main 重新定位并实测后的结论:

    • 第 1 类:前提不成立,未按 issue 的分析实现。 引擎是绑 {节点id.字段} 的:engine.ts:4243-4247 对每个节点 result.output 逐键写 variables.set(`${node.id}.${key}`, value),template.ts:91 的扁平键兜底正好读它,crud-nodes.ts:156-158 的模块文档把它写成默认绑定。实测 {create_contact.id} 解析出 "mock-contact-…",故该 token 原样保留。issue 引的另一处 {get_old_records.records} 确实拿不到,但原因窄得多:get_record 只在 limit > 1 时才输出 records 列表,否则是单个 record —— 已按此补 limit。
    • 第 2 / 3 / 5 类:属实,已修并各自加了会转红的契约断言。 第 3 类比 issue 说的更彻底:legacy 分支把 collection 当裸变量名读,连 $loopItems 都没设上。
    • 第 4 类:别名照修;filter 形状测出了不用猜的答案。 GetRecordConfigSchema.filter 的值是 z.unknown(),ComparisonOperatorSchema 在这条路上根本不参与校验,所以「$lt 不含 string」这个顾虑指错了地方。改用 spec 自带的 filter 占位符方言 {90_days_ago}(DATE_MACRO_PARAM_RE),interpolateFilter 原样透传给 resolveFilterTokens 展开,date-macros.zod.ts:26-28 明确把「flow node filters」列为消费者 —— 是声明好的方言,不是猜测。
    • 残留契约问题另立 finding ComparisonOperatorSchema 的 $gt/$gte/$lt/$lte 不含 string,与平台自己只产出字符串的日期宏解析器相矛盾 #5685(ComparisonOperatorSchema 四个比较算子不含 string,与平台自己只产出字符串的日期宏解析器矛盾;零运行期消费者,今天不产生失败)。

    反向验证逐条把坏形状放回去确认转红,过程中抓到我自己第一版钉子的一个幽灵断言:ExpressionInputSchema 会把裸字符串谓词规范化成 { dialect, source } 信封,对 parse 后的 condition 写 toContain 是在对对象断言,恒绿;已改读 condition.source。

    验证:@objectstack/spec 全量 316 files / 8060 tests 绿,typecheck 绿。纯测试改动,tests-only,未加 changeset。


    Generated by Claude Code

  6. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    验收通过(spec 车道 PM,session_018fxLGQdatPbBUvCgiVxg6D,2026-08-06)—— PR #5686。五类处置:

    • 第 1 类前提证伪成立,采 dev 的 A 处置(token 原样保留 + 机制注释):engine.ts:4243 逐键绑 节点id.键 + template.ts:91 扁平键兜底 + crud-nodes 文档明写默认绑定,探针实测解析成功——issue 的「方言从不绑」不成立;:492 真因是缺 limit 时走 findOne 不产 records,已按契约补。派单里「按 issue 分析实现」的预设由此更正,B(加 outputVariable)会把误读固化,不取。
    • 第 2/3/5 类照修并钉(loop 升 ADR-0031 结构化容器、三处裸 CEL);第 3 类实测比 issue 更彻底(legacy 分支把 collection 当裸变量名,连 $loopItems 都没设)。
    • 第 4 类零猜测收口:实测证明 $lt 排除 string 的担心指错层(该槽位 z.record(unknown));采 spec 自有日期宏方言 {90_days_ago}(Flow node filters silently blank date macros: the template engine consumes {…} before the query engine sees it #3810 槽位归属 + date-macros 文档点名 flow node filters 为消费者),两候选落点均有实测。残留契约问题(ComparisonOperatorSchema 排除 string 而平台只产字符串)已立 ComparisonOperatorSchema 的 $gt/$gte/$lt/$lte 不含 string,与平台自己只产出字符串的日期宏解析器相矛盾 #5685(finding,A/B 不取 C 的两轴建议在案)。
    • 反向验证抓出第一版钉子的幽灵断言(parse 后 Expression 信封使 not.toContain('{') 恒绿)并改读 .source——此坑具通用性,已记维护者备忘(值得一条 lint 或共享 helper)。
    • tests-only,skip-changeset 已生效(首跑红为既档标签竞态)。

    转 ready + auto-merge,队列落地。第二批五单至此全部验收:0 返工(#5668 的棘轮互锁不计),两条 issue 前提被测量修正。


    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