在做 #5364(saveMetaItem 的 422 展开 invalid_union,PR #5596)时核出的第五个消费者。#5364 的分诊帖把这条线数成"四份各自独立的消费端代码",实测是五份 —— 第五份就在 #5364 同一个包里,但在读路径上,不在那单的文件面内,故独立记录。
落点
packages/metadata-protocol/src/metadata-diagnostics.ts:77(computeMetadataDiagnostics):
const parsed = (schema as z.ZodTypeAny).safeParse(candidate);
if (parsed.success) return { valid: true };
const errors = parsed.error.issues.map((issue) => ({
path: issue.path.map(String).join('.'),
message: issue.message,
code: issue.code as string,
}));
和 #4971 / #5014 / #5341 / #5364 完全同一个 .map():只映射顶层 issue,issue.errors 里每个分支的真实拒绝理由(含 #4001 那批 strictObject 的策展处方)被丢弃。
为什么它是独立的一条
#5364 修的是 saveMetaItem 的写路径 422。这一条是读路径:computeMetadataDiagnostics → decorateMetadataItem / decorateMetadataItems 给 getMetaItems() / getMetaItem() 服务出去的每个文档挂 _diagnostics 信封,该文件自己的模块头写明用途是"so Studio (and any other consumer) can render validity badges, inline field errors, and governance dashboards"。
也就是说:#5364 修好之后,作者保存一个有缺陷的 view 能看到字段名了;但打开一个库里已经存着的有缺陷的 view,Studio 的内联字段错误仍然是一条没有字段的 Invalid input。两条路径的判决会不一致。
computeMetadataDiagnostics / decorateMetadataItem / decorateMetadataItems 都是 @objectstack/metadata-protocol 的公开导出(src/index.ts:29-32)。
实测(origin/main @ e900015)
用 #5364 那个同样的 view 输入:
computeMetadataDiagnostics('view', {
name: 'task_list', object: 'task', type: 'list', label: 'Tasks',
columns: [{ field: 'title', summary: { type: 'sum', fieldd: 'amount' } }],
})
得到:
{
"valid": false,
"errors": [
{ "path": "", "message": "Invalid input", "code": "invalid_union" }
]
}
ViewMetadataSchema 顶层本身是 union(view.zod.ts 的 z.preprocess(stripViewConsoleDecorations, z.union([...]))),所以 view 类型的每一个有缺陷的存量文档都退化成这一条。
这一条的修法应当是最便宜的一次
PR #5596 已经在同一个包、同一个文件的隔壁落了 zodIssuesToMetadataIssues(issues),信封形态就是 { path, message, code } 且 code 透传 zod 原码 —— 与 MetadataValidationResult.errors[] 的形态完全一致。所以这一条大概率是:导出/引入该函数,把上面那个 .map() 换掉,加读路径的回归测试。⚠️ 注意 computeMetadataDiagnostics 先做了 _diagnostics 的 strip(stripDiagnostics),那一段不要动。
判决必须与另外四处一致(丢弃只报根部 KIND 不匹配的分支;报得最少的分支胜出;unrecognized_keys 破平局;并列全出且有上限;嵌套 union 按绝对路径递归),否则同一个错误在保存时和打开时给出两套说法。
同族现状
| # |
消费者 |
文件 |
状态 |
| 1 |
formatZodError |
packages/spec/src/shared/error-map.zod.ts |
✅ #4971(PR #5342) |
| 2 |
zodIssuesToFields |
packages/rest/src/rest-server.ts |
✅ #5014(PR #5362) |
| 3 |
formatZodErrors |
packages/cli/src/utils/format.ts |
⏳ #5341 待派 |
| 4 |
saveMetaItem 的 422 |
packages/metadata-protocol/src/protocol.ts |
⏳ #5364(PR #5596 待评审) |
| 5 |
computeMetadataDiagnostics |
packages/metadata-protocol/src/metadata-diagnostics.ts |
⏳ 本单 |
值得记一笔的是,这五个里有四个都是在修上一个的过程中发现的 —— 这类"同一机制多份拷贝"的缺陷靠一次静态扫描数不全。第 5 份修完后,或许值得单独裁决要不要把这套策略收敛成一个共享实现(spec 目前只导出字符串渲染器,三个结构化消费者各抄一份),而不是继续按份数增长。
在做 #5364(saveMetaItem 的 422 展开
invalid_union,PR #5596)时核出的第五个消费者。#5364 的分诊帖把这条线数成"四份各自独立的消费端代码",实测是五份 —— 第五份就在 #5364 同一个包里,但在读路径上,不在那单的文件面内,故独立记录。落点
packages/metadata-protocol/src/metadata-diagnostics.ts:77(computeMetadataDiagnostics):和 #4971 / #5014 / #5341 / #5364 完全同一个
.map():只映射顶层 issue,issue.errors里每个分支的真实拒绝理由(含 #4001 那批strictObject的策展处方)被丢弃。为什么它是独立的一条
#5364 修的是
saveMetaItem的写路径 422。这一条是读路径:computeMetadataDiagnostics→decorateMetadataItem/decorateMetadataItems给getMetaItems()/getMetaItem()服务出去的每个文档挂_diagnostics信封,该文件自己的模块头写明用途是"so Studio (and any other consumer) can render validity badges, inline field errors, and governance dashboards"。也就是说:#5364 修好之后,作者保存一个有缺陷的 view 能看到字段名了;但打开一个库里已经存着的有缺陷的 view,Studio 的内联字段错误仍然是一条没有字段的
Invalid input。两条路径的判决会不一致。computeMetadataDiagnostics/decorateMetadataItem/decorateMetadataItems都是@objectstack/metadata-protocol的公开导出(src/index.ts:29-32)。实测(
origin/main@e900015)用 #5364 那个同样的 view 输入:
得到:
{ "valid": false, "errors": [ { "path": "", "message": "Invalid input", "code": "invalid_union" } ] }ViewMetadataSchema顶层本身是 union(view.zod.ts的z.preprocess(stripViewConsoleDecorations, z.union([...]))),所以 view 类型的每一个有缺陷的存量文档都退化成这一条。这一条的修法应当是最便宜的一次
PR #5596 已经在同一个包、同一个文件的隔壁落了⚠️ 注意
zodIssuesToMetadataIssues(issues),信封形态就是{ path, message, code }且code透传 zod 原码 —— 与MetadataValidationResult.errors[]的形态完全一致。所以这一条大概率是:导出/引入该函数,把上面那个.map()换掉,加读路径的回归测试。computeMetadataDiagnostics先做了_diagnostics的 strip(stripDiagnostics),那一段不要动。判决必须与另外四处一致(丢弃只报根部 KIND 不匹配的分支;报得最少的分支胜出;
unrecognized_keys破平局;并列全出且有上限;嵌套 union 按绝对路径递归),否则同一个错误在保存时和打开时给出两套说法。同族现状
formatZodErrorpackages/spec/src/shared/error-map.zod.tszodIssuesToFieldspackages/rest/src/rest-server.tsformatZodErrorspackages/cli/src/utils/format.tssaveMetaItem的 422packages/metadata-protocol/src/protocol.tscomputeMetadataDiagnosticspackages/metadata-protocol/src/metadata-diagnostics.ts值得记一笔的是,这五个里有四个都是在修上一个的过程中发现的 —— 这类"同一机制多份拷贝"的缺陷靠一次静态扫描数不全。第 5 份修完后,或许值得单独裁决要不要把这套策略收敛成一个共享实现(spec 目前只导出字符串渲染器,三个结构化消费者各抄一份),而不是继续按份数增长。