Skip to content

feat(spec,automation)!: converge script to a function call and parse script/subflow config at execute time (#4343) - #4516

Merged
os-zhuang merged 2 commits into
mainfrom
claude/script-config-parse-contract-gy035e
Aug 1, 2026
Merged

feat(spec,automation)!: converge script to a function call and parse script/subflow config at execute time (#4343)#4516
os-zhuang merged 2 commits into
mainfrom
claude/script-config-parse-contract-gy035e

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Closes #4343.

方向变更(已在 issue 上说明)

原 issue 要求给 script 的 config 契约做判别式(actionType)形态,再接 #4277 的执行期 parse。评估后改为激进收敛:与其给一套历史形态建模,不如退役它——四个分支里只有函数路径跑真实逻辑。

分支 真实行为
actionType: 'email' | 'slack' logger stub:写一行日志、报告成功、任何配置下都不投递任何东西
template / recipients / variables 喂给上面那个 stub,寻址一条从没被发出的消息
内联 config.script 从未执行——内置运行时没有服务端 JS 沙盒,节点 warn 后 no-op
其他 actionType 注册函数名的简写(invoke_function 只是个 marker,自身不指向任何东西)

留下的就是能用的:config.function(改为必需)+ inputs + outputVariable

这同时解决了 #4343 的原始诉求:契约无法 parse 的原因正是「合法键集随 actionType 变」——平铺 parse 要么误拒合法形态、要么放行一切。收敛之后契约天然扁平,scriptsubflow 一并接入 #4277 给扁平内置节点的执行期 parse(违约 = guard refusal,fault 边不可路由,#3863)。decision 按 issue 维持现状:唯一的键是可选的,parse 无物可查。

改动

退役(ADR-0049 removal 路线) — 5 个键打 retiredKey() 墓碑,各带自己的处方(三条替代是不同机制,不是一次改名):

退役 替代
actionType: 'email'(+ template/recipients/variables) notify 节点——经 messaging 服务真实投递:默认站内收件箱,装了 @objectstack/plugin-email 后走真实邮件
actionType: 'slack' connector_action(Slack connector)或 http 打 incoming webhook——notify 没有 slack channel
actionType: 'my_fn'(简写) function: 'my_fn'——conversion 自动搬
内联 script 逻辑移入注册函数,用 config.function 调用

ADR-0087 D2 conversion flow-node-script-branch-keys-removed(step 17 已接线,retiredFromLoadPath):简写 actionType 搬进 function(那正是它指的东西),除非 function 已经赢了——那种情况下它本来就是执行器够不着的死元数据;其余四键直接丢弃,没有任何读者,无值可留。

执行期 parse:script / subflowparseNodeConfig()subflow 手写的 flowName guard 换成同一条契约(文案随之变化,inventory 与单测已更新)。

一个值得记录的事实:墓碑对存量 JSON 元数据是听不见的——load 路径从不 parse 节点 config(FlowNodeSchema.configz.record(z.unknown())),script 也不发布 descriptor configSchema。两条通道分别对应不同人群:墓碑教作者(tsc 把键类型变 never,直接 parse 抛出处方),而存量流程走另一条——registerFlow 连退役 conversion 一起重放(#3903,sys_metadata 里的行没有作者可教),所以老的 email-stub 节点到达时已被剥掉没人读的键,然后因为没有可调用对象而被拒,而不是像以前那样打一行日志报成功。这个翻转就是本次退役买到的行为变化。

其余:删除 SCRIPT_BUILTIN_ACTION_TYPES / SCRIPT_INVOKE_FUNCTION_ACTION_TYPE / ScriptBuiltinActionType(它们描述的分发集合已不存在);os validate 现在点名退役键并给出替代,而不是报一句笼统的「没有 callable」;例子里 7 个 email stub 改为 notify、1 个 slack stub 改为 http webhook,showcase 新增一个注册函数,让它的 script 节点演示唯一能用的形态

验证

  • packages/spec:281 文件 / 7076 用例全绿(含新建的 schemaless-node-config.test.ts 与 conversions 的 8 组新 pin)。
  • service-automation 51/629lint 44/746cli 67 文件全绿。
  • 13 个 spec gate 全 PASS:check:liveness / empty-state / authorable-surface / docs / api-surface / spec-changes / upgrade-guide / skill-refs / skill-docs / skill-examples / strictness-ledger / variant-docs / exported-any
  • authorable-surface.json diff 恰好是 5 行 [RETIRED] 标记,function 未消失。
  • 例子:showcase 10 文件 / 60 用例绿(含 flowNodeTypes 覆盖测试——script 仍被覆盖,由新的函数调用节点),两个 app 的 validate + typecheck 均通过,且零新增告警(改造中途 notify 暴露出 2 条 {record.project.owner} 跨对象跳转,已在例子里改为记录自身字段;剩下 2 条属我未触碰的既有节点)。
  • changeset 双守卫通过:pre-release(rc)模式下 check-changeset-no-major 明确让行,与近期所有退役 changeset(wait wait 声明了超时契约但完全没有实现:onTimeout 零读取者,timeoutMs 被当成定时时长用 —— showcase 自己在依赖它 #4158、tool、datasource)一致。

配套 PR

objectui 半边(表单收敛 + 跨仓对账测试双态):objectstack-ai/objectui#3170。已用本地构建的 spec 实测过跨仓契约:收敛后的面板双向对账干净。

顺带记录一条既有债(不在本 PR 范围):objectui 的 wait 面板仍编辑 #4158 已退役的 waitEventConfig.timeoutMs / .onTimeout,所以下一次 spec 升版会让它的 ratchet 亮红——这正是那个 ratchet 的本意,由 objectui#3101 追踪。

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ct9NXp2JumjKuARtQnrbPf

…e script/subflow config at execute time (#4343)

A `script` node had four ways to name what it ran and only one of them ran
anything. `actionType: 'email' | 'slack'` were logger-backed stubs that wrote a
line, reported success and delivered nothing under any configuration, with
`template` / `recipients` / `variables` addressing a message no channel sent.
Inline `config.script` was recognized and never executed (no server-side JS
sandbox). Every other `actionType` value was shorthand for a registered-function
name, and `'invoke_function'` was a marker that named nothing on its own.

All five keys are tombstoned (`retiredKey`) and `config.function` becomes
required, which is also what made the contract parseable: while the legal key set
depended on `actionType`, a flat parse would either reject valid shapes or wave
everything through. `script` and `subflow` now run their config through the
execute-time contract parse #4277 gave the flat builtins — a violation refuses
the node as a guard, un-routable by a `fault` edge (#3863). `decision` stays
export-only: its one key is optional, so a parse would check nothing.

The ADR-0087 D2 conversion `flow-node-script-branch-keys-removed` rewrites stored
sources — a shorthand `actionType` moves into `function` (that is what it named)
unless `function` already won; the other keys drop, nothing having read them.
Retired from the load path with the rest of the keys retired for misdescribing
themselves, so `os migrate meta --from 16` is what rewrites an authored source.
`registerFlow` still replays it (#3903 — a stored row has no author to teach), so
an old email-stub node arrives stripped and then refuses for naming no callable,
where it used to report success.

Also: the `SCRIPT_BUILTIN_ACTION_TYPES` / `SCRIPT_INVOKE_FUNCTION_ACTION_TYPE`
constants and `ScriptBuiltinActionType` are removed; `os validate` names a retired
key and its replacement; the examples move to `notify` (real delivery) and `http`
(Slack webhook), and the showcase gains a registered function so its `script` node
demonstrates the one form that works.

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

vercel Bot commented Aug 1, 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 Aug 1, 2026 3:24pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/xl labels Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @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 @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/service-automation, @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/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.

…ck({ functions })` survives a build (#4343)

`objectstack build` lowers every inline callable to a serialisable string ref
BEFORE the stack is parsed — it must, since `z.function()` wraps callables and
would break the ref mapping — so a built manifest holds `{ myFn: 'myFn' }`.
`FlowFunctionEntrySchema` accepted only a function or a `{ handler, effect }`
declaration, so the parse rejected what the build had just produced: a
documented, first-class authoring mechanism could not survive a build.

Nothing had noticed because no bundled example used `functions`. #4343 turns
that from latent into blocking: `config.function` becomes the only thing a
`script` node runs, so registering one is now mandatory for any app with a
script node — which is what the showcase demo in this branch hit.

`Hook.handler` already declared exactly this pair (a string post-build, an
inline function pre-build), so this puts `functions` on the platform's existing
shape rather than a new one. A string carries no callable and
`normalizeFlowFunctionEntry` still drops it by design — the real functions ride
in the sibling ESM module the build emits and are merged by name — so
hand-authoring one registers nothing and fails loudly at execute rather than
silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ct9NXp2JumjKuARtQnrbPf
@os-zhuang
os-zhuang marked this pull request as ready for review August 1, 2026 15:42
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit fd3013a Aug 1, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/script-config-parse-contract-gy035e branch August 1, 2026 15:55
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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

script 的 config 契约要接入 #4277 的执行期 parse,先得有判别式(actionType)形态

2 participants