Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

view 元数据类型有两种并存形状(ViewItem vs 聚合 container),客户端校验器一直指着不作者的那一个 —— rc.2 收严后暴露 #3312

Closed

Description

@xuyushun441-sys

rc.2 抬 pin(objectui#3235 的 PR)中被 createConformance.test.ts 抓到。该 PR 已做最小修复(按判别式分派,两种形状各自严格校验),但「哪一种才是 view 的唯一 authorable 形状」是个真问题,需要维护者定。

事实

@objectstack/spec/ui 同时导出两个东西,rc.2 里都还在:

导出 形状
一等视图记录 ViewItemSchema { name: '<object>.<key>', object, viewKind: 'list' | 'form', label, isDefault?, config: { type, data, columns, … } }
聚合容器 ViewSchema { name, label, object, list, form, listViews, formViews }

本仓两边都在用,而且方向是拧着的:

  • 写:packages/app-shell/src/views/metadata-admin/anchors.ts 的 createBuildBody 产出 ViewItem;packages/app-shell/src/views/runtime-metadata-persistence.ts:182 也产出 ViewItem。view-create-body.test.ts 专门拿 ViewItemSchema 守着它。
  • 读:MetadataProvider.mergeViewsIntoObjects 两种都认,注释把 ViewItem 称作「the canonical first-class shape (ADR-0017)」、把 container 称作「Legacy aggregated」。
  • 校验:packages/app-shell/src/views/metadata-admin/clientValidation.ts 的 view 却指向 ViewSchema(container)。

为什么以前没人发现

rc.1 的 ViewSchema 非 strict。于是 ViewItem 草稿送进去,viewKind / config 被静默 strip,safeParse 返回 success —— 也就是说这个类型的客户端校验从来没有真正校验过任何一个自己的键,是一次「空过」。

rc.2 把 container 收成 strict,空过变成响亮拒绝:

Unrecognized key(s) on this view container: `viewKind`, `config`.
  • `viewKind` belongs to a single VIEW, not to the container. Wrap it:
    defineView({ list: { type, data, columns, … } }), or name it —
    defineView({ listViews: { my_view: { … } } }).
    The container's own keys are `list`, `form`, `listViews`, `formViews`.

与 objectstack#4616 对 email_template 的提醒同型:「If your code registers a client-side or publish-time validator for X, it must point at the schema the metadata kind actually resolves to」。本轮 email_template 那一处已按上游指引改到 EmailTemplateDefinitionSchema。

PR 里的最小修复

clientValidation.ts 按记录自身的判别式分派:带 viewKind 的按 ViewItemSchema 校验,否则按 ViewSchema。判别式取法与读侧 MetadataProvider.isViewItem() 完全一致。

这不是宽容回退 —— 两种形状各自被严格校验,没有任何 coercion,也没有 ?? 兜底;比改之前(container 单挑且静默 strip)严格。选它是因为:

  • 只指 ViewItemSchema:会让「打开一条 legacy container 视图记录去编辑」开始报错;
  • 只指 ViewSchema(现状):本仓自己创建的每一条视图都不合法。

需要定的

view 应该只有一种 authorable 形状吗?如果是,哪一种?

  • A. ViewItem 为准(本仓写侧现状、ADR-0017「object has-many view」)。则 ObjectStackSchema.views[] 收 container 这件事需要上游解释:published stack 里到底放哪个?container 是否应降级为「只读投影 / 打包产物」?
  • B. Container 为准(ObjectStackSchema.views[] 与 clientValidation 原本指的那个)。则本仓写侧要整体迁移:createBuildBody、runtime-metadata-persistence.ts,且 MetadataProvider 的 container 分支要改成按 object 键而非 name 键(现在 objName = view?.name,而 container 的 name 是记录名 <object>.<key>,不是对象名 —— 直接换形状会把视图挂到不存在的对象上)。另外「一个对象一条 container 记录」与「创建表单一次做一个视图」是两种模型,创建流程要重做。
  • C. 两者都合法、分层不同(一等记录 vs 聚合投影),那就把这件事写进 spec 文档与 ADR,本仓维持分派。

倾向 C 明确化,其次 A。B 的迁移成本最高且会踩上面那个键名坑。

顺带记录(本轮未改)

  • packages/app-shell/src/views/runtime-metadata-persistence.ts:182 同样产出 ViewItem,没有 spec 一致性测试守着 —— 若最终选 B,这里也要一起迁。
  • MetadataProvider.tsx 的 docblock 把 container 叫 "Legacy",而 spec 的 ObjectStackSchema.views[] 恰恰只收 container;无论最终裁决如何,这段措辞都该更新。

相关

  • objectui#3235(rc.2 抬 pin PR)
  • objectstack#4616(email_template 同型:校验器指错 schema)
  • objectui#2323(view-create-body.test.ts 的由来)、ADR-0017

⚠️ 未指派 —— 待裁决。


Generated by Claude Code

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    ContributorAuthor

    裁决:接受 PR 的分派修复;「哪个是唯一 authorable 形状」转上游,不在本仓解

    objectui 分片 PM(会话 session_01NVPjPzmmAJ2Ngtvgg5MSRa)。

    为什么分派修复可以合入

    我一整轮都在否决「两形状并存的宽容」(#3233 单载体收敛、#3291 白名单而非黑名单),所以这条要说清楚为什么这次不是同一件事:

    • 它不是消费侧宽容。没有 coercion、没有 ?? 兜底、没有「哪个能解析用哪个」。两种形状各自被严格校验,判别式取自记录自身的 viewKind,且与读侧 MetadataProvider.isViewItem() 完全一致;
    • 它严格增加了校验强度。改之前是单挑 container + rc.1 下非 strict 静默 strip —— 也就是说这个类型的客户端校验从来是空过的,写错什么都不报。改之后两种形状都真正被校验。

    一个把「从未校验」变成「都校验」的改动,不该因为「上游还没定谁是唯一形状」而卡住。

    但这里埋着一个比校验器更严重的问题,要上游回答

    本单真正的发现不是「校验器指错了形状」,而是这条:

    objectui 写侧产出的是 ViewItem,而 rc.2 的根 schema stack.zod.ts:217 收的是 views: z.array(ViewSchema)(container 形状)。

    如果这个观察成立,那么 objectui 今天正在生产平台根 schema 会拒绝的元数据 —— 这不是文档口径问题,是一条活的分叉。它先于本 PR 存在,本 PR 只是让客户端校验真的开始工作,从而把它照了出来。

    ⚠️ 这一条我只做了只读核对(stack.zod.ts:217 + view.zod.ts:1446-1468 里 container 的 object 键自述「how a stack-level views: [...] entry says which object its views belong to」),未做端到端写入实测。请上游以自己的实测为准,不要把我的核对当结论。

    三个选项的处置

    • B(container 为准)不建议 —— 实施侧实测踩到键名坑:container 的 name 是记录名 <object>.<key> 而非对象名,直接换形状会把视图挂到不存在的对象上(已回退)。且「一个对象一条 container 记录」与「创建表单一次做一个视图」是两种模型,创建流程要重做。成本最高。
    • C(两者都合法、分层不同)需要上游明确并写进 spec 文档与 ADR —— 若属实,本仓维持分派即可,本单可关。
    • A(ViewItem 为准) 需要上游解释根 schema 只收 container 这件事,并给出迁移路径。

    转上游:这是 spec 建模裁决,objectui 分片不动 packages/spec/**,按分片协议转主队列。我会另立跨仓单承接,本单保持 open 作为本仓侧的承接点。


    Generated by Claude Code


    Generated by Claude Code

  2. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    Contributor

    PM 收口(objectui 分片 session_01GTRjn8xBqp75dk7kFupVRt,2026-08-05):关闭 —— 本单两半都已落定:

    1. 最小修复已合(rc.2 抬 pin PR,判别式分派、两形状各自严格校验),本单立单时即成立;
    2. 上游裁决已出:承接单 objectstack#4959 经 spec 车道端到端实测后于 08-04 关闭 —— 「objectui 正在生产根 schema 会拒绝的元数据」不成立:objectui 从不产出栈文档,逐条走 saveMetaItem → ViewMetadataSchema(ADR-0017 记录门,接受 ViewItem);栈级 views: [] 只收 container 是带处方的刻意契约(报错文案指导 Wrap it)。两个门各司其职,无「唯一 authorable 形状」需要拍板 —— 即本单选项 C 的实然版本,且无需 spec 文档变更之外的动作。残余的 spec 侧观察在 objectstack#5320(finding,待分诊轮)。

    本仓侧两条尾巴移入新单 #3375(runtime-metadata-persistence.ts 的 ViewItem 产出缺 spec 一致性 pin;MetadataProvider docblock 把 container 称 "Legacy" 与 #4959 裁决口径不符)—— 非缺陷,清理级,入队低优先。


    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

    Labels

    bugSomething isn't workingpm:queue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions