Skip to content

需要决策:strictness 台账的 Class 词汇缺一个判定 —— "被消费者全面把守的形状片段"(#4001 批 19 / BaseNavItemSchema) #5249

Description

@xuyushun441-sys

来自 #4001 批 19。测量已经做完且无歧义;悬而未决的是用哪个词记录它,而这个词是机读的,所以我没有猜。

测量结果(已完成)

ui/app.zod.ts 的最后一个 strip 站点是 BaseNavItemSchema。台账把它记为 verify,附带的指令是"确认成员的 strictness 是否已经覆盖它"。答案是已经覆盖,而且台账当时的前提错了两处:

  1. 成员不是 .extend() 这个基底,而是 spread ...BaseNavItemSchema.shape 两者机制不同,这个区别正是 finding 16 的全部内容:.extend() 产生的克隆继承基底的 unknown-key 姿态(所以关掉两个 view 授权 schema 会连带关掉 Studio 往返 overlay,把平台自己写的形状变成 422),而 ...shape 只是把逐键 schema 复制进一个全新的 z.object,姿态是新对象自己的。双向实测过,因为"关掉基底会连带关掉成员"和"关掉基底是 no-op"是互斥的断言:

    strictBase.extend({ b }).safeParse({ a:'x', zzz:1 }).success              // false —— 继承
    z.object({ ...strictBase.shape, b }).safeParse({ a:'x', zzz:1 }).success  // true  —— 不继承
    z.object({ ...openBase.shape, b }).strict().safeParse({ a:'x', zzz:1 })   // false —— 姿态是新对象自己的
  2. 九个分支各自已经 .strict() 并带 navItemUnknownKeyError。逐分支通过真实的门(AppSchema.navigation,按 type 判别的 discriminatedUnion)断言过,同一次运行里有正控制(基底贡献的每个键——包括没有任何分支自己声明的 requiresService——都被 ACCEPT)和负控制(未声明键被 REJECT)。

基底还是模块私有且全仓零 .parse().strict() 是 PARSE 的属性,所以关掉它保证是 no-op —— 而 #4583 明确指出 no-op 收紧并非中性("a precisely-validated dead slot is the more convincing lie")。

结论:不改姿态。这部分不需要决策。

需要决策的部分:Class 单元格写什么

台账的 Class机读的(小计是对它做算术),枚举值只有八个:authorable · verify · mixed · split · wire · open · no door · no gate。对这个形状,没有一个是诚实的:

候选 机械上是否匹配 它规定的后续动作 问题
no door ✅ carrier 缺席 + parse 缺席,正是两轴表的定义 ADR-0049 退役 破坏性。这套词汇是活的,九个分支都在带它;退役基底 = 删掉九个分支的共享键。台账自己写着"读错方向,规定的动作不只是浪费而是破坏性的"
no gate ❌ carrier 并非"活着但没 parse" 在 carrier 自己的门上接 parse 已经存在,就在成员上
authorable 收紧它 这正是 reverse-pin 讨论里说的误读:把一个 parse 不到的形状标成"强制范围",等着下一次 sweep 来"把活干完"
verify(当前值) 语义是"待检查" 去做检查 检查已经做完了;继续挂着会让下一个人重做一遍

view.zod.tsFormFieldBaseSchema 是最近的先例,记为 authorable 并保持打开 —— 但那个基底是真的被 .extend(),所以关掉它会改变行为,它是一扇真实的、有意留给消费者的门。BaseNavItemSchema 不是同一个形状。

两个选项

A. 加第九个判定词(例如 covered:carrier 缺席、parse 缺席,但词汇在每个消费者处都被完整把守;后续动作是)。

  • 需要动 packages/spec/scripts/lib/strictness-ledger-doc.tsVERDICTS / BUCKETS / BUCKET_OF,以及计数生成器的分桶。
  • 长期健全性:这是战役里第二次遇到"两轴表返回的词规定了错误动作"——第一次是批 15,当时的答案就是加一个词(no gate),而不是四舍五入到最近的错误答案。同一个问题第二次出现,倾向于说明词汇表确实缺了一格,而不是这个站点特殊。
  • 让 AI 写的元数据更难出错:这一格是给未来的 agent 看的路标no door 会把下一个 agent 指向"退役这个 vocabulary",而那是九个分支共享键的删除操作 —— 这正是"消费者侧宽容"的镜像失败:一个宽松/错误的分类让错误在下游放大。加词是在编写时结构性地阻止误读。
  • 成本:改机读契约,现有行需要复核是否有别的站点其实属于新格(我只测了这一个)。

B. 沿用 FormFieldBaseSchema 先例,记为 authorable 并保持打开,靠散文承载差异。

  • 成本最低,不动契约。
  • 长期代价要说清楚:authorable 的公开含义是"ruling 的强制范围",小计会把它算进"还没做完的活"。战役已经明确记录过,一个停在有意底线上的行与一个没人做完的行,reverse pin 分辨不出,只有 Class 列能分辨——用 authorable 等于主动放弃这个唯一的分辨手段,并把散文当作唯一防线。散文防不住 sweep;这份台账自己就记着这类"把活干完"的事故。

我的建议

A,但两条轴上都要说实话:

  • 长期健全性:批 15 已经证明这个词汇表是可以增长的,而且增长的理由和这次一模一样——两个判定失败于同一个测量(没有 parse)但方向相反,合并它们会把下一批指向恰好错误的动作。这次是第三个方向:没有 parse,但也不需要 parse,因为词汇在每个消费者处都已被把守。把它塞进 no door 会在台账里留下一条指向破坏性动作的记录。
  • 让 AI 写的代码更难出错:这一格的读者主要是后续 agent。选 A 是在分类层面做"声明即强制";选 B 是把正确性推给散文,也就是这份台账反复记录会失效的那一层。

但 A 的成本是真实的,而且只有一个已知实例——如果维护者认为一格判定词不该为单个站点而生,B 是可以接受的,前提是接受上面写明的长期代价。我不替这个取舍下结论。

现状

批 19 不改姿态,也不改 Class 单元格(保持 verify),因为 verify 是唯一一个对结论不发表主张的既有值,并且让小计停在原处 —— 无论最终选哪个,这都是诚实的数字:这个站点既没有关闭,也还没有被重新分类。

测量记录在三处:BaseNavItemSchema 的 JSDoc、packages/spec/src/ui/app-strictness-batch19.test.ts(含机制本身的双向断言,以及一条"任何分支停止拒绝未知键就报错"的守卫——那是唯一会让这个判定需要重取的变化)、以及台账的 ui/ 行。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions