Skip to content

app 元数据表单仍然提供三个已 retiredKey() 退役的键(sharing / embed / mobileNavigation)—— 填了必然存盘失败,而 #3786 对账门看不见 #5280

Description

@os-zhuang

#5015EmbedConfigSchema / NotificationActionSchema ADR-0049 退役)的前置核验中顺带测到,与该单无因果关系,单独立案。

事实

packages/spec/src/ui/app.form.ts 的「Access & sharing」「…」分区仍然给作者渲染三个 composite 输入:

{ field: 'sharing',          type: 'composite', helpText: 'Public/internal/restricted access control' }
{ field: 'embed',            type: 'composite', helpText: 'iFrame embed configuration' }
{ field: 'mobileNavigation', ... }

而这三个键在 packages/spec/src/ui/app.zod.ts:1227/1233/1240 早已是 retiredKey() 墓碑(17.0.0,2026-06 liveness audit / ADR-0049)。retiredKey()z.never({...}).optional(),所以填进去不是「静默丢弃」,是硬解析失败

实测(AppSchema = getMetadataTypeSchema('app'),同一次运行带阳性对照):

shape has "sharing": true
shape has "embed": true
shape has "mobileNavigation": true
form offers: [ 'mobileNavigation', 'sharing', 'embed' ]
AppSchema.safeParse with sharing: success=false :: `App.sharing` was removed in @objectstack/spec 17.0.0 …
AppSchema.safeParse with embed:   success=false :: `App.embed` was removed in @objectstack/spec 17.0.0 …
control (no retired key): success= true

阳性对照(不带退役键的同一份 payload)通过,所以 success=false 是关于这三个键的事实,不是 payload 本身有问题。

为什么门没开火 —— 这才是要记的那一半

packages/spec/src/system/metadata-form-zod-reconciliation.test.ts(#3786)有一条正好针对这个形状的断言:

%s: every field the form offers is a key the Zod accepts —— "An offered key the schema does not declare is silently stripped on save"

它用 keysOf(root) 取 shape 的键来判定。retiredKey() 按设计把键留在 walked shape 里(spec-property-retirement 套件里写明的那条:墓碑路线的 liveness 行必须保留,正因为键还在 shape 中)。于是墓碑键对这条断言永远是「Zod accepts」,门绿着,而作者拿到的是 422。

即门的谓词写的是 key ∈ shape,要判的却是 key 是真实可授权面。两者在 retiredKey() 出现之前是同一件事,之后不是了。

顺带:该断言的措辞("silently stripped on save")现在也只覆盖了两种失败里的一种 —— 未声明键静默丢弃、墓碑键硬失败,后者更响但更早,作者在控制台里根本不该看到这个控件。

建议范围(PM 定,不在本单做)

  1. app.form.ts 摘掉这三个 { field: … } 输入,按套件惯例在原位留一行注释说明退役出处;
  2. 排查「手抄 spec 清单 + "keep in sync" 注释」模式:一天内确认三例,全部曾静默漂移 #3786 那条断言的谓词从 key ∈ shape 收紧为 key ∈ shape 且不是 retiredKeyz.never 内层判定即可),这样下一个墓碑键落地时门自己会红 —— 否则同样的漂移会在每一次 retiredKey() 退役后重演;
  3. 顺带扫一遍其余 *.form.ts 是否还有别的墓碑键在被提供(本单没有全量扫)。

第 2 条是这单真正的价值:没有它,第 1 条只是把当前这三个擦掉,门依旧是瞎的。

现场

  • packages/spec/src/ui/app.form.ts(约 51-59 行)
  • packages/spec/src/ui/app.zod.ts:1227 / :1233 / :1240
  • packages/spec/src/system/metadata-form-zod-reconciliation.test.tsevery field the form offers is a key the Zod accepts

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions