Skip to content

fix(spec): action-param rejection names the built-in a "differs by one underscore" key meant (#5622) - #6368

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-5622-param-near-miss-hint
Aug 7, 2026
Merged

fix(spec): action-param rejection names the built-in a "differs by one underscore" key meant (#5622)#6368
hotlong merged 1 commit into
mainfrom
claude/issue-5622-param-near-miss-hint

Conversation

@hotlong

@hotlong hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #5622

前提核验(先于实现)

Issue 是线索不是规格,三条前提都在 origin/main 上核过:

  • ACTION_PARAM_BUILTIN_KEYS 仍是 ['recordId', 'objectName', '_selectedIds'](packages/spec/src/ui/action-params.zod.ts:82)。
  • 今天的消息确实不带指路,且全仓只有一处构造它:Unknown action param "${key}" — not declared on this action(同文件 :137,git grep 全仓仅此一处命中)。
  • PM 的机制假设成立:构造 unknown_field 的那一层,allow 就是生效的内建键集合(const allow = new Set(opts?.builtinKeys ?? ACTION_PARAM_BUILTIN_KEYS)),所以近似匹配直接读 allow,不需要另接 ACTION_PARAM_BUILTIN_KEYS——顺带把 builtinKeys 覆盖的场景也一并接住了。

前提有效,继续实现。

改了什么

unknown_field 的消息在尾部追加一条指路:当 '_' + key 或去掉前导下划线的 key 命中 allow 时,点名那个内建键。

Unknown action param "selectedIds" — not declared on this action. Did you mean
the built-in "_selectedIds"? Built-in params are never declared on an action —
an aggregate bulk dispatch (`execution: 'aggregate'`) injects every selected
record id under it, and a handler reads `ctx.params._selectedIds`.

为什么值得改一句消息:原句是真的,但它唯一可操作的读法是假的——读者的下一步是去 action 上把这个键声明成 param,而内建键恰恰是不能被声明的那一个。#5568 的报告者把这条路走到了尽头,由此判定 REST 没有任何合法形状能携带选择集并开了平台单,而 params._selectedIds 一直是通的。

机制句按键区分(与 issue 的建议措辞有出入,这是实测后的修正)

Issue 建议的那句「injected by an aggregate bulk dispatch」只对 _selectedIds 为真。三个内建键有三个不同的生产者,实测:

内建键 谁放进 params 的 证据
recordId dispatcher 服务端合入,值来自记录作用域路由或请求体顶层 recordId runtime/src/domains/actions.ts:346action-execution.ts:1056params: { ...reqParams, recordId, objectName }
objectName 同上,值是 action 派发所在对象名 同上
_selectedIds 客户端送来的,在请求自己的 params 里,由渲染器聚合批量派发注入 objectui#3139;bulk-action.zod.ts:205

所以机制句按键给,不给一句通用的。这不是措辞洁癖:对 recordId 说「送 params.recordId 即可」是主动错误——dispatcher 那个展开会覆盖 bag 里的同名键,路由和请求体都没带记录 id 时覆盖成 undefined。走 builtinKeys 覆盖进来的键没有对应条目,拿通用句 the dispatcher supplies it.——这句按该选项自身的定义对其每个成员都为真。

判定不变

反向验证(方向先预测,后实测)

预测:把 builtinNearMissHint 改成无条件返回 ''(即还原今天的行为)→ 只有断言消息的 4 条用例转红,所有 codes(...) / 接受集合断言保持绿——因为判定本来就没动。

实测与预测一致:

× names `_selectedIds` when the caller sent `selectedIds` (the #5568 road)
× names `recordId` when the caller sent `_recordId` (the reverse direction)
× gives each of the THREE built-ins its own true origin sentence
× hints a custom `builtinKeys` entry with the GENERIC origin, never another key's mechanism
 Tests  4 failed | 21 passed (25)

⚠️ 诚实标注:「普通未知键保持今天的消息」这条是 companion,不是本改动的 pin(#5722 的区分)。它断言的正是未改动前就存在的消息,所以在还原两侧都绿。留它的理由是另一件事:将来若有人把匹配放宽成模糊匹配,红的就是它——本文件其他用例看不到那个回归。

测试

拒收类用例的最低断言集是结构化字段:每条都同时断言 code(unknown_field)与消息内容,负向那条逐字节钉死消息。只断言「校验失败」在这里是零信息——该键改动前后都失败。

  • pnpm --filter @objectstack/spec test:332 files / 8503 tests passed
  • pnpm --filter @objectstack/spec typecheck:green(tsc --noEmit + check:test-typecheck)
  • runtime 消费侧 10 个 action 路径测试文件:118 passed
  • .github/workflows/lint.yml 全量门(ESLint job 28 项 + TypeScript Type Check job 21 项)逐条跑过,全绿。其中 check:i18n / check:i18n-coverage 首轮报的是它们自己声明的 PREREQUISITE(全新 worktree 未构建 CLI / examples 依赖),按门自身给的处方构建后转绿——不是本改动引起。

无生成物漂移

packages/spec/authorable-surface.base.jsonpackages/spec/api-surface/content/docs/references/ 均无变化(check:authorable-surfacecheck:api-surfacecheck:docs 全绿)。近似匹配的辅助函数与 origin 表都是模块私有,不进公开 API 面;错误消息文本不是 .describe() 文本,不触发参考文档重生成。


Generated by Claude Code

`validateActionParams`(ADR-0104 D2)对每个未声明键都给同一句
`Unknown action param "selectedIds" — not declared on this action`。这句话本身是真的,
但它唯一可操作的读法是假的:读者的下一步是去 action 上把这个键声明成 param,而内建键
恰恰是**不能**被声明的那一个。#5568 的报告者把这条路走到了尽头,由此判定 REST 没有任何
合法形状能携带选择集并开了平台单 —— 而 `params._selectedIds` 一直是通的。

`unknown_field` 的消息现在在尾部追加一条指路:当 `'_' + key` 或去掉前导下划线的 `key`
命中允许的内建键集合时,消息点名那个内建键,并给出一句「它从哪来」。三个内建键有三个不同
的生产者,所以这句话按键区分:`recordId` / `objectName` 由 dispatcher 在服务端合入
(`params: { ...reqParams, recordId, objectName }`),`_selectedIds` 由渲染器的聚合批量
派发从客户端带入。走 `builtinKeys` 覆盖进来的键拿通用句。

**纯消息层改动 —— 判定不变。** 该键改动前后一样被拒,接受集合一个字节都没动,不构成
near-miss 的未知键消息与今天逐字节一致(匹配的是一个前导下划线,不是相似度)。这不是给
`selectedIds` 另开一条接受通道:契约仍然只有 `params._selectedIds` 一种拼写。

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

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 3:16pm

Request Review

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

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

112 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/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via 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 packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @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 @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/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • 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/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 @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/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 @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • 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/spec)
  • content/docs/plugins/packages.mdx (via @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/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.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/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/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/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.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/field-grouping-and-order.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.

@hotlong
hotlong marked this pull request as ready for review August 7, 2026 15:29
@hotlong
hotlong enabled auto-merge August 7, 2026 15:29
@hotlong
hotlong added this pull request to the merge queue Aug 7, 2026
@hotlong hotlong changed the title fix(spec): 参数门拒绝「差一个下划线」的内建键时指出那个内建键 (#5622) fix(spec): action-param rejection names the built-in a "differs by one underscore" key meant (#5622) Aug 7, 2026

hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

PM note (spec-surface seat #6298): retitled from Chinese to English to match the maintainer's 2026-08-06 language policy — the PR title becomes the merge-commit subject, so it lands in permanent git history. Title now mirrors the changeset's own summary line.

The body is still Chinese and is deliberately left as-is. It is a careful technical record (per-key producer table with file:line evidence, the reverse-verification measurement, the honest companion-vs-pin labelling), and re-translating it risks corrupting that content for a presentation fix. Flagged to the maintainer in this round's report rather than rewritten unilaterally — say the word and it gets translated.

Review verdict is unchanged: ACCEPT, already flipped ready with auto-merge armed. No code, test, or changeset content was touched by this edit.


Generated by Claude Code

Merged via the queue into main with commit c2429b0 Aug 7, 2026
26 checks passed
@hotlong
hotlong deleted the claude/issue-5622-param-near-miss-hint branch August 7, 2026 15:47
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.

参数门拒绝「差一个下划线」的内建键时无提示:selectedIds 报 Unknown param,不指向 _selectedIds(#5568 验证副产品)

2 participants