Skip to content

[Decision] zod 升级后 7 条断言变成空转探头 —— 先落地另立卡、把夹具改接真实入口、还是装全局 override? #19730

Description

@os-warren

Filed by the domain:spec execution seat 2, session session_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-22T13:27Z. ⛔ Blocks PR #19658 from being armed — see the re-check commands below.

维护者速读

升 zod 是为了修掉一个真缺陷:错误格式化器碰到 __proto__直接崩。升级本身做完了,原本红的 78 条断言现在绿了 74 条,而且一个断言字都没改

剩下 7 条红的,是探头本身失效了:它们在测试文件里自己搭了一个假零件来喂给格式化器,而新版 zod 下这个假零件已经造不出要测的那种输入 —— 于是这 7 条既不会通过也不再检查任何东西。⛔ 产品代码怎么改都够不着它们,因为那个假零件是测试自己造的。

三条路:A 先落地、这 7 条另立卡(代价:这张 PR 红着进不了队,崩溃修复一直不发布);B 把那两个假零件改接到真实的产品入口上(7 条全绿,而且恢复了它们本来要测的东西,代价是动了测试喂什么、且其中 3 条在另一个车道);C 装一个全局开关绕过(⛔ 本席明确反对,它靠的是 zod 自己标了「非公开 API」的钩子)。

本席荐 B。你只需回一个字母:A / B / C


一句话问题

为了修掉一个会让用户看到崩溃堆栈的缺陷,平台升级了一个依赖;升完之后,有 7 条本该守着「错误信息要指向正确那一支」的测试,变成了空转的摆设 —— 它们既不通过,也不再守着任何东西。

Governing text

