Skip to content

Commit 44fc2a2

Browse files
committed
fix(validate): 0.6.12 校验过严回归 + 修内置 workflow 拓扑反向
E2E 把 44 个内置 workflow 跑一遍 validate,发现 6 个失败。两类问题: 1. 0.6.12 新加的"output 唯一性"对两类合法 ao 设计模式误报: - any_completed 分支收敛:多个并行 step 同名 output + 下游 any_completed 引用,是"任一分支完成即走"的设计 (incident-response / hiring-pipeline) - loop 迭代覆盖:种子 step + loop step 反复覆盖同名 output,是"原地修改" 的迭代模式 (content-publish 的 write/revise 循环) 修复:parser.ts 加两类例外不报错 2. 3 个内置 workflow 本身有拓扑反向 bug,新校验把它们暴露出来: - legal-consultation: legal_risk 引用下游 document_review.output 但 depends_on 缺它,补上 - investment-analysis: risk 引用下游 financial.output 但 depends_on 缺它 - xiaohongshu-content: visual_plan 引用 write_notes.output 但 depends_on 缺它 新增 2 项 parser 测试覆盖 any_completed / loop 例外。 全量 114/114 通过 + 44/44 内置 workflow validate 通过
1 parent 2870a2c commit 44fc2a2

6 files changed

