Skip to content

refactor(spec,lint): one declaration of where ADR-0031 regions live (#4401) - #4408

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

refactor(spec,lint): one declaration of where ADR-0031 regions live (#4401)#4408
os-zhuang merged 1 commit into
mainfrom
claude/loving-rubin-wd2qq0

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4401。接 #4381 / #4388 / #4399

问题

region 是住在 FlowNodeSchema.config(开放 z.record)里的子图。类型系统里没有任何东西说明「哪个节点类型的哪个 key 装着 region」,所以每一个需要够到 region 节点的 pass 都得被单独告知 —— 而在一周之内,有三个各自被告知了,来自两个各自都正确的改动:

pass 自带的表
mapFlowNodes(ADR-0087 转换) spec FLOW_REGION_SLOTS
validateControlFlow / normalizeControlFlowRegions / collectFlowGraphs spec regionSlotsOf
walkFlowNodes(lint flow 规则) lint REGION_SLOTS

三份各自都配了 reconciliation 测试钉住自己。于是每一份都被保护着不偏离 schema,却没有任何测试会因为三份互相偏离而失败 —— 同时加第四种构造要改三个地方,漏掉一个的失败模式恰好就是 #4347#4380 各自报告的那种静默盲区。

我修正了 #4401 里自己提的方向

issue 里我提议「把 flow-walk.ts 的能力搬进 spec,统一成一套 walker」。仔细比对三者之后这是错的:

collectFlowGraphs walkFlowNodes mapFlowNodes
单位 图(nodes+edges) 节点 节点(copy-on-write 重写)
输入 FlowNodeParsed 原始作者记录 原始 dict
产出 {scope, nodes, edges} {node, path, localConfig, regionTrail, depth} 重写后的树

这是三件不同的事。而且 walkFlowNodes 从节点 label 生成人读诊断面包屑 —— 那是消费方逻辑,不是协议,搬进 spec 违反 Prime Directive #2。把一张四行的表的重复,换成一个谁都服务不好的 walker,不划算。

所以收敛范围窄得多:只统一那份数据,三个遍历各自保留。 这恰好也是 issue 陈述的风险(「加第四种构造要改三个地方」)的完整解法。

改动

  • 新增 @objectstack/spec/automation 导出 FLOW_REGION_SLOTS(以及 FLOW_REGION_SLOTS_BY_TYPE / FLOW_REGION_CONFIG_KEYS 两个派生视图),成为这件事的唯一陈述。
  • 放在一个零 import 的模块里,这样 spec/conversions/walk.ts 能读它、同时保持它被写成的样子(纯形状 walker,不引 schema)。把槽位映射到「它的值该用哪个 Zod schema 解析」这半留在 control-flow.zod.ts —— 那是 schema 的事。
  • lint 的 REGION_SLOTS / REGION_CONFIG_KEYS 改为从 spec 投影。
  • 三个 reconciliation 测试合并成一个 region-slots.test.ts,保留其中最强的那个做法(fix(lint): flow 规则能看进 try_catch / loop / parallel 嵌套 region(#4380) #4388 的):它行为性地推导每个构造的 region 键 —— 拿 region 形状的值去探测 config schema 看哪些键存活,而不是读 .shape 的键名。

补上一个原本会留下的洞

合并后的测试仍要手写一份「哪些构造带 region」的映射,新增第四种时作者可能两处都忘。所以加了一条:遍历本模块导出的每一个 *ConfigSchema 去探测,任何未申报却接受 region 形状的 schema 直接失败。

已验证 ratchet 真的咬得住 —— 临时从表里删掉 try_catch.catch:

× declares exactly the slots each construct accepts, at the right arity
AssertionError: region slots for 'try_catch':
  expected [ { key: 'try', arity: 'one' } ] to deeply equal [ Array(2) ]

无行为变更

这是纯重构,最强的安全性质就是所有既有测试原封不动通过 —— 这正是本次验证的重点:

gate 结果
pnpm build 71/71 ✅
pnpm test 132/132 ✅(spec 443 / lint 728 / service-automation 555 全部未改动)
pnpm typecheck 119/119 ✅
pnpm lint
check:generated 8/8 ✅
api-surface 已重新生成,新增 5 个导出

Generated by Claude Code

…4401)

A region is a sub-graph inside `FlowNodeSchema.config`, an open `z.record`.
Nothing in the type system says which key on which node type holds one, so every
pass that needs to reach a region node has to be told — and within one week three
of them were told separately, by two changes that were each correct on their own
(#4381 and #4388):

  mapFlowNodes (ADR-0087 conversions)                spec   FLOW_REGION_SLOTS
  validateControlFlow / normalizeControlFlowRegions
    / collectFlowGraphs                              spec   regionSlotsOf
  walkFlowNodes (lint flow rules)                    lint   REGION_SLOTS

Each pinned its own copy with its own reconciliation test. So every copy was
protected from drifting away from the schemas, and nothing would have failed if
the copies drifted from each other — while adding a fourth construct meant
editing three places, and missing one reproduces exactly the silent blind spot
#4347 and #4380 were both filed about.

- New `@objectstack/spec/automation` export `FLOW_REGION_SLOTS` (+ the
  `FLOW_REGION_SLOTS_BY_TYPE` / `FLOW_REGION_CONFIG_KEYS` views) is the only
  statement of the fact. Import-free module, so `spec/conversions/walk.ts` can
  read it and stay the pure shape walker it was written as; mapping a slot onto
  the Zod schema its value parses as stays in `control-flow.zod.ts`.
- The three reconciliation tests collapse into `region-slots.test.ts`, keeping
  the strongest of them (#4388's): it derives each construct's region keys
  BEHAVIOURALLY, by asking the config schema what it accepts in a region shape,
  rather than reading names off `.shape`. It also probes every other exported
  `*ConfigSchema`, so a new region-bearing construct cannot be added without
  either declaring its slots or failing here.

The three walks are deliberately left separate: different inputs (parsed vs raw
authored records), different units (a graph, a node, a copy-on-write rewritten
tree), and the lint one formats human diagnostic trails from node labels —
consumer logic, not protocol (Prime Directive #2).

No behaviour change: every existing test passes unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSBbKMdDgHjGpQdrvQUQXj
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 31, 2026 12:03pm

Request Review

@github-actions github-actions Bot added 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/lint, @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/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint, 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/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, @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/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/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/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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 31, 2026 12:16
@os-zhuang
os-zhuang merged commit 4bfd455 into main Jul 31, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/loving-rubin-wd2qq0 branch July 31, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Three independent declarations of "where ADR-0031 regions live", and two region walkers inside @objectstack/lint

2 participants