「⛔ No test is rewritten to accept the degraded message. The 78 assertions across 17 files (a lower bound) turn green by repairing the diagnosis layer, not by editing what they assert.」
—— 裁决 5774631464(总监席,决策批 #212 item 1,letter A,维护者原话 「19674 反正要升级,建议一步该到位」,2026-09-22T10:05Z)

「The taker declares the file surface; if it crosses packages/cli and packages/spec / scripts, the cli half is a sibling PR by the domain:cli seat, ⛔ not a rider across lanes.
—— 裁决 5644350230 item 4

Prior rulings read: test fixture,invalid_union,lane split,vacuous pin → 0 hits; none; thread: not read (no --card); repo: objectstack-ai/objectstack

协议声明,是否改协议

不改协议。 三条路没有一条改变任何门接受或拒绝什么;争的只是测试拿什么去喂格式化器。接受集在交付轮里逐门实测未动。

前提,带 re-check 命令

git fetch origin main
gh/curl  GET /repos/objectstack-ai/objectstack/pulls/19658           # head、draft、mergeable
curl     GET /repos/objectstack-ai/objectstack/commits/<head>/check-runs

本卡落卡时:PR #19658,head 56ace769d408c3fa46f2528c46efb2c2a02b859d,draft,24 文件 +508/−115。

选项 × 真实代价

做什么 客户可感知的后果
A 现在落地修复,7 条残留另立卡 崩溃修复不发布,直到另一个车道的卡排到并落地。这张 PR 红着,⛔ 进不了合并队列。实测:2 个 Test Core 分片红
B 把两个夹具改指向用 strictObject 建的真实产品入口 7 条全绿,崩溃修复本周可发;而且这两个探头恢复了它们本来在测的东西。代价:动了测试喂什么(⛔ 不是断言什么),且其中 3 条在 packages/cli,踩裁决 item 4 的车道线
C 从 spec 装一个进程级 zod 全局 override 7 条全绿且不动测试。⛔ 本席与交付轮都反对:它靠 zod 自己在 d.ts 里标了 @internal「Not part of the public API」的单槽钩子,一个补丁版就可能静默失效 —— 正是这张卡要修的那类故障;而且一个发布出去的库会在 import 时改写使用方自己无关的 zod schema。全仓零先例(z.config/core.config/setErrorMappackages/*/src 命中 0)

业务含义直译

  • A = 安全带已经做好了,但先放在仓库里不装车,等另一个班组把他们的检测工装修完。
  • B = 把两个已经测不到东西的探头,重新接到真实管路上。
  • C = 为了让仪表盘好看,在别人家的电路上并一根私接线。

四轴,从业务立场

① 项目长远合理性 —— B 缩小特例:两个探头从「空转」回到「真的在守一条契约」。A 不增不减,但在板上留一张红着的 PR 当既成事实。C 扩大特例,而且是最坏的一种:把一条产品级诊断的正确性,押在供应商标注为非公开的钩子上。

② 实际业务拉动 —— 今天撞上的是:任何让错误格式化器碰到 __proto__ 的用户,他看到的是崩溃堆栈而不是错误提示。A 让这个人继续等,等的时间不由这张卡决定,由另一个车道的队列决定。⇒ 拉动是实的且已在发生,不是零拉动。

③ 防 AI 犯错 —— 留着这 7 条空转探头,是静默容忍:绿色的测试套件会让下一个作者以为「格式化器指向正确分支」这条契约有人守着。B 让它重新响亮拒绝⚠️ 注意契约本身并未失守 —— union-author-message-pins.test.ts 走真实产品入口测同一件事且是绿的 —— 所以这 7 条是冗余且已失效,⛔ 不是唯一守卫。

④ 创业阶段不扩散 —— C 是典型的 declare-and-maintain:每一个私有钩子都是永久义务。B 不新增任何声明。A 新增一张卡和一段跨车道协调。

防 AI 犯错轴:出错时谁看到什么

  • 选 A:测试套件绿(那 7 条被移走后),但没有人在守「错误指向正确分支」,直到新卡落地 —— 静默
  • 选 B:夹具回到真实入口,下一次诊断层退化会响亮红在这两个文件上。
  • 选 C:钩子在某个 zod 补丁版里悄悄失效,诊断退回错误分支,测试仍然绿 —— 最坏的静默。

推荐 + 回退 + 置信缺口

推荐 B。回退 A。

理由一句话:这 7 条之所以失效,是这张 PR 自己抬高地板造成的,所以它是这张 PR 的残留而不是另一个车道的独立缺陷;而维护者对这次变更的原话就是「一步到位」。

自检行:只看① 选 B;②③④ 是否翻转:

⚠️ 置信缺口,明说本席看不见什么:交付轮定位的机制(zod 4.5.0 给 unrecognized_keys 加了 continue: true,致使 handleUnionResults 的单非中止短路提前返回、invalid_union 信封整个消失)本席没有第一手验证 —— 共享检出没有 node_modules,而 ⛔ 本席拒绝在共享检出里安装。该读数归属交付轮,已原样交给达档复核在它自己的 worktree 里核。若该机制被证伪,三个选项的前提都要重估。

⛔ 另需你一并裁的一点:B 的 cli 那半(3 条)是否可以随这张 PR 走,还是裁决 5644350230 item 4 的车道切分照样咬。本席读法是「夹具因本 PR 抬地板而失效 ⇒ 属本 PR 残留」,但这是在解释你的裁决,所以不该由本席定。

裁后执行段

  • B ⇒ 本席派第 4 轮:把 packages/spec/src/shared/error-map.test.ts:191 与(若你允许跨车道)packages/cli/test/format-zod-union.test.ts:62 的夹具改指向用 strictObject 建的真实入口,⛔ 断言文字一字不动;绿后重取达档复核档,再入队。若你只放行 spec 那半 ⇒ 同轮只改 spec,cli 立姊妹卡,PR 仍红着等它。
  • A ⇒ 本席立一张卡带这 7 条(查重词已备),PR 保持 draft 不入队,并在卡上写明它等的是哪张。
  • C ⇒ ⛔ 本席会先回一次异议再执行,因为两处独立意见都反对。

相关单与 PR

#19581 · PR #19658(head 56ace769d4)· 裁决 5774631464(批 #212 item 1)· 裁决 5770530634(已在版本前提上被修正)· 已关的决策卡 #19674

os-decision-facets
① 项目长远合理性:B 缩小特例(两个空转探头回到真的守契约);C 扩大特例,把产品级诊断押在供应商标注非公开的钩子上。
② 实际业务拉动:今天就撞上 —— 错误格式化器碰 __proto__ 直接崩的用户;A 让这个修复的发布时间由另一个车道的队列决定。
③ 防 AI 犯错:空转探头是静默容忍(绿套件让下个作者以为有人守着);B 恢复响亮拒绝;C 的失效形态最坏 —— 钩子没了测试仍绿。
④ 创业阶段不扩散:C 是 declare-and-maintain 一个私有钩子的永久义务;B 不新增任何声明;A 新增一张卡与一段跨车道协调。

Prior rulings read: test fixture,invalid_union,lane split,vacuous pin → 0 hits; none; thread: not read (no --card); repo: objectstack-ai/objectstack


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions