Skip to content

metadata-admin:范畴判定只认 invalid_type,enum 成员对非枚举值答 invalid_value,把可判定的 union 留在塌陷态 #3694

Description

@yinlianghui

发现于 #3678 的实施(PR #3693)。观察类,不是用户今天会撞到的回归 —— path 是真的,只是 message 泛化。分诊定级请自行裁决,本卡只负责记录测量。

背景

#3626 / PR #3677 引入了一条范畴判定:某个 union 成员如果在 union 节点自身上直接拒绝了值的类型,它压根没读过值,内容就不能作为它的证据,于是它不是候选。#3678 / PR #3693 把同一个判定读成普查:恰好剩一个成员时选它。

两条规则都建立在同一个谓词上:

function memberRejectedNodeType(group: ZodLikeIssue[]): boolean {
  return group.some((i) => i.code === 'invalid_type' && (i.path ?? []).length === 0);
}

测量

它只认 invalid_type。Zod 的 enum 成员被喂一个非枚举值时答的是 invalid_value,不是 invalid_type —— 于是这个成员被算作「读过值的候选」,尽管它同样是在节点上整体拒绝、同样没看过内部。

columns[0].summaryenum | {type, field},实测(@objectstack/spec,PR #3693 之后):

body 成员普查 今天的 path + message
summary: 'bogus'(字符串) 对象成员答 invalid_type → k=1 config.columns.0.summary / Invalid option: expected one of "none"|"count"|…
summary: {type: 'bogus'}(对象) enum 成员答 invalid_value path=[] → 被计为候选,k=2 config.columns.0.summary / Invalid input

第二行里真正读过这个对象的只有 {type, field} 一个成员;若范畴判定放宽成「任何在 union 节点自身(相对 path 为空)整体拒绝该值的 issue」,k 就是 1,唯一候选规则可以下降到 config.columns.0.summary.type,给出 type 的具名 enum 选项。

为什么 PR #3693 没有顺手改

范畴判定是 #3626 立的,#3678 的有界授权明确是「判定门不动、沿用既有约束」。放宽这个谓词会同时改变两条规则的适用范围(内容判别的弃权集合也随之变化),那是另一次测量 + 另一轮逆向验证,不该搭车。PR #3693 把这一条写进了「已知残留」并在 memberRejectedNodeType 的注释里注明了这个 caveat 是刻意保留的,免得下一个读者以为是漏写。

判断这值不值得做

  • 赞成:范畴判定的本意是「这个成员有没有读过值」,invalid_value at path=[] 同样是「没读过」,只认 invalid_type 是实现细节漏进了语义。放宽之后规则更贴近它自己的说法。
  • 反对:要重新普查整个 view 家族确认放宽不会让某个 union 从「无人」掉进「唯一候选」并选错;invalid_value 在 path=[] 上还可能来自 z.literal / refine 等,语义未必都是「整体拒绝」。必须先测量,不能按论证直接改。
  • 收益上限很小:目前只影响 summary: {type:…} 这一类形状,且路径已经准确,只是 message 泛化。

倾向:低优先。与 #3626 / #3678 同族,同样在 objectstack#6391(spec 侧提供判别入口)落地后大概率整体消解 —— 若 spec 接了,这一条连同前两条局部规则一起退休。

相关:#3626#3678、PR #3677、PR #3693、objectstack#6391。


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions