fix(metadata-protocol): 元数据保存的 422 保留 union 分支处方,Studio 重新拿得到字段名 (#5364) - #5596
Merged
Conversation
…#5364) The spec-conformance 422's own comment promises "structured Zod issues so the Studio form can highlight the offending field". A top-level `z.union` broke that completely: zod folds every branch of a failed union into ONE issue whose path is '' and whose message is the literal "Invalid input", and `parsed.error.issues.map(...)` mapped exactly that. Since `ViewMetadataSchema` IS a top-level union, EVERY failed view save arrived at Studio as one rootless line with no field name in it at all. `zodIssuesToMetadataIssues` expands the branches that explain the rejection, resolving branch-relative paths against the union's own. The union's own entry is kept, so the change is strictly additive. Branch selection is byte-for-byte the ranking `formatZodError` (#4971, spec) and `zodIssuesToFields` (#5014, rest) already use: drop kind-mismatch-only branches, fewest issues wins, `unrecognized_keys` breaks the tie, declaration order breaks the rest, ties all emitted (cap 3), nested unions recursed (depth 3). Three copies, one verdict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7WetGmnfoXNn8cLieKKmx
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…emeta-union-branches
Contributor
Author
|
CI 红说明(engine-core 车道 PM):ESLint job 的失败是 Generated by Claude Code |
This was referenced Aug 5, 2026
Closed
…emeta-union-branches
os-zhuang
marked this pull request as ready for review
August 5, 2026 21:38
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 6, 2026
…ack-ai#5598) (objectstack-ai#5765) computeMetadataDiagnostics 把 zod 的 error.issues 直接 .map() 成 _diagnostics 条目。zod 会把一个失败 z.union 的全部分支折叠成一条顶层 issue —— path 为空、 message 是字面量 "Invalid input" —— 而 ViewMetadataSchema 顶层本身就是 union, 所以库里每一个有缺陷的 view 读出来都退化成这一条没有字段名的记录,模块头承诺的 "内联字段错误"无处可标。 后果是同一份文档在两条路径上判决不一致:objectstack-ai#5364(PR objectstack-ai#5596)修好写路径之后,保存 一个有缺陷的 view 能看到出错的键名,打开同一份已存库的文档却仍然只有一条 Invalid input。 改法是复用而非再抄一份策略:读路径改调同包 objectstack-ai#5596 落地的 zodIssuesToMetadataIssues,分支选取口径由该函数单点定义,读写两路径按构造一致。 展开是纯增量 —— union 自己那条仍在 errors[0]。stripDiagnostics 一段未动。 Claude-Session: https://claude.ai/code/session_01V7WetGmnfoXNn8cLieKKmx Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5364
> 排版说明:下文一律用
⟨root⟩指代源码里那个 ASCII 尖括号字面量(小于号 + root + 大于号,见i.path || ...那行的 fallback)。GitHub 的正文消毒器会把小于号紧跟字母整段当 HTML 标签删掉,本 PR 初版正是这么被吃掉了一次,故改用这对括号写。✅ 停放已解除(PM 维护,2026-08-05)
曾因 base 分支的 #5604 红(
check-engine-double-contract挂action-execution-calldata-not-found.test.ts)停放;#5615(2614aef)落地后本分支已同步 main(head2cf9cc0),CI 23 项全绿,按 ACCEPT 流程转 ready 入队。前提复核(在 origin/main
e900015上实测)先按「issue 是线索不是规格」核了一遍。issue 引用的
protocol.ts:7128已漂移,INVALID_METADATA现在有两处:L7010 是「分层读信封当写体」的防御性拒绝(与本单无关),L7141 才是 saveMetaItem 的 spec-conformance 422。缺陷本身则完全成立,走真实saveMetaItem路径复现:被
.map()丢掉的 4 个分支里躺着的是:branch[1] 的unrecognized_keys(点名type、columns,带 #4001 策展处方)、branch[2]/[3] 的invalid_value(带槽位路径与合法枚举)。改了什么
packages/metadata-protocol/src/protocol.ts新增zodIssuesToMetadataIssues,422 的issues由它产出。union 自己那条保留不动——展开是严格叠加的,今天读issues[0]的消费者不会少读到任何东西——后面跟上真正解释这次拒绝的分支,路径按绝对路径拼好(分支 issue 的path是相对于 union 的,#5014 为此付过学费)。422 的message摘要行随之变得可读。分支选择策略与 main 上两份参考实现逐条一致:丢弃只报根部 kind 不匹配的分支;报得最少的分支胜出;
unrecognized_keys破平局;声明顺序决定其余;并列全出(上限 3);嵌套 union 递归(上限 3 层)。这是同一机制的第三份拷贝,形态不同而判决相同:spec 的formatZodError(#4971)只导出字符串渲染器,rest 的zodIssuesToFields(#5014)产出 ADR-0114 的{ field, code }目录条目,本处信封是{ path, message, code }且code透传 zod 原码(按派发约束,不顺手对齐 ADR-0114 目录 —— 那需要单独裁决)。修后实测
issue 那个 view 输入,
issues从 1 条变成 2 条,第二条code是unrecognized_keys,message 点名`type`、`columns`并带 #4001 的完整处方;摘要行从... failed spec validation: ⟨root⟩: Invalid input变成... failed spec validation: ⟨root⟩: Invalid input; ⟨root⟩: Unrecognized key(s) on this view container: ...。派发约束写的是「修后
issues必须含带真实键名的unrecognized_keys条目与带type路径的invalid_value条目」。用 issue 那个输入,后者不会出现,而且这不是实现缺陷:unrecognized_keys→ rank[1, 0][2, 1]「报得最少的分支胜出」是排序第 2 位,
unrecognized_keys破平局是第 3 位,所以只有 branch[1] 入选。要同时输出两者就必须改排序策略,而那会让四个消费者判决不一致——正是本单存在的理由。故按「语义必须与两份参考一致」执行,并把「带type路径、可高亮」的承重断言放到确实产出它的输入上(容器体{ list: { type: 'nope', ... } }→path为list.type、code为invalid_value、message 含合法枚举)。测试
新增
packages/metadata-protocol/src/protocol.save-union-issues.test.ts(14 例):issues[0];摘要行可读list.type绝对路径 +invalid_value+ 合法枚举rows.size === 0errors: []变体不加噪;最少胜出 +unrecognized_keys破平局;并列全出且上限 3;逐层拼绝对路径;第 4 层 union 不再展开;同键同话去重一次;去重不跨顶层 issue;非数组入参返回空信封反向验证(方向事先预判,结果与预判一致)
把调用点还原成旧的
.map(),预判「只有两条 save 路径的展开断言转红,其余 12 条(zod 事实钉、合法保存对照、10 条纯策略单测)保持绿」。实跑:影响面(按规则消费半径,不按被改包)
err.issues的下游 fixture 全扫了一遍:packages/objectql的protocol-meta.test.ts/metadata-validation-sweep.test.ts只断言Array.isArray(issues)与 code/status,packages/rest的两个信封测试自建issues数组——都不钉条数或内容,故不受影响。最终同步圈(head2cf9cc0,base 含2614aef)三包实跑:typecheck:
@objectstack/metadata-protocol无typecheck脚本(在check-type-check-coverage.mjs的 DEBT 台账里)。改用直跑 tsc 对比基线,63 → 63,未增一条;pnpm check:type-check-coverage与node scripts/check-nul-bytes.mjs均 OK;check:engine-double-contractOK(26 pinned);#5600 新落的 query-options-erasure ratchet 绿且本 PR 未进其基线。范围纪律
packages/spec(含strictness-batch14.test.ts:191/dashboard-compareto.test.ts:201的 docblock 校准 —— 那两句「zodIssuesToFields只映射顶层 issue」在 PR fix(rest): zodIssuesToFields 展开 invalid_union,联合分支里的处方到达调用方 (#5014) #5362 合入后对 REST 面已失真,本 PR 又让它对 saveMetaItem 面失真;按派发约束不动,债记在 元数据保存的 422 也丢掉 union 分支处方:一个 view 保存失败只回一条path:"" message:"Invalid input",Studio 无字段可高亮 #5364 时间线)packages/rest/packages/cli(union 分支里的 unknown-key 处方永远到不了作者:zodIssuesToFields只映射顶层 issue,失败的 union 只剩Invalid input#5014 已修、os validate/os build用的是 CLI 自己的 formatZodErrors,它同样把 union 分支的处方裁掉 —— #4971 修的不是这条路径 #5341 待派)content/docs/releases/;.changeset/savemeta-422-union-branch-issues.md已附packages/spec/authorable-surface.base.json(gen:schema重锚baseRev)已还原,不在本 PR 的 diff 里顺带发现(已另立 issue,不在本 PR 修)
_diagnostics是 union 折叠的第五个消费者:Studio 打开一个有缺陷的 view,内联字段错误同样只有一条path:"" Invalid input#5598 —— 同一折叠机制的第五个消费者:metadata-diagnostics.ts:77的computeMetadataDiagnostics(读路径_diagnostics信封,同形同病)。本 PR 修好后,保存一个有缺陷的 view 能看到字段名,打开一个存量有缺陷的 view 仍然只有一条无字段的Invalid input。view的 spec 校验闸门形同虚设:saveMetaItem({ item: { nope: 1 } })返回 success 并把{"nope":1}存成一个 active view #5599 ——view的 spec 校验闸门形同虚设:saveMetaItem({ item: { nope: 1 } })返回 success 并把该 body 存成一条 active view。ViewMetadataSchema第 4 个 arm(FormViewSchema.extend(...).strip())既 strip 又无必填键,等于给整个 union 开了万能通行证。落点在packages/spec,需要单独裁决。🤖 Generated with Claude Code
https://claude.ai/code/session_01V7WetGmnfoXNn8cLieKKmx