Skip to content

spec 门禁盲区:可作者化 key 的「默认值 / 约束」变更不被任何 gate、tombstone 或 conversion 记录(#4650 / #4659 同族) #4666

Description

@os-zhuang

从 #4661(#4535 C8 RetryPolicy 收敛)里分出来的独立发现,已实证,不是推测。与 #4650(基线可手编)、#4659(检查 (b) 按 leaf name 匹配)是同一族:门禁看起来绿,但它没在看你以为它在看的东西。

结论

packages/spec/scripts/build-schemas.ts 的可作者化面门禁 只比较 key 的集合。同一个 key 的 默认值 (.default()) 和 约束 (.min() / .max() / .positive()) 变更,对它完全不可见 —— 而这类变更恰恰能静默改变已部署元数据的运行时行为。

三条记录通道全部漏掉它:

通道 是否记录默认值变更
authorable-surface.json 比对(检查 (a) vanish) ❌ 只有 key 名
retiredKey() tombstone(检查 (b)) ❌ 只在 live → retired 时触发
spec-changes.json / upgrade guide(ADR-0087 D4) ❌ 是 conversion + migration registry 的投影,而默认值变更不必然带 conversion

也就是说:一个 PR 可以把 job.retryPolicy.maxRetries 的默认值从 3 改成 0(让所有依赖默认值的存量 job 静默停止重试),而全部门禁绿、基线不动、没有任何一行 tombstone 或 conversion 记录这件事。

实证(#4661 的 sabotage 验证)

在 #4661 的分支上,把 shared/retry-policy.zod.ts 的 maxRetries 默认值从 0 改成 3,只改这一个字符,然后跑门禁:

$ pnpm check:authorable-surface

✅ Generated bundled schema: objectstack.json (1686 definitions)
✅ Successfully generated 1703 schemas.

绿。 同一处改动被 #4661 手写的运行时 pin 抓住了:

$ npx vitest run src/shared/retry-policy.test.ts

 × pins the opt-in defaults that no gate can observe
 FAIL  ... > pins the opt-in defaults that no gate can observe
 AssertionError: expected 3 to be +0 // Object.is equality

即:今天唯一能挡住这类变更的,是作者自己想起来手写一个 pin 测试。 没有任何机制强制它存在。

为什么这比听上去更糟

  1. 它专挑最危险的语义。 默认值决定「作者没写这个 key 时会发生什么」。retry 次数、超时、enabled、required、各种 allow* —— 这些的默认值翻转就是安全/可靠性事件,而且是静默的。
  2. AI 写的元数据大量依赖默认值。 省略 key 是 LLM 生成元数据的常态,所以默认值的实际覆盖面远大于人手写的时代。
  3. 约束收紧同理。 #4661 把 maxRetries 加了 max(10)、backoffMultiplier 加了 min(1),存量 maxRetries: 20 的 job 会硬拒。硬拒是响亮的、可接受的,但门禁一样没记录 —— 是人写了 semantic 迁移条目才留下痕迹。
  4. ADR-0087 的机器不建模它。 D2 conversion 的 ConversionApplication 是 {from, to, path} —— 描述的是值的重写,没有「这个 key 的默认语义变了」的表达形式。

可能的处置方向(未裁决,列出来供讨论)

  • A. 把默认值/约束纳入 authorable-surface.json 的比对。 每个 key 除了名字再记一个形状指纹(默认值 + 约束的规范化摘要)。变更即失败,要求显式确认。
  • B. 只对「默认值存在性/取值」做指纹,不管约束。 更窄、更少噪音,覆盖最危险的那一类。
  • C. 不改门禁,改流程:要求任何 .default() 变更必须带 semantic 迁移条目,用 lint 规则(AST)检查 diff。
    • 好处:噪音低。代价:靠 diff 检测,git 语境下比 ratchet 脆。
  • D. 什么都不做,只把「默认值变更必须写进 changeset」写进 AGENTS.md。 最便宜,但正是本 issue 想指出的「靠自觉」。

倾向 A 或 B —— 与 #4650 要加的可达性窄例外是同一批门禁加固,可以一起做。但选哪个取决于维护者对「ratchet 噪音 vs 覆盖面」的偏好,所以不预设。

关联

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    Collaborator

    分诊(2026-08-03,PM 会话 session_01Ehu85kbvMcrNTUJjwxvLJ9):入队,走 A(默认值 + 约束都纳入指纹),PM 决定 + 维护者否决窗口 —— 门禁设计属验证策略,不升级。

    前提复核(以 origin/main 实测):build-schemas.ts 仍无任何 fingerprint/default 追踪;authorable-surface.json 仍只记裸 key 名 —— 盲区原样存在。关联项状态更新:#4650 已关(基线删行合法性门禁已落地),正文「与 #4650 可一起做」的联动过时,本单独立成篇;#4650 的建议 (2)(收窄收集面)未见立项,若本单实现时噪音超预期,那是它的活,不归本单扩权。

    A 而非 B 的两轴理由:

    排期:非线协议 breaking(门禁工具),不占 v17 窗口位,窗口关键批次之后排。


    Generated by Claude Code

  2. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    疑似域误标:本单要补的是「默认值 / 约束变更」的 gate / tombstone / conversion 记录面——落点是门禁与生成器(C 包 #5163 文件面),不是 schema 本体,建议改标 domain:spec-tooling ——交分诊座位定夺。背景:维护者 2026-08-06 拍板 spec 车道缩盘方案,见 #5837。


    Generated by Claude Code

  3. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    标签落实(维护者 2026-08-06 指示,session_01LeEfA7CFwbJb7JJmXm2KM3):按 08-06 转席建议执行 domain:spec → domain:spec-tooling(要补的是「默认值/约束变更」的 gate / tombstone / conversion 记录面,落点门禁与生成器)。


    Generated by Claude Code

  4. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    ContributorAuthor

    维护者裁决(2026-08-07,经 domain:spec-tooling 座位 PM 呈报):方向 B —— 只对默认值做指纹。

    即:每个可作者化 key 除名字外,再记一个**「默认值存在性 + 取值」**的规范化指纹,变更即红。约束(.min() / .max() / .positive())不纳入。

    裁决理由(呈报时给出,维护者采纳)

    关键是一个不对称:

    • 约束收紧会硬拒。 存量 maxRetries: 20 的 job 会被明确报错拒绝 —— 响亮、看得见、可诊断。本 issue 自己也写了「硬拒是响亮的、可接受的」。
    • 只有默认值翻转是静默的。 客户收不到任何报错,只会发现「怎么不重试了」。这是从客户事故发现、而不是从 CI 发现的那一类。

    方向 A 覆盖面最全,但代价是 authorable-surface 明显变大变吵,而 #4535 §1 已经在抱怨该文件过度收集;每次微调 .describe() 或约束都要走一次「已确认变更」出口。B 用 A 的一小部分噪音,精确盖住危险的那一半。

    方向 C(AST 查 diff)噪音最低,但靠 diff 判断在 git 语境下脆(变基、压缩提交都能骗过它);方向 D 正是本 issue 要指出的「靠自觉」,不解决机制问题。

    ⏸ 暂不派发的原因(纯文件面串行,不是优先级)

    本单落点是 packages/spec/scripts/build-schemas.ts,而 PR #6309(#5847,锚点漂移提示方向)此刻正在该文件上。同文件两单并行会互埋 diff,故本单排在 #6309 之后。#5475(把 scripts/** 并进 tsconfig,含 build-schemas.ts 的 6 条 code-tier never 真错)同样在这条串行链上。

    派发时会带上的额外要求(先记在这里,免得漏):


    Generated by Claude Code

  5. self-assigned this
    on Aug 7, 2026
  6. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    ContributorAuthor

    认领(os-dev 开发座位,session session_014wsZeReNTqiceBfLb5Pyf5,分支 claude/issue-4666-default-value-fingerprint)。

    按维护者 2026-08-07 裁决执行方向 B —— 只对默认值做指纹,约束不纳入。开工前先在当前 origin/main 上复核前提(build-schemas.ts 今天已被 #6200/#6256/#6309 移动过三次,正文行号已过期)。


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions