Repository navigation
Description
现象
ActionSchema.body 的 spec 声称 body 只在 type === 'script' 时生效,运行时不看 type。
packages/spec/src/ui/action.zod.ts:
body: HookBodySchema.optional().describe(
'Action body — expression (L1) or sandboxed JS (L2). Only used when type is `script`.'
),JSDoc 里更明确:"Only meaningful when type === 'script'. When set, the runtime invokes the body inside the sandbox … and ignores target."
运行时两处都不过滤 type:
packages/runtime/src/app-plugin.ts的collectBundleActions只要a.name是字符串就收,type完全不参与;packages/runtime/src/sandbox/body-runner.ts的actionBodyRunnerFactory只判action.body在不在、HookBodySchema.safeParse过不过,从不读type。
所以一个 type: 'url' 且带 body 的 action 照样 ql.registerAction(...),handler 可以从 POST /api/v1/actions/:object/:action 调起来并在沙箱里执行。
为什么值得掰扯
这是 declared ≠ enforced 的一个小切口(Prime Directive #10):spec 说"只在 script 下生效",runtime 说"有 body 就跑"。两种读法各自会引出不同的正确行为:
- 如果 spec 是对的 —— 作者把
type从script改成url之后,会以为那段 body 已经不再执行了,而它还在; - 如果 runtime 是对的 —— spec 在教作者一件不成立的事,而且
type就不该出现在这句话里。
目前无人被这个坑到,是因为把 body 写在非 script action 上本来就少见。但"改了 type 以为逻辑停了、其实还在跑"这种形状,踩到时非常难查。
已经受影响的地方
#4344(action body 写集 lint)刻意跟了 runtime:检查所有带 body 的 action,不看 type,理由写在
packages/lint/src/validate-action-body-writes.ts 的 collectActionBodies 注释里 ——
typeis deliberately not consulted: the runtime binds a handler fromaction.bodyalone
(actionBodyRunnerFactorynever readstype), so a body on a non-scriptaction still runs
and still fails silently. Checking what executes beats checking what the schema says should.
这个选择在"两边不一致"的前提下是对的,但它同时把不一致固化进了第三个地方。定了之后 lint 那边要跟着调。
两条出路(需要决策)
- runtime 认下 spec:
actionBodyRunnerFactory(或collectBundleActions)在type !== 'script'时不绑 handler。附带要想清楚type缺省是'script'(ActionType.default('script')),所以只影响显式写了别的 type 的 action;还要决定"带 body 但 type 不是 script"要不要在作者时报错 —— 按 ADR-0087 的姿态,这种自相矛盾的元数据应该在 publish gate 上被拒,而不是静默忽略。 - spec 认下 runtime:去掉
.describe()和 JSDoc 里的 type 条件,改成"body 一旦存在就是这个 action 的实现,target被忽略",并说明type只影响渲染侧的呈现。
倾向 1 + 作者时拒绝:type: 'url' 配一段沙箱 body 是自相矛盾的元数据,静默各跑各的比报错糟。但这会改变现有行为,得确认没有 app 在依赖"非 script 也能跑 body"。
参考
packages/spec/src/ui/action.zod.ts(ActionSchema.body的 describe + JSDoc)packages/runtime/src/sandbox/body-runner.ts(actionBodyRunnerFactory)packages/runtime/src/app-plugin.ts(collectBundleActions)packages/lint/src/validate-action-body-writes.ts(collectActionBodies—— 当前跟的是 runtime)- 相关:feat(lint,spec): L2 action body 写不存在字段从盲区变为作者时 lint 告警 (#4271) #4344
Activity
os-zhuang commentedon Aug 2, 2026
裁决(维护者 2026-08-02 委托,按四轴评估:长远合理性 / 防 AI 静默犯错 / 实际业务 / 不扩边界):出路 1 —— runtime 认下 spec,附作者时拒绝。
actionBodyRunnerFactory/collectBundleActions在type !== 'script'时不绑 handler;body但type ≠ 'script'」的自相矛盾元数据直接报错(ADR-0087 姿态),不静默忽略;type过滤(其注释已预告「定了之后要跟着调」)。type的语义被永久掏空。type缺省'script',只影响显式写了其它 type 且带 body 的 action。按 feat(spec): reject unknown keys on an action param instead of stripping them (#3405) #3746 先例实测三个示例 app + 全部 docs,预期零命中;若有命中在 PR 里逐个列出再定。v17 major 窗口内落地(行为变更)。
Generated by Claude Code
os-zhuang commentedon Aug 2, 2026
🔒 CLAIM — pm-dispatch round 7
branch:
claude/issue-4352-action-body-type-gateworktree:
objectstack-issue-4352按本 issue 已记录的裁决实施(出路 1:runtime 认下 spec + publish gate 对矛盾元数据报错 + #4344 的 lint 改回按
type过滤)。三个示例 app 与全部 docs 的实测命中情况须在 PR 里如实列出;若出现真实依赖"非 script 也跑 body"的命中,停手按 needs_decision 回报。Generated by Claude Code
docs(changeset): action.body binds only for type 'script' (#4352)
os-zhuang commentedon Aug 2, 2026
PM 复核:ACCEPT ✅(PR #4654,已在合并队列)
按裁决三条全部落地,且有两处判断比指令更严谨:
collectBundleActions刻意保持 type-blind(治理面要看到所有声明的 action),而另一条绑定路径engine.setDefaultActionRunner(Studio 写的 action 元数据)根本不走 collector——在 collector 上再抄一份规则会"一半重复、另一半漏网";userfield carries its target in the TYPE — bare{type:'user'}is not targetless #4438 已落地,本 PR 不重复实现,改为补接线的 pin:发布门经getMetadataTypeSchema('action')按元数据类型解析,注册一旦被重指向,洞就悄悄重开而所有 schema 测试仍全绿。钉住接线而不是钉住 schema,正是"检查真正执行的东西";undefined并 warn,而非静默拒绝——"静默拒绝只是把看不见换个地方"。影响面实测(我的放行前提)已满足且做法正确:用
tsximport 真实 stack 对象做结构化深度遍历,而不是 grep——并说明了 grep 在这里为什么会骗人(content/docs里 37 处body:大多是 fetch 请求体、邮件/通知正文、flow step 区域,还有一个叫body的字段)。三个示例 app + 全部 docs 零命中;app-showcase的 9 处 body 里 5 处是type:'script'的 action、4 处是根本不走本门的 hook。另外protocol/objectui/actions.mdx早已把这条写成 "parse-time error",文档描述的一直是本 PR 实现的世界,无需跟改。Generated by Claude Code
fix(runtime,spec,lint): bind action.body only for type 'script' (obje…
test(spec): the first ui/ file group's test titles state each cited d…