#5762 把 flow-time-relative-descriptor-invalid 从 warning 升为 error 时实测出来的,记录防丢。未指派,交分诊定级。 本条是 #5762 第三问(「zod history 句在 error 显示位的噪音」)里未在该 PR 内解决的那一半 —— 裁定允许折叠,但干净的修法不在那个 PR 的文件面内(见下)。
测量
strictUnknownKeyError 的消息结构是(packages/spec/src/shared/suggestions.zod.ts:320):
Unrecognized key(s) on <surface>: <keys>. <history> ← 拼在这里
[ Did you mean `k` → `canonical`? ] ← 修法在最后
[ \n • <guidance 逐条> ]
history 是每个 surface 自己声明的一句沿革。以 TimeRelativeTriggerSchema 为例,它是 222 字符:
Until #4001 these were dropped silently — the descriptor still parsed and the sweep still bound, so a mis-spelled window or filter produced a trigger that matched nothing (or everything) while reporting itself as configured.
实测 validateFlowTriggerReadiness 转发后的 finding 全长(#5496 那个真实描述符,三个 zod issue):
| 用例 |
message 长度 |
#5496 原始描述符(field + 缺 dateField + 标量 offsetDays) |
709 |
单个拼错键(offsetDay) |
595 |
命中 guidance 的键(schedule) |
779 |
| 无未知键(只违反「恰好其一」) |
316 |
对比第 1 行与第 4 行:history 句是这条 finding 近 1/3 的长度,且它的位置在「field 错了」与「Did you mean field → dateField?」之间。
为什么现在才碍事
CLI 把 finding 打成单行(packages/cli/src/commands/validate.ts:141):
console.log(` • ${f.where}: ${f.message}`);
在 warning 块里,读者可以整段跳过。#5762 之后这条规则进入 error 块,作者必须据此动手 —— 而修法(Did you mean …)落在 709 字符的最末。history 讲的是「#4001 之前会怎样静默失败」,对正在修的这个人没有动作价值。
history 的设计初衷是好的:让「以前会静默剥离」这件事可追溯。问题只在渲染顺序与消费位置 —— 它被拼在消息中段,而消费方之一是单行 error 显示位。
四条路都不干净,PR #5952 正文已记录:
- 消费者侧正则剥离 —— 要在
packages/lint 里重述生产者的消息结构(Unrecognized key(s) on …: …. + history + Did you mean …?),正是 validate-flow-trigger-readiness.ts 模块注释声明要避免的「契约的第二份拷贝」,也违反 contract-first(消费者容忍换不来正确性)。
- 按长度截断 ——
Did you mean 在末尾,截断恰好切掉最有用的部分,比不截更糟。
- 消费者查生产者拿 history 原文 —— 拿不到:
strictObjectDeclarations()(shared/strict-object.ts)与 directAliasTables()(shared/alias-table-registry.ts)都刻意不进 barrel,是内部审计接缝,且前者无 schema 身份可匹配。
- 生产者侧改渲染 —— 干净,但影响面是 spec 的授权错误契约对外变更:全仓 62 个
strictObject 站点 / 293 处 history: 声明,以及跨包钉死这些文案的测试。属独立 PR + 独立拍板。
候选方向(供分诊/拍板)
- A. history 排到消息末尾(
… keys. Did you mean …? <history>)。最小改动、修法前置,信息一句不丢。代价:所有钉死完整消息顺序的测试要跟着改。
- B. history 只在
guidance/rename 都无话可说时才拼。噪音只在「有明确修法」时消失,但规则变得依赖分支、可解释性下降。
- C. 结构化返回,由消费方决定渲染(单行位丢 history,完整报告位保留)。最正确也最贵:zod 的 error map 只能返回字符串,要另开通道。
- D. 不改,承认单行 error 位不适合承载沿革,由消费者改用多行渲染(CLI 把 message 折行/分段)。改的是
packages/cli 而非 spec,影响所有 finding。
倾向 A —— 它不删任何信息(#5762 裁定的「保留原始信息可达路径」自动满足),只把「对正在修的人有动作价值的部分」排到前面,且是四条里唯一既不新增分支也不新增通道的。
关联:#5762(实测出处)、PR #5952(记录了完整推理)、#4001(history 机制的来源)、#5593(strictUnknownKeyError 直调点迁移 —— 不同关切:机制迁移,非消息版式)。
#5762 把
flow-time-relative-descriptor-invalid从 warning 升为 error 时实测出来的,记录防丢。未指派,交分诊定级。 本条是 #5762 第三问(「zod history 句在 error 显示位的噪音」)里未在该 PR 内解决的那一半 —— 裁定允许折叠,但干净的修法不在那个 PR 的文件面内(见下)。测量
strictUnknownKeyError的消息结构是(packages/spec/src/shared/suggestions.zod.ts:320):history是每个 surface 自己声明的一句沿革。以TimeRelativeTriggerSchema为例,它是 222 字符:实测
validateFlowTriggerReadiness转发后的 finding 全长(#5496 那个真实描述符,三个 zod issue):field+ 缺dateField+ 标量offsetDays)offsetDay)schedule)对比第 1 行与第 4 行:history 句是这条 finding 近 1/3 的长度,且它的位置在「
field错了」与「Did you mean field → dateField?」之间。为什么现在才碍事
CLI 把 finding 打成单行(
packages/cli/src/commands/validate.ts:141):在 warning 块里,读者可以整段跳过。#5762 之后这条规则进入 error 块,作者必须据此动手 —— 而修法(
Did you mean …)落在 709 字符的最末。history 讲的是「#4001 之前会怎样静默失败」,对正在修的这个人没有动作价值。history的设计初衷是好的:让「以前会静默剥离」这件事可追溯。问题只在渲染顺序与消费位置 —— 它被拼在消息中段,而消费方之一是单行 error 显示位。为什么没在 #5762 内修
四条路都不干净,PR #5952 正文已记录:
packages/lint里重述生产者的消息结构(Unrecognized key(s) on …: ….+ history +Did you mean …?),正是validate-flow-trigger-readiness.ts模块注释声明要避免的「契约的第二份拷贝」,也违反 contract-first(消费者容忍换不来正确性)。Did you mean在末尾,截断恰好切掉最有用的部分,比不截更糟。strictObjectDeclarations()(shared/strict-object.ts)与directAliasTables()(shared/alias-table-registry.ts)都刻意不进 barrel,是内部审计接缝,且前者无 schema 身份可匹配。strictObject站点 / 293 处history:声明,以及跨包钉死这些文案的测试。属独立 PR + 独立拍板。候选方向(供分诊/拍板)
… keys. Did you mean …? <history>)。最小改动、修法前置,信息一句不丢。代价:所有钉死完整消息顺序的测试要跟着改。guidance/rename 都无话可说时才拼。噪音只在「有明确修法」时消失,但规则变得依赖分支、可解释性下降。packages/cli而非 spec,影响所有 finding。倾向 A —— 它不删任何信息(#5762 裁定的「保留原始信息可达路径」自动满足),只把「对正在修的人有动作价值的部分」排到前面,且是四条里唯一既不新增分支也不新增通道的。
关联:#5762(实测出处)、PR #5952(记录了完整推理)、#4001(history 机制的来源)、#5593(
strictUnknownKeyError直调点迁移 —— 不同关切:机制迁移,非消息版式)。