Skip to content

排查「手抄 spec 清单 + "keep in sync" 注释」模式:objectui 侧六处,目前全部一致但零闸门(framework#3786) #3017

Description

@os-zhuang

承 framework#3786 —— 该 issue 点名 objectui 是头号嫌疑(「它消费 spec 的类型/枚举清单做渲染分发,而本轮没有覆盖它」)。framework 侧的排查已完成并落在 objectstack#4120:packages/spec 的 17 份元数据表单里查出四份已漂移、全部静默(表单绑到不存在的键 → 值被 .strict() 缺席的 schema 静默丢弃,作者看不到任何报错)。

本 issue 记录 objectui 侧的扫描结果。

结论先行:六处候选,目前全部内容一致 —— 没有查到活的漂移

这不是「没问题」,而是「还没轮到」。framework 那四例证明了这套写法的失效方式是静默的:漂移发生时没有任何信号,只有用户发现某个能力凭空消失。下面六处每一处都是同样的结构 —— 两份清单、一句注释、零机制。

# 位置 对照对象 现状
1 app-shell/…/inspectors/FlowReferenceField.tsx KIND_TO_RECORD_LOOKUP spec APPROVER_VALUE_SOURCES ✅ 一致(4 项 object/valueField 全同)
2 app-shell/…/inspectors/ActionDefaultInspector.tsx LOCATIONS spec ACTION_LOCATIONS ✅ 一致(7 值)
3 app-shell/…/previews/object-fields-bridge.ts DESIGNER_TYPES FieldDesignerFIELD_TYPE_META ✅ 一致(26 型)
4 plugin-detail/src/RecordMetaFooter.tsx AUDIT_FIELDS RecordDetailViewAUDIT_FIELD_NAMES ✅ 一致(4 项)
5 plugin-grid/src/ImportWizard.tsx BOOLEAN_IMPORT_TOKENS 服务端 import-coerce.tsBOOL_TRUE/BOOL_FALSE ✅ 一致(20 token)
6 plugin-grid/src/ImportWizard.tsx REFERENCE_IMPORT_TYPES 服务端 REFERENCE_TYPES ✅ 一致(5 型)

最扎眼的一点:#1#2 抄的是「spec 已经为此导出的常量」

这两处不需要闸门,直接删掉第二份清单即可 —— 单一来源已经在那儿了:

  • ACTION_LOCATIONS 是 spec 的导出常量。objectui 重述了它的 7 个值(只是顺序不同)再配上友好 label。改法:import { ACTION_LOCATIONS } from '@objectstack/spec/ui',label 用一张 Record<ActionLocation, string> 映射 —— 这样漏掉新 location 会变成类型错误,而不是一个悄悄缺席的下拉项。

  • APPROVER_VALUE_SOURCES 更值得一提:spec 里这个投影就是为了让 objectui 别再自己推导而专门发布的,它的文档注释白纸黑字写着这段历史 ——

    xRef.map 只说了渲染哪种 picker,从没说候选来自哪里。所以设计器不得不自带一份数据契约,而第一份抄错了:每个目录类 kind 都被接到了 /api/v1/meta/:type(元数据注册表),而它根本不存放 sys_user/sys_team/… 的。候选返回空,控件退化成自由文本框(framework #3508)。…… 发布这个 binding 就是在源头堵住这个缺口:渲染器直接读该查哪个 object、该提交哪个列,而不是重新推导一遍。」

    spec 侧还用 satisfies 保证了新增 ApproverType 不声明 binding 就是编译错误。但 objectui 至今仍在手工维护 KIND_TO_RECORD_LOOKUP —— 注释写的是「mirrors the spec's APPROVER_VALUE_SOURCES」,而它 mirror 的那个东西,本来就是造出来给人 import 的。spec 那侧的编译期保证,一步都没传导过来。

    ⚠️ 注意 #3508 那次翻车正是这一处:sales_manager 被打进了只接受三个值的字段。这处的历史故障率不是零。

其余四处的改法

修法模板

沿用 #3747 / cloud#898 / objectstack#4120:能派生就从唯一来源派生;确实无法派生的,加一条集合覆盖/不相交断言当闸门。注释不是机制。``

#4120 里的 metadata-form-zod-reconciliation.test.ts 可作参照,其中两个设计点值得抄:

  1. 两个方向刻意不对称 —— 「消费方提供了源不接受的键」永远是缺陷、不可豁免(没有任何设计理由让用户去填一个值会被丢弃的控件);「源有而消费方不提供」可登记豁免,但必须带理由。
  2. 豁免登记表本身要被校验 —— 非空、且每条仍能在两侧解析,否则登记表会腐烂成对已删除键的引用。#4120 里就抓到该模式的终末形态:一句「两侧同时更新」的注释,它指向的 ToolCategorySchema 早在 #3896 就被删了 —— 注释腐烂得比清单还安静。

闸门写完请做变异测试确认它真会红(#4120 那份三个方向分别触发 1、4、4 个失败)—— 一个不会失败的对账测试,和一句注释是等价的。

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions