Repository navigation
Closed
Labels
toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surfaceFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
Description
#4278 与 #4277 在同一天落地,两者交界处留下一个明确的后续,记在这里以免被当成「忘了接」。
现状
#4277(#4332)让 12 个「有契约」的内置节点在执行前 parse() 自己的
node.config(service-automation/builtin/parse-config.ts),并让 registerFlow()
拒绝 descriptor configSchema 未声明的键。
#4278(#4325)给另外三个 descriptor-schemaless 节点补上了执行器派生的契约
(spec/automation/schemaless-node-config.zod.ts:ScriptConfigSchema /
SubflowConfigSchema / DecisionConfigSchema),但刻意没有接入 parse,也没有发布
descriptor configSchema——两件事是同一个理由。
为什么不能直接接上去
它们的键集不等于契约。
script—— 合法键随actionType变:内置副作用分支读
template/recipients/variables;函数分支读
function/inputs/outputVariable;内联script是被识别但不执行的第三种。
平铺 parse 只能取这些键的并集,于是
{ actionType: 'email', function: 'x' }这种自相矛盾的配置照样通过——检了个寂寞;
若反过来收紧成任一分支,就会拒绝另一半合法元数据。decision—— 完全可以不带conditions,只靠出边的edge.condition分支
(这是遗留但合法的形态)。subflow—— 这个其实最接近可接入:键集扁平,flowName执行期必需,
执行器已经用refuseNode()拒绝缺失的情况。
建议做法
subflow可以先接——低风险,把现有的refuseNode换成统一的 parse guard,
与其它 12 个节点的失败形态一致(guard refusal,不走 fault 边)。script需要先改造成判别式:按actionType拆成
z.discriminatedUnion(或等价的superRefine),分支分别声明各自的键。
这同时会让「自相矛盾的配置」在作者时就被拒,而不是运行到一半才发现
——actionType: 'email'却写了function是当前唯一还能悄悄通过的形态。decision大概率维持现状:它的真实契约一半在边上,不在 config 里;
接 parse 的收益仅限于「conditions[]元素的类型」,可以等 2 做完再评估。
一个附带好处:script 一旦有了判别式形态,objectui 的
flow-node-config.spec-reconciliation.test.ts(objectui#3099)可以从「键集并集比对」
升级为「按 actionType 分支比对表单的 showWhen 分组」——那才是表单实际的形状,
也能挡住「某个分支少提供了一个字段」这类 #4278 没覆盖到的漂移。
Related
- The schemaless nodes' designer forms live only in objectui's hand-written table, and nothing reconciles them —
scriptoffers three broken options and cannot author the one that works #4278 —— schemaless 节点的表单无人对账(已关闭) - feat(spec,automation): publish executor-derived config contracts for the schemaless flow nodes (#4278) #4325 —— 发布这三个契约的 PR
- 3b — wire the flow executors to
parse()their config, and tighten the undeclared-key warning into an error #4277 / feat(automation,spec): 流程执行器 parse() 自己的 config,未声明键在注册时报错 (#4277) #4332 —— 执行器 parse config + registerFlow 拒绝未声明键 - objectui#3099 —— 跨仓对账测试
- [P1] Flow action/script nodes pointing to unregistered callables silently no-op; no template fn-registration path #1870 ——
config.function作为 canonical 调用引用的来源
Metadata
Metadata
Assignees
Labels
toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surfaceFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
Activity
os-zhuang commentedon Aug 1, 2026
方向变更(操作者裁定):不再给
script的多形态做判别式契约,改为激进收敛——script收敛为纯函数调用节点(function必需 +inputs+outputVariable),actionType/template/recipients/variables/script五键按 ADR-0049 removal 路线打retiredKey()墓碑(处方:email →notify;slack →connector_action/http;内联脚本与函数名简写 → 注册函数),并注册 ADR-0087 D2 conversion。理由:四个分支里只有函数路径跑真实逻辑(email/slack 是 logger stub、内联 script 是 no-op、其余 actionType 是函数名别名),多形态是历史积累。收敛后契约天然平铺,
script/subflow直接接入 #4277 的执行期 parse——本 issue 的目标以更简单的形态达成;墓碑处方也正是经这条 parse 通道对存量 JSON 元数据发声(load 路径不 parse config)。decision维持现状。本 session 接手实施,分支
claude/script-config-parse-contract-gy035e(objectstack + objectui 配套),PR 链接随后附上。Generated by Claude Code
os-zhuang commentedon Aug 1, 2026
PR 已开(均为 draft):
scriptto a function call and parse script/subflow config at execute time (#4343) #4516 —— 5 个键retiredKey()墓碑 +flow-node-script-branch-keys-removedD2 conversion(step 17 接线,retiredFromLoadPath)+script/subflow接入parseNodeConfig()+ 例子/skills/docs 清理SCRIPT_BUILTIN_ACTION_TYPES是否存在作为判别依据两点执行中发现、与原计划不同的事实,记在这里:
FlowNodeSchema.config是z.record(z.unknown())),而registerFlow会连退役 conversion 一起重放([P2] The spec contract stops at authored source — stored metadata is rehydrated unparsed, unconverted, and ungated #3903——sys_metadata里的行没有作者可教)。所以存量流程走的是「注册时被 conversion 剥掉没人读的键 → 执行期因为没有 callable 被拒」,墓碑本身只对写 TypeScript 的作者和直接 parse 的调用者发声。真正的行为翻转是老的 email-stub 节点从「打一行日志报成功」变成「大声拒绝」。flow-node-script-config-aliases([P3] Six flow-node config aliases bypassreadAliasedConfig— no warning, no ledger, no retirement path #3796)的 fixture 两侧都带actionType: 'invoke_function',那个终态在 17 已不可达,fixture 已改写(conversion 本体未动)——与wait声明了超时契约但完全没有实现:onTimeout零读取者,timeoutMs被当成定时时长用 —— showcase 自己在依赖它 #4158 退役时踩到的是同一类交互。decision按 issue 建议维持现状。Generated by Claude Code
fix(spec): accept a lowered handler ref in `functions`, so `defineSta…
feat(spec,automation)!: converge `script` to a function call and pars…
docs(sweep): run 4 — rc.1 catch-up, close the run-2 coverage gap (obj…
fix(example-showcase): functions 用裸函数形态注册 sweepProjectHealth (#4774)
fix(example-showcase): 让 4 条「声明了但运行时从不兑现」的接线真正生效 (objectstack-ai#4774) (
refactor(spec)!: strip internal tracker ids from the three customer-f…
docs(spec): re-anchor the dead tracker citations in stack.zod.ts and …
fix(spec): the protocol 16 → 17 conversion summaries and three descri…
test(spec): automation test titles state each cited decision in words…
test(spec): contracts and conversions test titles state each cited de…