Skip to content

An action param's defaultValue is typed z.unknown(), so a default that can never satisfy the param's own value contract is accepted at authoring time and only 400s at submit #6970

Closed

Description

@yinlianghui

在实现 objectstack#5061(objectui 侧 datetime action 参数提交形状)时发现的相邻缺口,与该单各自独立闭环,不是它的子问题:#5061 的完成范围是 Console 提交侧的形状,这一条是 spec 授权时的校验缺口。

缺口

ActionParamSchema.defaultValue 声明为 z.unknown().optional()(packages/spec/src/ui/action.zod.ts:262),即任何 type 的参数都接受任何形状的默认值。而自 17.0 起,dispatcher 在 handler 之前用 validateActionParams(packages/spec/src/ui/action-params.zod.ts,ADR-0104 D2)按同一个 valueSchemaFor 校验实际提交的 bag。两者不相交时,授权期静默通过、提交期 400。

最容易触发的具体例子(datetime):

params: [{ name: 'start', type: 'datetime', defaultValue: '2026-08-10T15:00' }]
  • 授权/发布:通过,没有任何告警。
  • 用户打开对话框:控件把该值显示出来(datetime-local 接受这个形状)。
  • 用户不改这个字段直接提交 → InstantValueSchema 拒绝:
    Action param "start" (datetime): expected an ISO-8601 instant with explicit zone (e.g. 2026-03-15T14:30:00.000Z)

报错文案指向参数名,但不指向真正的原因(作者写的默认值),用户看到的是一个自己没碰过的字段导致提交失败。

同一个洞对其他类型同样成立,datetime 只是最刺眼的一个:type: 'number' 配 defaultValue: 'abc'、type: 'select' 配一个不在自己 options 里的 defaultValue、multiple: true 配一个标量默认值,都是授权期通过、提交期 400。

为什么不在 Console 侧兜

objectstack#5061 的修复(objectui PR,见下)刻意没有在渲染器里把无时区默认值归一化,理由记录在 serializeParamValues 的注释里:

  1. 语义上是歧义元数据 —— 一个不带时区的墙上时钟,按谁的时区解释?按浏览者时区归一化会让同一份元数据对不同用户产生不同 instant,而元数据从未声明这个语义。
  2. 更糟的是分裂:UI 里"能用了",而 REST / MCP 提交同一个字面量依旧 400。这是最难排查的一种不一致。

按 contract-first(AGENTS.md #0.1),正确的位置是生产端在授权时被拒绝,而不是消费端容忍。

建议修向

ActionParamSchema 的 defaultValue 不再是 z.unknown(),而是用参数自身的 type / multiple / options 走同一个 valueSchemaFor(..., 'stored') 校验 —— 即让"声明即强制"对默认值也成立,复用已有的 D1 值形状契约,不新增第二套规则。字段级 defaultValue 是否有同样的洞值得一并核实。

对 AI 生成的元数据尤其重要:这类默认值是 AI 作者最容易写错的地方之一(写成人类可读的墙上时钟),而当前唯一的反馈是运行期一条不指向原因的 400。授权期硬拒绝能让这个错误在写下的那一刻就暴露。

关联

  • objectstack#5061 —— Console 侧提交形状(已修,objectui PR 见该单)。本单是它注释里点名的后续,不与之合并。
  • objectui#3127 / objectui#3565 —— DateTimeField 两向 ISO 基准的由来。

发现于 objectstack#5061 的实现过程(objectui 仓),按 Prime Directive #10 未认领入档。

Activity

  1. yinlianghui commented on Aug 9, 2026

    @yinlianghui
    ContributorAuthor

    分诊轮裁定(objectui 分片 PM,session session_01GTRjn8xBqp75dk7kFupVRt,2026-08-09):晋级 pm:queue,归 spec 车道(domain:spec 沿用)。

    理由:decision-free 的契约收紧 —— defaultValue 用参数自身 type/multiple/options 走同一 valueSchemaFor 校验是恢复 declared = enforced,不是新设计;授权期静默、提交期 400 且报错不指真因的现状是「AI 批量犯错被掩盖」的教科书形态(#5061 的 datetime 只是其中一个 type)。spec 席位经 backlog 扫描自取。


    Generated by Claude Code

  2. self-assigned this
    on Aug 9, 2026
  3. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    CLAIM — PM seat domain:spec (session session_01PiRUoQkTSBBmpyXBY3cVn2), dispatching to an os-dev subagent.


    Generated by Claude Code

  4. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    PM bookkeeping: pm:dispatched → pm:queue. The dispatched os-dev was stopped externally (mass session-level cancellation, discovered by the 15:21Z patrol census) before pushing any code — no branch exists, zero work product. Claim released; card returns to the queue intact. Re-dispatch awaits the maintainer's word; the dispatch brief (triage ruling: decision-free tightening via valueSchemaFor) remains valid as written.


    Generated by Claude Code

  5. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    RE-CLAIM — PM seat (session session_01PiRUoQkTSBBmpyXBY3cVn2), fresh os-dev dispatched under the maintainer's re-acceleration (2026-08-09 chat). Branch claude/issue-6970-action-param-default-value, worktree ../objectstack-issue-6970. All constraints in the 12:19Z claim comment remain binding.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions