Skip to content

读路径 _diagnostics 是 union 折叠的第五个消费者:Studio 打开一个有缺陷的 view,内联字段错误同样只有一条 path:"" Invalid input #5598

Closed

Description

@os-zhuang

在做 #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 目前只导出字符串渲染器,三个结构化消费者各抄一份),而不是继续按份数增长。

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    分诊:入队 pm:queue + domain:engine-core + pm:blocked。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 6, 2026
  3. self-assigned this
    on Aug 6, 2026
  4. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    解锁 + 认领:PM 循环第 3 轮(engine-core 车道)。Blocker #5364 已随 PR #5596 合并关闭,按 unlock sweep 摘 pm:blocked 回队并认领。
    会话:session_01V7WetGmnfoXNn8cLieKKmx
    分支:claude/issue-5598-diagnostics-union-branches
    Worktree:objectstack-issue-5598
    域:domain:engine-core
    文件面:packages/metadata-protocol/src/metadata-diagnostics.ts、metadata-protocol 测试、.changeset/*.md。⛔ 不碰 protocol.ts(#5736 在队列)、不碰 packages/spec。修法照 issue:复用同包 #5596 落的 zodIssuesToMetadataIssues,判决与另四处一致;stripDiagnostics 一段不动。「五处收敛共享实现」的后续裁决项不并入本单。


    Generated by Claude Code

  5. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    PM 评审(会话 session_01V7WetGmnfoXNn8cLieKKmx):PR #5765 ACCEPT,待 CI 绿后放行。

    已读全量 diff。要点:

    1. 复用而非再抄:一处 .map() 换成同包 fix(metadata-protocol): 元数据保存的 422 保留 union 分支处方,Studio 重新拿得到字段名 (#5364) #5596 落地的 zodIssuesToMetadataIssues,分支选取口径单点定义,读写两路径按构造一致——正是派单要求的形状。⛔ protocol.ts 未动(函数本就模块级导出),stripDiagnostics 未动且有「读两遍稳定」用例守着。
    2. 纯增量语义已钉:union 自己的条目仍是 errors[0],既有消费者读到的还是同一条;4 例对照组(普通字段拒绝/非对象/合法文档/未注册类型)钉住非 union 路径逐字节不变。
    3. 不重复断言面:排序策略的行为钉在其定义处(protocol.save-union-issues.test.ts),本文件只钉「读路径接上了同一份判决」——两份测试不会漂成两份口径。
    4. 反向验证方向先判后跑,3 红 4 绿与预测一致;消费半径按「判决在哪被读」清扫(五个包),objectql 侧直接测过。

    裁决 dev 提请注意的循环 import:本 PR 原样接受。metadata-diagnostics.ts → protocol.ts 的包内循环运行期安全(函数声明提升、无模块级互调)、tsup/DTS 无告警;当下抽中立模块必须动 protocol.ts,会与在飞的 #5264 冲突。此观察作为新论据并入本单正文的后续裁决项(五处策略是否收敛为一个共享实现):若收敛,落点应是中立叶子模块(如 metadata-issues.ts),由 protocol.ts 与 metadata-diagnostics.ts 同向依赖,而不是按份数继续增长。该项仍留 triage/维护者裁决,不阻塞本 PR。

    CI 绿后我会转 ready 并挂 auto-merge。


    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

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions