feat(spec): reject unknown keys on an action param instead of stripping them (#3405) - #3746
Merged
Merged
Conversation
…ng them (#3405) Closes part 3 of #3405 — the item deferred out of #3406 as "evaluate separately". Parts 1 and 2 gave an inline record-picker param a `reference` key and made a targetless one a parse error. That fixed the symptom. The mechanism that caused it stayed: `ActionParamSchema` was zod-default `.strip`, so any key it does not declare was discarded silently and the param went on parsing. An author wrote a correct, clearly intended `reference: 'sys_user'`, the key was eaten, and the dialog rendered a text box asking a human to paste a UUID — no error anywhere. The next mis-spelled key would have failed the same way, just as quietly. An action param is now `.strict()`, with an error map that makes the rejection fixable rather than merely loud: - Case/underscore slips (`help_text` → `helpText`, `default_value` → `defaultValue`) resolve through the shared `findClosestMatches`, bounded by the same length-relative distance `suggestKey` uses in `data/object.zod.ts` — a flat distance of 3 suggests `visible` for `wibble`. - Semantic near-misses edit distance cannot reach are named explicitly, in the `FIELD_TYPE_ALIASES` style: `reference_to` / `referenceTo` / `targetObject` → `reference` (the runtime field shape spells it the first way, objectui's resolved param the second), and `visibleWhen` / `visibleOn` / `visibility` → `visible`. That last one is why this matters beyond typos: ADR-0089 made `visibleWhen` canonical on view/page schemas, so borrowing it here used to strip a param's capability gate and render it unconditionally. Follows ADR-0078 (no-silently-inert-metadata) and ADR-0049 (enforce-or-remove), and matches the precedent set by ADR-0089 D3a for the view/page schemas. Verification: spec 258 files / 6716 tests pass; `tsc --noEmit` clean; app-showcase, app-crm and app-todo all `validate` clean — no existing metadata in the repo carried an undeclared param key. Planting `visibleWhen` on showcase's inline picker param reproduces the new error with the `visibleWhen` → `visible` prescription, and removing it validates again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
`scripts/build-docs.ts` `getFileDescription()` takes the FIRST `/** */` block in a module, verbatim, as the description of its generated reference page. Placing the new `ACTION_PARAM_KEYS` / error-map helpers above the "Action Parameter Schema" JSDoc therefore replaced the public authoring guide on `content/docs/references/ui/action.mdx` with an internal note about why a key list is kept beside the schema — which is what `check:docs` caught. Moved the helpers back below that JSDoc (after the `lazySchema` import, where they were originally). The generated doc is byte-identical to main again: `check:docs` reports 250 generated files in sync. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
…3405) Two more generated-artifact gates behind `check:docs`, both tripped by the same commit: - `check:skill-refs` — importing `shared/suggestions.zod.ts` from `ui/action.zod.ts` pulls it into the transitive reference set of the objectstack-data / -ui / -platform skills. Regenerated via `gen:skill-refs`; the diff is the one expected line per skill, and `action.zod.ts` still resolves to "Action Parameter Schema", confirming the JSDoc-order fix held. - `check:api-surface` — `actionParamUnknownKeyError` was exported, which added it to the package's public API. It has no caller outside its own module, so the export was unnecessary: unlike `strictVisibilityError`, which is shared across the view/page schemas, this map is wired into exactly one schema. Made it module-private; the public API surface is now unchanged by this PR. All ten `check:*` gates in packages/spec pass locally, alongside 258 files / 6716 tests and a clean `tsc --noEmit`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
os-zhuang
marked this pull request as ready for review
July 28, 2026 02:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes part 3 of #3405 — the item #3406 deferred as「单独评估、必要时拆出去做」。
问题
#3406 / objectui#2786 修的是症状:给内联 record-picker 参数加了
reference键,并让缺目标的参数在解析期报错。导致它的机制原样留着 ——ActionParamSchema是 zod 默认.strip,任何它没声明的键都被静默丢弃,参数照常 parse 通过。作者写了一个语义完全正确、且与
FieldSchema.reference同名的键,得到的唯一反馈是一个要求粘 UUID 的文本框。下一个写错键名的作者会以同样的方式失败,同样毫无声音。这一条不修,#3405 修的就只是一个键,不是那个坑。
改动
ActionParamSchema改为.strict(),并配一个让拒绝可修而不只是大声的 error map:help_text→helpText、default_value→defaultValue)交给共享的findClosestMatches,距离上界沿用data/object.zod.ts里suggestKey的长度相对公式 —— 固定距离 3 会给wibble推荐visible。FIELD_TYPE_ALIASES的风格显式列出:reference_to/referenceTo/targetObject→reference(运行时字段形状是前者,objectui 解析后的参数是后者 —— 正是 动作参数弹窗:内联 lookup 参数无法声明引用目标(配置被静默剥离 + 文案谎报「即将上线」) #3405 那次被丢的两种拼法)visibleWhen/visibleOn/visibility→visible最后这条是这个 PR 超出「防错别字」的价值所在:ADR-0089 把
visibleWhen定为 view/page schema 的正统拼法,在这里借用它,过去会把参数的能力开关整个剥掉,让它无条件渲染 —— 一个静默失效的权限门。遵循 ADR-0078(no-silently-inert-metadata)与 ADR-0049(enforce-or-remove),做法与 ADR-0089 D3a 对 view/page schema 的处理一致。
影响面(issue 里担心的那一点)
issue 原文说这条「影响面比前两条大得多,可能踩到其他既有元数据」。实测本仓零破坏:
@objectstack/spec全量 258 文件 / 6716 用例通过tsc --noEmit干净validate通过 —— 仓库里没有任何既有元数据带着未声明的参数键lint467 例、metadata276 例、metadata-core100 例、platform-objects223 例、metadata-protocol70 例、sdui-parser6 例,全过验证
在 showcase 的内联 picker 参数上实种一个坏键(保留合法的
reference,只加一个visibleWhen,以隔离本 PR 的新行为):去掉后
✓ Validation passed。reference_to同样命中→ reference。破坏性说明
带多余键的既有参数现在会 parse 失败。这正是本 PR 的意图 —— 那些键本来就没有生效,只是坏得没声音。错误信息直接点名该键并给出正确拼法,changeset 里附了 FROM → TO 对照表。
遗留(不在本 PR)
#3405 验收里的 PLAT-DEF-005 真机回归仍卡在发版:npm 上
@objectstack/spec最新是 16.1.0(2026-07-22T01:06Z),早于 #3406 合并(同日 20:46Z)。我拉 16.1.0 的 tarball 确认过,dist/里没有reference的校验。天顺 EHR 那行reference: 'sys_user'至今仍被服务端 strip。#3406 的 changeset 也还挂在.changeset/里未消费 —— 同一个佐证。需要发一次 spec 版本才能收尾。🤖 Generated with Claude Code
https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
Generated by Claude Code