Skip to content

union 分支里的 unknown-key 处方永远到不了作者:zodIssuesToFields 只映射顶层 issue,失败的 union 只剩 Invalid input #5014

Description

@xuyushun441-sys

#4001 战役的每一条策展处方,只要它所在的 schema 被包在一个 z.union([...]) 分支里,在线上就是不可见的。测量于 2026-08-03(批 14 收紧 DashboardWidget.compareTo 时,先写断言再证红,发现断言红在了意料之外的地方)。

机制

zod 把一个失配的 union 折叠成一条顶层 invalid_union issue,message 是裸的 Invalid input;各分支的真实错误挂在 issue.errors(每个分支一个数组)。

packages/rest/src/rest-server.tszodIssuesToFields 是:

return issues.map((i: any) => ({
    field: ..., code: zodIssueToFieldCode(i, ...), message: String(i?.message ?? 'Invalid value'),
}));

—— 只走顶层。invalid_union 被映射成 code: 'invalid_shape',message: 'Invalid input',issue.errors 里的内容整个丢掉

实测

DashboardWidgetSchema.parse({ id, dataset, values, compareTo: { offset: '7d', granularity: 'month' } }):

顶层:

code=invalid_union path=["compareTo"] msg=Invalid input

而分支错误里躺着这条(批 14 新写的):

Unrecognized key(s) on this comparison window: `granularity`. Until #4001 closed this shape
these were dropped silently — the dashboard still rendered, without whatever the key was
meant to configure.

作者看到的是 Invalid input

为什么这是战役级问题而不是一个 widget 的问题

拒绝本身是对的、也是 #4001 的收益(静默半丢弃 -> 硬失败)。问题是处方这一半:账本 finding 18 已经把它写成规则 —— 「prose in a rejection is behaviour, not documentation」。战役花在 aliases/guidance 上的策展,一旦落在 union 分支内,就等于没写;而且没有任何测试会发现,因为对着 schema 断言 message 是能过的(错误确实被产生了,只是没被传递)。

需要一次普查:packages/spec 里有多少条 strictObject( 落在 union 分支内(compareTosubmitBehaviorview.zod 的若干 union 成员等)。

建议

zodIssuesToFields 里展开 invalid_union:把每个分支的子 issue 提升为独立的 field 条目(path 需要拼上父 path),或者在只有一个分支「明显是作者意图的那支」时优先提升该分支。后者更难判定但输出更干净 —— 对象输入配对象分支,是一个可用的启发。

⚠️ 这是 wire 契约的改动(fields[] 的条数会变),按 ADR-0114 需要和 mapDataError 保持同形。

批 14 已在 dashboard.zod.tscompareTo 处记下这条实测限制,并在测试里分开 pin 了两件事:作者今天真正看到的裸 message,和等着被传递的处方文案 —— 免得一个绿测试冒充一条没人打印的消息。

发现于 #4001 批 14 的范围外体检,未指派。

Activity

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