Lines changed: 67 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
本项目采用 [语义化版本](https://semver.org/lang/zh-CN/)
44

5+
## [0.6.13] - 2026-04-27
6+
7+
### Fixed
8+
- **回归修复**:0.6.12 新加的"output 唯一性校验"对两类合法的 ao 设计模式误报,导致 6 个内置 workflow 在 validate 时失败。现在校验放过两类例外:
9+
- **`any_completed` 分支收敛**:多个并行 step 产出同名 output,下游用 `depends_on_mode: any_completed` 引用,是有意的"任一分支完成即走"设计(如 incident-response.yaml 的多团队并行分析、hiring-pipeline.yaml 的多维度评估)
10+
- **loop 迭代覆盖**:种子 step 产生初始值 + loop step 反复覆盖同名 output,是常见的"原地修改"迭代模式(如 content-publish.yaml 的 write/revise 循环)
11+
- 修了 3 个内置 workflow 的拓扑反向引用:legal-consultation.yaml / investment-analysis.yaml / xiaohongshu-content.yaml 的相关 step 补 `depends_on`(不是新校验过严,是 yaml 本身设计就有缺陷,新校验把它们暴露出来)
12+
13+
### Tests
14+
- 新增 2 项 parser 测试覆盖 any_completed / loop 迭代覆盖的合法重名例外
15+
- E2E 验证:44 个内置 workflow 现在全部通过 validate
16+
517
## [0.6.12] - 2026-04-27
618

719
### Fixed

src/core/parser.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export function validateWorkflow(workflow: WorkflowDefinition): string[] {
7474

7575
// step.output 唯一性检查:两个 step 不能 output 到同一个变量名
7676
// 否则下游引用拿到的值取决于 context Map 的写入顺序,不可预期
77+
//
78+
// 两类合法例外不报错:
79+
// 1. any_completed 分支收敛:下游用 depends_on_mode: any_completed 引用这些
80+
// 重名 step(任一分支完成即走,重名 output 是有意设计)
81+
// 2. loop 迭代覆盖:重名 step 中有任一个带 loop 字段(种子 step + loop step
82+
// 反复覆盖同名 output,是常见的"原地修改"迭代模式,如 write/revise/brand_review 链)
7783
const outputToSteps = new Map<string, string[]>();
7884
for (const step of workflow.steps) {
7985
if (!step.output) continue;
@@ -82,9 +88,16 @@ export function validateWorkflow(workflow: WorkflowDefinition): string[] {
8288
outputToSteps.set(step.output, owners);
8389
}
8490
for (const [outName, owners] of outputToSteps) {
85-
if (owners.length > 1) {
86-
errors.push(`output 变量 "${outName}" 被多个 step 同时产出: ${owners.join(', ')}(重名会让下游引用结果不确定)`);
87-
}
91+
if (owners.length <= 1) continue;
92+
const ownerSet = new Set(owners);
93+
const hasAnyCompletedConsumer = workflow.steps.some(s =>
94+
s.depends_on_mode === 'any_completed'
95+
&& s.depends_on?.some(d => ownerSet.has(d))
96+
);
97+
if (hasAnyCompletedConsumer) continue;
98+
const hasLoopOwner = owners.some(id => stepById.get(id)?.loop);
99+
if (hasLoopOwner) continue;
100+
errors.push(`output 变量 "${outName}" 被多个 step 同时产出: ${owners.join(', ')}(重名会让下游引用结果不确定)`);
88101
}
89102

90103
// 计算每个 step 的 DAG 上游 step ids(递归 depends_on 闭包,不含自身)。

test/run.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,42 @@ test('检测 condition 字段里的未定义变量', () => {
111111
);
112112
});
113113

114+
test('output 重名例外:any_completed 分支收敛模式合法', () => {
115+
// 多个并行 step 产出同名 output,下游用 any_completed 引用——是有意的"分支收敛"设计
116+
const wf = parseWorkflow(workflowPath);
117+
if (wf.steps.length < 4) throw new Error('fixture 至少需要 4 个 step');
118+
// 让两个并行 step 都 output 同名
119+
wf.steps[1].output = 'analysis_result';
120+
wf.steps[2].output = 'analysis_result';
121+
// 下游 step(已存在的)改成 any_completed
122+
wf.steps[3].depends_on = [wf.steps[1].id, wf.steps[2].id];
123+
wf.steps[3].depends_on_mode = 'any_completed';
124+
const errors = validateWorkflow(wf);
125+
// 不应报 output 重名错误
126+
assert(
127+
!errors.some(e => e.includes('analysis_result') && e.includes('多个 step 同时产出')),
128+
`any_completed 模式下 output 重名应被允许,实际错误: ${errors.join('; ')}`
129+
);
130+
});
131+
132+
test('output 重名例外:loop 迭代覆盖模式合法', () => {
133+
// write 产生种子 + revise 用 loop 反复覆盖同名 output,是合法的"原地修改"迭代
134+
const wf = parseWorkflow(workflowPath);
135+
if (wf.steps.length < 3) throw new Error('fixture 至少需要 3 个 step');
136+
wf.steps[0].output = 'doc';
137+
wf.steps[1].output = 'doc';
138+
wf.steps[1].loop = {
139+
back_to: wf.steps[0].id,
140+
max_iterations: 3,
141+
exit_condition: '{{doc}} contains 通过',
142+
};
143+
const errors = validateWorkflow(wf);
144+
assert(
145+
!errors.some(e => e.includes('"doc"') && e.includes('多个 step 同时产出')),
146+
`loop 迭代覆盖应被允许,实际错误: ${errors.join('; ')}`
147+
);
148+
});
149+
114150
test('检测 output 重名:两个 step 产出同一个变量', () => {
115151
const wf = parseWorkflow(workflowPath);
116152
// 强制把第二个 step 的 output 改成和第一个相同

workflows/investment-analysis.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ steps:
8787
8888
每条风险明确标「发生概率」(高/中/低)。
8989
output: risk
90-
depends_on: [research]
90+
depends_on: [research, financial]
9191

9292
- id: cfo_opinion
9393
role: "finance/finance-financial-forecaster"

workflows/legal-consultation.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ steps:
9797
9898
⚠️ 所有法条引用不确定处必须明确标「需核实最新法规」。
9999
output: risk_analysis
100-
depends_on: [intake]
100+
depends_on: [intake, document_review]
101101

102102
- id: opinion
103103
role: "legal/legal-policy-writer"

workflows/marketing/xiaohongshu-content.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ steps:
8181
8282
风格参考:小红书当前热门的视觉趋势。
8383
output: visual_plan
84-
depends_on: [topic_planning]
84+
depends_on: [topic_planning, write_notes]
8585

8686
- id: optimize
8787
role: "marketing/marketing-xiaohongshu-operator"

0 commit comments

Comments
 (0)