Skip to content

action.body 的 type 门是 declared ≠ enforced —— spec 说只在 type:'script' 生效,runtime 有 body 就绑 #4352

Description

@os-zhuang

现象

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.tscollectBundleActions 只要 a.name 是字符串就收,type 完全不参与;
  • packages/runtime/src/sandbox/body-runner.tsactionBodyRunnerFactory 只判 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 是对的 —— 作者把 typescript 改成 url 之后,会以为那段 body 已经不再执行了,而它还在;
  • 如果 runtime 是对的 —— spec 在教作者一件不成立的事,而且 type 就不该出现在这句话里。

目前无人被这个坑到,是因为把 body 写在非 script action 上本来就少见。但"改了 type 以为逻辑停了、其实还在跑"这种形状,踩到时非常难查。

已经受影响的地方

#4344(action body 写集 lint)刻意跟了 runtime:检查所有带 body 的 action,不看 type,理由写在
packages/lint/src/validate-action-body-writes.tscollectActionBodies 注释里 ——

type is deliberately not consulted: the runtime binds a handler from action.body alone
(actionBodyRunnerFactory never reads type), so a body on a non-script action still runs
and still fails silently. Checking what executes beats checking what the schema says should.

这个选择在"两边不一致"的前提下是对的,但它同时把不一致固化进了第三个地方。定了之后 lint 那边要跟着调。

两条出路(需要决策)

  1. runtime 认下 spec:actionBodyRunnerFactory(或 collectBundleActions)在 type !== 'script' 时不绑 handler。附带要想清楚 type 缺省是 'script'(ActionType.default('script')),所以只影响显式写了别的 type 的 action;还要决定"带 body 但 type 不是 script"要不要在作者时报错 —— 按 ADR-0087 的姿态,这种自相矛盾的元数据应该在 publish gate 上被拒,而不是静默忽略。
  2. spec 认下 runtime:去掉 .describe() 和 JSDoc 里的 type 条件,改成"body 一旦存在就是这个 action 的实现,target 被忽略",并说明 type 只影响渲染侧的呈现。

倾向 1 + 作者时拒绝:type: 'url' 配一段沙箱 body 是自相矛盾的元数据,静默各跑各的比报错糟。但这会改变现有行为,得确认没有 app 在依赖"非 script 也能跑 body"。

参考

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions