#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()` 拒绝缺失的情况。 ## 建议做法 1. **`subflow` 可以先接**——低风险,把现有的 `refuseNode` 换成统一的 parse guard, 与其它 12 个节点的失败形态一致(guard refusal,不走 fault 边)。 2. **`script` 需要先改造成判别式**:按 `actionType` 拆成 `z.discriminatedUnion`(或等价的 `superRefine`),分支分别声明各自的键。 这同时会让「自相矛盾的配置」在作者时就被拒,而不是运行到一半才发现 ——`actionType: 'email'` 却写了 `function` 是当前唯一还能悄悄通过的形态。 3. **`decision` 大概率维持现状**:它的真实契约一半在边上,不在 config 里; 接 parse 的收益仅限于「`conditions[]` 元素的类型」,可以等 2 做完再评估。 一个附带好处:`script` 一旦有了判别式形态,objectui 的 `flow-node-config.spec-reconciliation.test.ts`(objectui#3099)可以从「键集并集比对」 升级为「按 `actionType` 分支比对表单的 `showWhen` 分组」——那才是表单实际的形状, 也能挡住「某个分支少提供了一个字段」这类 #4278 没覆盖到的漂移。 ## Related - #4278 —— schemaless 节点的表单无人对账(已关闭) - #4325 —— 发布这三个契约的 PR - #4277 / #4332 —— 执行器 parse config + registerFlow 拒绝未声明键 - objectui#3099 —— 跨仓对账测试 - #1870 —— `config.function` 作为 canonical 调用引用的来源
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…