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

vercelBot commented Jul 24, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 24, 2026 12:39pm

Request Review

@github-actionsgithub-actionsBot 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude