fix(spec): action-param rejection names the built-in a "differs by one underscore" key meant (#5622) - #6368
Conversation
`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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
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 |
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全仓仅此一处命中)。unknown_field的那一层,allow就是生效的内建键集合(const allow = new Set(opts?.builtinKeys ?? ACTION_PARAM_BUILTIN_KEYS)),所以近似匹配直接读allow,不需要另接ACTION_PARAM_BUILTIN_KEYS——顺带把builtinKeys覆盖的场景也一并接住了。前提有效,继续实现。
改了什么
unknown_field的消息在尾部追加一条指路:当'_' + key或去掉前导下划线的key命中allow时,点名那个内建键。为什么值得改一句消息:原句是真的,但它唯一可操作的读法是假的——读者的下一步是去 action 上把这个键声明成 param,而内建键恰恰是不能被声明的那一个。#5568 的报告者把这条路走到了尽头,由此判定 REST 没有任何合法形状能携带选择集并开了平台单,而
params._selectedIds一直是通的。机制句按键区分(与 issue 的建议措辞有出入,这是实测后的修正)
Issue 建议的那句「injected by an aggregate bulk dispatch」只对
_selectedIds为真。三个内建键有三个不同的生产者,实测:recordIdrecordIdruntime/src/domains/actions.ts:346、action-execution.ts:1056的params: { ...reqParams, recordId, objectName }objectName_selectedIdsparams里,由渲染器聚合批量派发注入bulk-action.zod.ts:205所以机制句按键给,不给一句通用的。这不是措辞洁癖:对
recordId说「送params.recordId即可」是主动错误——dispatcher 那个展开会覆盖 bag 里的同名键,路由和请求体都没带记录 id 时覆盖成undefined。走builtinKeys覆盖进来的键没有对应条目,拿通用句the dispatcher supplies it.——这句按该选项自身的定义对其每个成员都为真。判定不变
selectedIds另开接受通道:契约仍然只有params._selectedIds一种拼写。{ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240「一个条件一种措辞」。反向验证(方向先预测,后实测)
预测:把
builtinNearMissHint改成无条件返回''(即还原今天的行为)→ 只有断言消息的 4 条用例转红,所有codes(...)/ 接受集合断言保持绿——因为判定本来就没动。实测与预测一致:
测试
拒收类用例的最低断言集是结构化字段:每条都同时断言
code(unknown_field)与消息内容,负向那条逐字节钉死消息。只断言「校验失败」在这里是零信息——该键改动前后都失败。pnpm --filter @objectstack/spec test:332 files / 8503 tests passedpnpm --filter @objectstack/spec typecheck:green(tsc --noEmit+check:test-typecheck).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.json、packages/spec/api-surface/、content/docs/references/均无变化(check:authorable-surface、check:api-surface、check:docs全绿)。近似匹配的辅助函数与 origin 表都是模块私有,不进公开 API 面;错误消息文本不是.describe()文本,不触发参考文档重生成。Generated by Claude Code