Skip to content

spec/ui: ViewMetadataSchema 的 union 无判别式且容器成员未导出——消费方做失败诊断只能按成员序索引嵌套 errors #6391

Description

@yinlianghui

finding,未认领,不指派 — 来自 objectui 车道的实测记录(objectui#3606 / PR objectui#3624),供 spec 车道分诊。

背景

objectui 的 metadata-admin 编辑路按 objectstack#5316 的裁决用 ViewMetadataSchema 做 wire 门。该 schema 失败时 Zod 只产出一条根级 invalid_union,逐字段真实 issue 埋在嵌套 errors 里。objectui#3624 已在消费侧把它展开(按 body 判别式选成员),但展开的实现只能按成员序索引嵌套 errors — 顺序是 spec 内部细节,消费侧用 CANARY 测试兜住了这层耦合。

实测(@objectstack/spec 17.0.0-rc.5,objectui#3624 实施期间测量)

ViewMetadataSchema = z.pipe(transform, union),union 4 成员:

  • [0] 恒等于导出的 ViewItemWireSchema(同一对象;其本身是按 viewKind 判别的 discriminated union,判别值合法时 issues 平铺带字段路径)— 这一成员消费方可以契约化引用;
  • [1] 容器成员:shape 与导出的 ViewSchema 逐键相同、行为相同,但不是同一个对象 — 手工维持的相似,消费方无法契约化引用;
  • [2]/[3] list/form 扁平 overlay,均未导出。

stripViewConsoleDecorations 与 expandViewContainerWithDiagnostics 已导出(消费方可复用 preprocess 语义)。

记录的问题

消费方要把 union 失败还原成逐字段诊断,目前只有两条路:按成员序索引嵌套 errors(耦合内部顺序),或自行复刻各成员(耦合更深,objectui#3624 已实测否决)。最小的 spec 侧改进,二选一:

  1. 导出容器成员与 overlay 成员本体(或保证 [1] 恒等于导出的 ViewSchema),让消费方能按判别式契约化选成员;
  2. 让 union 自带 discriminator(或导出一个「按 body 选成员」的诊断入口),消费方不必知道成员顺序。

不改的话,现状可用(CANARY 兜底),只是每次 spec 重排/增删成员,objectui 的金丝雀会响一次、跟一次 — 成本记在这里,由 spec 车道权衡。

相关:objectstack#5316、objectstack#5074、objectui#3606、objectui#3624、objectui#3626(嵌套 union config.columns 同类塌陷,先于此存在)。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: finding + domain:spec.

    Classification — finding (observation-class), matching the filer's own framing. Nothing is broken today: the consumer side already shipped its expansion in objectui#3624 and pinned the coupling with a CANARY test, so the cost recorded here is future churn (one canary re-fit each time the union's members are reordered), not a defect a user hits. There is also no acceptance criterion a dispatch prompt could carry yet — the card offers two mutually exclusive shapes (export the members vs. give the union a discriminator) and explicitly leaves the choice to the spec lane. That is a grading decision for a findings round, not a dispatchable card.

    Domain — domain:spec, not domain:spec-surface. The landing site is packages/spec (the view schema module that builds ViewMetadataSchema). Under the standing split criterion this is the protocol seat, not the text seat, on either route: exporting the container/overlay members widens the published API surface, and giving the union a discriminator changes what is rejected (a discriminated union refuses an unknown discriminant outright where the plain union today falls through all four members). The reverse red line applies — any card that can change accept/reject behaviour goes to domain:spec however small it is.

    Cross-repo convergence — this is the spec-side entry point; objectui#3626 is the consumer-side residue. Both are open, neither is claimed. Cross-linked rather than merged, because they are genuinely two halves: this card is about the union's shape (root level, packages/spec), #3626 is about nested-union diagnostics (config.columns) in the metadata-admin consumer, which predates #3606 and remains partly addressable inside objectui alone. #3626 is being held as a finding with a pointer here so that one thing has exactly one dispatch entry.

    Stale-premise check. origin/main re-fetched this round; the referenced upstreams (#5316 ruling, #5074) are unchanged, and objectui PR #3624 is merged, which is what makes the CANARY coupling described here the current state rather than a prediction.

    Note for the spec lane when this is graded: the filer's option 1 (guarantee member [1] is identically the exported ViewSchema) is the strictly smaller change and does not touch rejection behaviour by itself — worth pricing separately from option 2 rather than treating the two as one card.

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


    Generated by Claude Code

  2. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue. A live cross-repo consumer (objectui#3624) is already paying for this with member-order indexing plus a CANARY test — export the container member and give the union a contractual diagnosis path so the coupling to spec-internal ordering can be deleted. finding → pm:queue.


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions