feat(spec,automation)!: converge script to a function call and parse script/subflow config at execute time (#4343) - #4516
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…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
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 #4343.
方向变更(已在 issue 上说明)
原 issue 要求给
script的 config 契约做判别式(actionType)形态,再接 #4277 的执行期 parse。评估后改为激进收敛:与其给一套历史形态建模,不如退役它——四个分支里只有函数路径跑真实逻辑。actionType: 'email' | 'slack'template/recipients/variablesconfig.scriptactionTypeinvoke_function只是个 marker,自身不指向任何东西)留下的就是能用的:
config.function(改为必需)+inputs+outputVariable。这同时解决了 #4343 的原始诉求:契约无法 parse 的原因正是「合法键集随
actionType变」——平铺 parse 要么误拒合法形态、要么放行一切。收敛之后契约天然扁平,script与subflow一并接入 #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 channelactionType: 'my_fn'(简写)function: 'my_fn'——conversion 自动搬scriptconfig.function调用ADR-0087 D2 conversion
flow-node-script-branch-keys-removed(step 17 已接线,retiredFromLoadPath):简写actionType搬进function(那正是它指的东西),除非function已经赢了——那种情况下它本来就是执行器够不着的死元数据;其余四键直接丢弃,没有任何读者,无值可留。执行期 parse:
script/subflow走parseNodeConfig()。subflow手写的flowNameguard 换成同一条契约(文案随之变化,inventory 与单测已更新)。一个值得记录的事实:墓碑对存量 JSON 元数据是听不见的——load 路径从不 parse 节点 config(
FlowNodeSchema.config是z.record(z.unknown())),script也不发布 descriptorconfigSchema。两条通道分别对应不同人群:墓碑教作者(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 改为httpwebhook,showcase 新增一个注册函数,让它的script节点演示唯一能用的形态。验证
packages/spec:281 文件 / 7076 用例全绿(含新建的schemaless-node-config.test.ts与 conversions 的 8 组新 pin)。service-automation51/629、lint44/746、cli67 文件全绿。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.jsondiff 恰好是 5 行[RETIRED]标记,function未消失。flowNodeTypes覆盖测试——script仍被覆盖,由新的函数调用节点),两个 app 的validate+typecheck均通过,且零新增告警(改造中途notify暴露出 2 条{record.project.owner}跨对象跳转,已在例子里改为记录自身字段;剩下 2 条属我未触碰的既有节点)。check-changeset-no-major明确让行,与近期所有退役 changeset(waitwait声明了超时契约但完全没有实现:onTimeout零读取者,timeoutMs被当成定时时长用 —— showcase 自己在依赖它 #4158、tool、datasource)一致。配套 PR
objectui 半边(表单收敛 + 跨仓对账测试双态):objectstack-ai/objectui#3170。已用本地构建的 spec 实测过跨仓契约:收敛后的面板双向对账干净。
Related
parse()their config, and tighten the undeclared-key warning into an error #4277 / feat(automation,spec): 流程执行器 parse() 自己的 config,未声明键在注册时报错 (#4277) #4332 —— 执行期 parse config 的接缝scriptoffers three broken options and cannot author the one that works #4278 / feat(spec,automation): publish executor-derived config contracts for the schemaless flow nodes (#4278) #4325 —— 发布这三个 schemaless 契约wait声明了超时契约但完全没有实现:onTimeout零读取者,timeoutMs被当成定时时长用 —— showcase 自己在依赖它 #4158 ——wait超时键退役(本次退役的模板)config.function作为 canonical 调用引用🤖 Generated with Claude Code
https://claude.ai/code/session_01Ct9NXp2JumjKuARtQnrbPf