Repository navigation
需要决策:strictness 台账的 Class 词汇缺一个判定 —— "被消费者全面把守的形状片段"(#4001 批 19 / BaseNavItemSchema) #5249
Description
来自 #4001 批 19。测量已经做完且无歧义;悬而未决的是用哪个词记录它,而这个词是机读的,所以我没有猜。
测量结果(已完成)
ui/app.zod.ts 的最后一个 strip 站点是 BaseNavItemSchema。台账把它记为 verify,附带的指令是"确认成员的 strictness 是否已经覆盖它"。答案是已经覆盖,而且台账当时的前提错了两处:
-
成员不是
.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 —— 姿态是新对象自己的
-
九个分支各自已经
.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.ts 的 FormFieldBaseSchema 是最近的先例,记为 authorable 并保持打开 —— 但那个基底是真的被 .extend() 的,所以关掉它会改变行为,它是一扇真实的、有意留给消费者的门。BaseNavItemSchema 不是同一个形状。
两个选项
A. 加第九个判定词(例如 covered:carrier 缺席、parse 缺席,但词汇在每个消费者处都被完整把守;后续动作是无)。
- 需要动
packages/spec/scripts/lib/strictness-ledger-doc.ts的VERDICTS/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/ 行。
Activity
docs(spec): 把批 19/20 的两个保留判定接到已归档的 issue 上(#5247/#5248/#5249)
os-zhuang commentedon Aug 5, 2026
分诊结论
分类:
needs-user-decision领域标签:
domain:spec复核
origin/main(HEAD28ad90e),核对了事实前提仍然成立:packages/spec/src/ui/app.zod.ts中BaseNavItemSchema(line 179)确实是模块私有基底,九个导航分支各自...BaseNavItemSchema.shape展开后自带.strict()(line 269/340/362/373/383/412/425 等),不是.extend()继承。packages/spec/scripts/lib/strictness-ledger-doc.ts的VERDICTS(line 62-71)目前仍是原有八个值,且其 JSDoc 明确把verify归入authorable桶,理由写的正是BaseNavItemSchema这一站点本身("held pending the finding-16.extend()check")——即代码里的现状说明与 issue 描述完全一致,决策尚未做出,前提未过期。分类理由
这是一个协议/机读契约的语义决策,不是可直接执行的代码修复:issue 作者已经完成全部测量(spread vs extend 的双向实测、九个分支的正负控制断言),结论"不改姿态"没有争议;唯一悬而未决的是台账
Class单元格该记哪个词——这个词汇表是packages/spec/scripts/lib/strictness-ledger-doc.ts里机读的枚举契约,新增第九个判定词(方案 A:covered)还是沿用authorable先例(方案 B)会影响后续所有类似站点的分类和下游 agent 读到台账后采取的动作。这类"改机读契约 vs 接受既有词汇的长期代价"的取舍,作者本人在 issue 里明确写了"我不替这个取舍下结论",需要维护者拍板,因此归为needs-user-decision。领域标签理由
无论最终选 A 还是 B,改动点都落在
packages/spec/scripts/lib/strictness-ledger-doc.ts(VERDICTS/BUCKETS/BUCKET_OF及生成器分桶逻辑)以及docs/audits/2026-07-unknown-key-strictness-ledger.md台账本身,两者都在packages/spec/**之下,属于 spec 词汇/schema 决策本身,故打domain:spec(而非domain:spec-tooling——这不是工具链本身出错,而是它承载的机读词汇表语义是否完整的决策)。去重/阻塞/是否已解决
不重复、不阻塞、未过期。未发现需要
pm:blocked或已被合并 PR 解决的情况。跨领域
不需要拆分,单一决策点、单一落地文件族。
「本评论来自分诊座位 Routine(#5474 试点),不构成认领」
Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
维护者裁决(2026-08-06,经本会话转达):取 A —— strictness 台账新增第九个判定词
covered(carrier 缺席、parse 缺席,但词汇在每个消费者处被完整把守;后续动作:无)。实施要点:改packages/spec/scripts/lib/strictness-ledger-doc.ts的VERDICTS/BUCKETS/BUCKET_OF与计数分桶;BaseNavItemSchema行verify→covered;复核既有行中是否还有同格站点一并改判;.counts.md走gen:strictness-ledger整体重算;词表变更在台账头部散文记一行出处(本单)。needs-user-decision→pm:queue,归 spec 座位(strictness 台账单一所有者面)。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
feat(spec)!: object 内层块拒绝未知键 —— 13 站点收紧,2 个站点依据实测保留 (objectstack-ai#4001
os-zhuang commentedon Aug 6, 2026
分诊级别建议:本单核心是词汇/判定裁决而非既定缺陷修复,派发前需要维护者拍板——建议摘
pm:queue换needs-user-decision进决策箱,避免 dev 空转。交分诊座位定夺。背景:维护者 2026-08-06 拍板 spec 车道缩盘方案,见 #5837。Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
标签落实(维护者 2026-08-06 指示,session_01LeEfA7CFwbJb7JJmXm2KM3):按 08-06 降级建议执行
pm:queue→needs-user-decision——本单标题即「需要决策」(strictness 台账 Class 词汇判定),无裁决派谁都空转,进决策箱等拍板。Generated by Claude Code