Skip to content

fix(spec,service-automation): registerFlow's remaining validators cover structured regions (#4389) - #4399

Merged
os-zhuang merged 1 commit into
mainfrom
claude/loving-rubin-wd2qq0
Jul 31, 2026
Merged

fix(spec,service-automation): registerFlow's remaining validators cover structured regions (#4389)#4399
os-zhuang merged 1 commit into
mainfrom
claude/loving-rubin-wd2qq0

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes#4389. 接 #4347 / #4381

问题

#4347 修掉了「元数据随嵌套深度而变」的转换谓词校验两半。registerFlow 里还剩三个校验器只遍历 flow.nodes —— ADR-0031 容器把整张子图放在自己开放的 config 里,于是它们各自都在「只校验了一部分、却按整个 flow 汇报」。

先量,再改

两个是 hard-fail。扩大一个会拒绝的校验器的遍历范围是行为变更,不是 bugfix —— 所以先测量,而不是先动手。

测量方式是把三个 example app 的全部 flow 通过真正的 registerFlow 注册(转换 + parse + region 归一化都走完),再用 collectFlowGraphs 取出每个非顶层 graph,对它跑各校验器自己的代码(私有方法直接调用,不重写检查逻辑):

━━━ examples/app-crm flows 1/1 region graphs 0
━━━ examples/app-showcase flows 29/29 region graphs 9
━━━ examples/app-todo flows 4/4 region graphs 0
━━━ TOTAL
validateControlFlow 0
validateNodeTypes 0
validateNodeConfigKeys 0
validateNodeInputSchemas 0

0 新增违规。所以三个校验器按现有严重级别直接落地,不需要走 #4059#4277 那条 warn-then-error 阶梯。

样本确实薄(9 个 region graph / 9 个 region 节点),但风险不对称地小,理由见下。

改动

validateControlFlow 递归(hard-fail)

嵌套在别的容器 region 里的容器,注册期从来没被校验过 —— 它会一路走到运行时,由 runRegionfindRegionEntry迭代中途抛出,此时外层 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:

loop 'sweep' body · node 'w' (create_record): unknown config key `fieldz` at config.fieldz — did you mean `fields`?

不会从容器侧重复报:三个容器 descriptor 都把 region 槽声明成裸的 nodes: { type: 'array' }(无 items),所以 schema 对位遍历到那里就停,不会再下探一遍。有测试钉住这一点。

刻意不改 validateNodeInputSchemas

它同样只走顶层,但不扩,两个理由:

  1. 三个 app 的 159 个 flow 节点里声明 inputSchema 的是 0 个 —— 没有实证读者。
  2. 它拿 config 值的运行时类型和声明类型比(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 个通过。

验证

gate结果
pnpm build71/71 ✅
pnpm test132/132 ✅
pnpm typecheck119/119 ✅
pnpm lint
check:generated8/8 ✅

另注:三份 region 槽位表

#4388(晚 8 分钟合入)在 packages/lint/src/flow-walk.ts 引入了第三份「region 住在哪里」的声明,和 spec/conversions/walk.tsFLOW_REGION_SLOTSspec/automation/control-flow.zod.tsregionSlotsOf 并存;lint 包内部现在也是两个 walker 并行(validate-expressions.tscollectFlowGraphs,另外四条规则用 flow-walk.ts)。

本 PR 没有动它 —— 那是另一条轴上的问题,而且是别人刚落地的模块。会另开 issue 提合并方案。


Generated by Claude Code

…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
@vercel

vercelBot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredJul 31, 2026 11:16am

Request Review

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/service-automation, @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/service-automation, @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/service-automation, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/service-automation, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/service-automation, @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-zhuang@claude