Skip to content

feat: typed action handlers — enforce declared param contract at dispatch (ADR-0104 D2)#3432

Merged
os-zhuang merged 1 commit into
mainfrom
d2/typed-action-handlers
Jul 24, 2026
Merged

feat: typed action handlers — enforce declared param contract at dispatch (ADR-0104 D2)#3432
os-zhuang merged 1 commit into
mainfrom
d2/typed-action-handlers

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

实施 ADR-0104 的 D4 阶段 2(D2):动作参数的声明契约在派发时强制执行,handler 从「无类型口袋」变成有校验、可类型化的输入。依赖已合并的 D1(#3429)值形状契约。

问题

动作的 params[] 声明(type / required / multiple / options / reference)本是完整的值契约,但此前只喂给客户端弹窗:服务端把 reqBody.params 原样透传给 handler,零校验(REST handleActions 与 MCP invokeBusinessAction 两条路径),沙箱把它当 input: unknown,handler 注册签名 (ctx: any) => any,全靠裸 as 强转。AI/MCP 恰恰是最容易发出「看似合理实则错形」参数包的调用方,而唯一的校验在客户端——三个调用面里唯一的礼貌性表面。

改动

@objectstack/spec/ui(新增 action-params.zod.ts)

  • validateActionParams(resolved, bag):纯校验函数,复用 D1 的 valueSchemaFor,所以选项成员 / multiple 数组 / 引用 id 形态全部走同一个值契约。返回问题列表(不抛),由调用方决定 warn-vs-reject。配套 ResolvedActionParam / ActionParamIssue / ACTION_PARAM_BUILTIN_KEYS
  • 类型化 authoring 面:ActionHandler / ActionHandlerContext / ActionEngineFacade——handler 作者用 ActionHandler 标注取代内联 (ctx: any),这正是「让 handler 不再是无类型口袋」的落点。注册 seam 保持无类型,故为非破坏的 opt-in

runtime(http-dispatcher.ts)

  • REST 与 MCP 两条派发路径都解析动作声明的 params(字段引用式参数通过被引用对象字段解析出 type/multiple/options/required),在 handler 运行之前校验请求包:required 存在性、逐类型值形态、未知键(派发器自注入的 recordId / objectName 在白名单)。
  • resolveActionByName 透出 obj,让 MCP 路径也能做字段引用式解析。
  • 无声明 params 的动作保持原样透传(无契约可校验)。

warn-first 落地(ADR-0104 R3)

违规默认仅告警并放行——过去静默错配的参数包继续能用,漂移变得可见而非致命;设 OS_ACTION_PARAMS_STRICT_ENABLED=1 则 REST 返回 400、MCP 抛错。翻转为默认严格随后续 minor(与 D1 同姿态)。

测试

  • spec 6847 ✓(含 9 个 validateActionParams 单测:required / 选项 / 引用 id / multiple / 未知键 / 白名单 / 未知类型开放)
  • runtime 587 ✓ · objectql 1043
  • 新增 dogfood 端到端 3 ✓:用 showcase 既有的 showcase_action_param_gallery(feat(spec): let an inline lookup action param declare its reference target (#3405) #3406 的参数 gallery)真机 POST——warn-first 放行、strict 下畸形包被 400(错误信息含 p_text / p_priority / bogus 三处违规)、合规包通过。
  • API 表面快照已更新(+7 个纯新增导出);生成文档 check 通过(无新 zod schema → 无参考文档变更)。

不在本 PR 范围

  • 文件/图片参数变为 sys_file 引用——依赖 file-as-reference(D3)。
  • 从字面 params 数组推导 ctx.params 的逐名静态类型——延后的 DX 优化,运行时保证与其无关。

🤖 Generated with Claude Code

https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd


Generated by Claude Code

…hase 2 (D2)

An action's declared params[] (type/required/multiple/options/reference) was a
complete value contract that only informed the client dialog — the server
passed reqBody.params straight to the handler unvalidated (REST handleActions +
MCP invokeBusinessAction), and handlers read an untyped bag.

- @objectstack/spec/ui: validateActionParams (+ ResolvedActionParam,
  ActionParamIssue, ACTION_PARAM_BUILTIN_KEYS) — pure check reusing the D1
  valueSchemaFor so option membership / multiple arrays / reference-id shape
  ride the one value contract; plus typed authoring surface ActionHandler /
  ActionHandlerContext / ActionEngineFacade.
- runtime: both REST and MCP action paths resolve declared params (field-backed
  resolved through the referenced object field) and validate the request bag
  before the handler runs — required/shape/unknown-key; recordId/objectName
  allowlisted. Warn-first unless OS_ACTION_PARAMS_STRICT_ENABLED=1 (then 400).
  Actions with no declared params are untouched.
- ScriptContext.input doc + registerAction JSDoc state the validated contract.

Tests: spec 6847 (incl. 9 action-params units), runtime 587, objectql 1043,
new action-params dogfood 3 (warn-first passes / strict 400 / conformant passes)
— all green. API-surface snapshot updated (+7 additive exports).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 24, 2026 12:39pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ui tooling size/m labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/objectql, packages/qa, @objectstack/runtime, @objectstack/spec.

114 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/qa, packages/runtime, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants