fix(spec,service-automation): registerFlow's remaining validators cover structured regions (#4389) - #4399
Merged
Merged
Conversation
…er structured regions (#4389) #4347 closed the conversion and predicate halves of "metadata behaves differently depending on how deep it sits". Three validators were left walking `flow.nodes` only, so the same class stayed open one layer over: an ADR-0031 container keeps a whole sub-graph in its open `config`, and each of these checked part of the flow while reporting on all of it. - `validateControlFlow` recurses. A container nested inside another container's region was never validated at registration — it reached run time, where `runRegion` → `findRegionEntry` throws mid-iteration, after the enclosing loop has begun and its side effects have landed. Cannot break a working flow: everything newly rejected was already guaranteed to throw on execution. Also closes cycle detection over nested regions, since region bodies are cycle-checked by `analyzeRegion` here rather than by `detectCycles`. - `validateNodeTypes` covers region nodes (soft-fail). - `validateNodeConfigKeys` covers region nodes (hard-fail), with the region in each violation. No double-reporting from the container side: all three container descriptors declare their region slot as a bare `nodes: { type: 'array' }` with no `items`. Measured before extending the two hard-fail checks, since widening a rejecting validator is a behaviour change rather than a bugfix: registering every flow in app-showcase, app-crm and app-todo through the real `registerFlow` and re-running each validator's own code over all 9 region graphs produced 0 new findings. So they land at existing severity rather than staged through a warning window. `validateNodeInputSchemas` is deliberately NOT extended — 0 uses across all 159 example flow nodes, and it compares a config value's runtime type against the declared one, so extending it would newly fail a region node carrying a `{var}` template string in a `number`-typed slot. Noted on #4389 instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HSBbKMdDgHjGpQdrvQUQXj
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 31, 2026 11:36
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4389. 接 #4347 / #4381。
问题
#4347 修掉了「元数据随嵌套深度而变」的转换和谓词校验两半。
registerFlow里还剩三个校验器只遍历flow.nodes—— ADR-0031 容器把整张子图放在自己开放的config里,于是它们各自都在「只校验了一部分、却按整个 flow 汇报」。先量,再改
两个是 hard-fail。扩大一个会拒绝的校验器的遍历范围是行为变更,不是 bugfix —— 所以先测量,而不是先动手。
测量方式是把三个 example app 的全部 flow 通过真正的
registerFlow注册(转换 + parse + region 归一化都走完),再用collectFlowGraphs取出每个非顶层 graph,对它跑各校验器自己的代码(私有方法直接调用,不重写检查逻辑):0 新增违规。所以三个校验器按现有严重级别直接落地,不需要走 #4059 → #4277 那条 warn-then-error 阶梯。
样本确实薄(9 个 region graph / 9 个 region 节点),但风险不对称地小,理由见下。
改动
validateControlFlow递归(hard-fail)嵌套在别的容器 region 里的容器,注册期从来没被校验过 —— 它会一路走到运行时,由
runRegion→findRegionEntry在迭代中途抛出,此时外层 loop 已经开跑、副作用已经落地。这正是 ADR-0031 的「reject the malformed before it can run」要避免的。这一条不可能弄坏能正常工作的 flow:新被拒绝的东西本来就注定在执行时抛异常,只是从「跑到一半炸」提前成「注册期拒绝」。
顺带补上了嵌套 region 的环检测 —— region body 的环是由
analyzeRegion查的(不是detectCycles),所以这次递归同时把那个洞也关了,detectCycles不需要单独改。validateNodeTypes覆盖 region 节点(soft-fail)零风险。loop body 里的节点和它旁边的节点一样会被执行,漏掉 region 意味着这条「预测
NO_EXECUTOR」的警告恰好在最难定位运行时失败的那批节点上失声。validateNodeConfigKeys覆盖 region 节点(hard-fail)visibleIf正是 #4277 要抓的拼写错误,而把节点挪进 region 就能恢复 #4277 关掉的那种静默。违规信息带上 region:不会从容器侧重复报:三个容器 descriptor 都把 region 槽声明成裸的
nodes: { type: 'array' }(无items),所以 schema 对位遍历到那里就停,不会再下探一遍。有测试钉住这一点。刻意不改
validateNodeInputSchemas它同样只走顶层,但不扩,两个理由:
inputSchema的是 0 个 —— 没有实证读者。actualType !== paramDef.type)。扩进 region 会让一个在number类型槽里写{var}模板字符串的 region 节点新失败 —— 而那是活着的作者写法。给一个已知有误报模式、又没有实证价值的检查扩大范围不划算。这个遍历缺口记在 #4389 上,没有顺手做。
测试
nested-region-parity.test.ts新增 9 个用例(该文件共 22 个),每组都是对称性对照 —— 同一份坏元数据,一次放顶层、一次放 loop body,判定必须一致:validateNodeConfigKeys:顶层拒 / region 里也拒且带 region 名 / 每个嵌套键只报一次 / 正确的 region 不动validateNodeTypes:顶层警告 / region 里也警告validateControlFlow:顶层容器的畸形 region 被拒 / 嵌套容器的畸形 region 也被拒且路径带两层(loop 'loop' body → loop 'inner' body)/ 良构的嵌套容器不动非空断言已验证:把两个源文件 stash 掉重新构建后跑,恰好这 4 个嵌套用例失败、18 个通过。
验证
pnpm buildpnpm testpnpm typecheckpnpm lintcheck:generated另注:三份 region 槽位表
#4388(晚 8 分钟合入)在
packages/lint/src/flow-walk.ts引入了第三份「region 住在哪里」的声明,和spec/conversions/walk.ts的FLOW_REGION_SLOTS、spec/automation/control-flow.zod.ts的regionSlotsOf并存;lint 包内部现在也是两个 walker 并行(validate-expressions.ts用collectFlowGraphs,另外四条规则用flow-walk.ts)。本 PR 没有动它 —— 那是另一条轴上的问题,而且是别人刚落地的模块。会另开 issue 提合并方案。
Generated by Claude Code