Repository navigation
Description
发现于 #4971(formatZodError 展开 invalid_union)的实施过程,未在该 PR 中修复:那单的文件面锁在 packages/spec,而这里是 packages/cli。
现象
#4971 的前提句里有一处需要更正:formatZodError(packages/spec/src/shared/error-map.zod.ts)的 JSDoc 用途确实写着 CLI 输出,但 os validate / os build(compile)并不调用它。三个命令走的是 CLI 自己的那份:
packages/cli/src/commands/validate.ts:101packages/cli/src/commands/compile.ts:162packages/cli/src/commands/plugin/build.ts:114
三处都调 formatZodErrors(复数)—— packages/cli/src/utils/format.ts:167。这个函数只遍历顶层 issue:
const issues = error.issues || (error as any).errors || []; // :168
// ↑ 这个 `.errors` 是 ZodError 级的 zod v3 兼容别名,不是 issue 级的 `issue.errors`
for (const issue of sectionIssues) {
console.log(chalk.red(` ✗ ${path}`));
console.log(chalk.dim(` ${code}: ${msg}`)); // :189-190
}整个函数里没有任何一处读 issue.errors。所以一个失败的 union 在终端上是:
compareTo:
✗ compareTo
invalid_union: Invalid input
分支里那条策展处方(Unrecognized key(s) on this comparison window: ...)照样到不了作者。
与已有两单的关系
同一个缺陷家族的第三个消费者,三份各自独立的代码:
| 消费者 | 文件 | 状态 |
|---|---|---|
formatZodError(spec 公开导出,defineStack 抛错走它) |
packages/spec/src/shared/error-map.zod.ts |
#4971 已修 |
zodIssuesToFields(REST wire) |
packages/rest/src/rest-server.ts |
#5014 待修 |
formatZodErrors(CLI 终端,本单) |
packages/cli/src/utils/format.ts |
未修 |
不是 #5014 的子任务:那单的完成面是 zodIssuesToFields 与 ADR-0114 的 wire 契约(fields[] 条数会变),本单是终端渲染,不同包、无 wire 契约、互不依赖。也不依赖 #4971 —— 但 #4971 已经把分支选择策略写成可复用的实现(丢弃只报「值的种类不对」的分支、按 issue 最少挑最接近的那支、跨分支相同判决去重、绝对路径、深度上限),照抄比重新发明便宜,也能让两条路径的输出口径一致。最省事的形态可能是让 CLI 直接用 spec 的 formatZodIssue。
顺带记下(休眠,不是本单必修)
invalid_key / invalid_element(z.record 的键 schema 失败、map/set 元素失败)同样把真实 issue 挂在 issue.issues 里,上述三个消费者一个都不下降。今天点不着:packages/spec 里 z.record(...) 的键 schema 全是 z.string() 或 enum(enum 键的坏键会走顶层 unrecognized_keys,实测过),所以没有任何 authoring 面能产出 invalid_key。哪天有人写了 z.record(SnakeCaseIdentifierSchema, X),散文就同样消失。
验收建议
os validate对一个 union 分支内的未知键报错时,终端打印出该分支的处方(不是invalid_union: Invalid input)。- 一个未知键不因 N 个分支被报 N 次(未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 批 6c 的回归;formatZodError 把 union 分支的拒绝信息压成 "Invalid input" —— #4001 策展的散文在 CLI 路径上到不了作者 #4971 的
error-map.test.ts有现成的钉)。 --json路径不受影响:它透传error.issues,payload 一直是全的。
Activity
docs(spec): 更正测试头注释里的 CLI 路径归属 (#4971 / #5341)
docs(changeset): 更正 formatZodError 的实际到达面 (#4971 / #5341)
baozhoutao commentedon Aug 5, 2026
🔒 认领:PM 循环第 2 轮(cli 车道)
会话:
session_01VkPSGsX9o17MsGv3Lbxu2w分支:
claude/issue-5341-cli-format-zod-unionWorktree:
objectstack-issue-5341域:
domain:cli文件面:
packages/cli/src/utils/format.ts+ 调用点(validate.ts:101/compile.ts:162/plugin/build.ts:114,按需)+ 测试 + changeset。⛔ 不动packages/spec(若判定「直接复用 spec 的formatZodIssue」是正确形态,以 import 复用,不改 spec 侧)。与同批 #5382(doctor.ts)、#5242(runtime)不相交。执行口径:同族范本已两次落地 —— PR #5342(#4971,spec
formatZodError:分支挑选而非倾倒、丢弃 kind-mismatch 分支、按 issue 最少挑最近分支、跨分支去重、绝对路径、深度上限)与 PR #5362(#5014,RESTzodIssuesToFields第二次复用)。对齐这套策略而不是重新发明;最省形态可能是直接调 spec 已导出的实现,由 dev 按代码实况定。验收按 issue 三条:union 分支处方到达终端、一个未知键不报 N 次(#4001 批 6c 回归)、--json路径不受影响。注意:#5380(刚合并)新增
packages/cli/test/metadata-type-schema-gate.test.ts,其 B 组用例断言 validate 输出含处方文本 —— 你的改动必须让它保持绿(它断言的是顶层 unrecognized_keys 路径,与 union 展开正交,但输出形状变化要核)。同日 churn:packages/cli近日被 #5314 / #5380 / #5381(在队列)触及,以origin/main为准核对行号。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
✅ 验收通过 —— PR #5391,转 ready 挂合并队列(队列等全绿收单)
对实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):
import { formatZodIssue } from '@objectstack/spec',分支挑选策略与 formatZodError 把 union 分支的拒绝信息压成 "Invalid input" —— #4001 策展的散文在 CLI 路径上到不了作者 #4971(PR fix(spec): formatZodError 展开 union 分支的拒绝信息 (#4971) #5342)/ union 分支里的 unknown-key 处方永远到不了作者:zodIssuesToFields只映射顶层 issue,失败的 union 只剩Invalid input#5014(PR fix(rest): zodIssuesToFields 展开 invalid_union,联合分支里的处方到达调用方 (#5014) #5362)完全同源;unionBranchLines()严格增量(union 自身两行照旧、非 union 渲染逐字不变、页脚计数与--json不动)。零 spec 改动,文件面与声明一致(format.ts + 测试 + changeset)。views[].list.sort的direction→order处方);反向验证 6 红 4 绿且 4 绿逐条属「按设计该绿」;test(cli): #5000 前提被证伪 —— build/validate 一直按注册表 schema 解析,补上它缺的那份证据 #5380 pin 测试在正反两轮均绿。合并 fix(cli): 非法 OS_TENANCY_POSTURE 在 serve 最开头被显式拒绝,不再伪装成 AuthPlugin 加载失败 (#5359) #5381 后按 AGENTS.md §10 在合并树复跑整包(73 文件 / 670 用例)。compareTo已被 dashboard widgetcompareTo:三个声明分支在 ADR-0021 dataset 路径上全部无效(两个静默丢弃,一个抛错) #5011 收敛成 plain strict object(有钉),该例今日点不着 —— 缺陷本身不受影响,活标本替换正确。后续读者以 PR 记录为准。�转义)—— 正是.claude/skills/**的 markdown 不被任何门禁扫描 —— check:nul-bytes 只看 JS/TS,check:doc-authoring 的 ROOTS 不含 .claude/ #4890/check:nul-bytes 只扫 NUL(0x00)—— 0x01-0x08 等控制字节不在扫描面,#5140 实测一个 0x01 会从 NUL-only 修复下溜走 #5157 那类坑的现场预防。invalid_key/invalid_element把真实 issue 挂在issue.issues上,union 家族的三个消费者一个都不下降 #5389(未认领、无队列标签,待分诊轮),不随本单关闭而湮没。CI 已完成项全绿,余项由 auto-merge 等待。跟到 MERGED。
Generated by Claude Code
fix(spec): formatZodError 展开 union 分支的拒绝信息 (objectstack-ai#4971) (obj…
fix(cli): formatZodErrors expands invalid_union, the branch prescript…
fix(cli): pass unrecognised `functions` entries through the lowering (o…
docs(audits): the strictness ledger stops asserting a union flattenin…
docs(audits): correct the strictness ledger's fourth union-flattening…
docs(audits): the strictness ledger stops asserting a union flattenin…
docs(audits): correct the strictness ledger's fourth union-flattening…
test(cli): repoint the #5341 union specimen at a door that is still a…
test(cli): re-point the union specimen at a door that is still a z.union
test(cli): repoint the objectstack-ai#5341 union specimen at a door t…