Skip to content

strictObject 的 history 句夹在「哪个键错了」与「该写什么」之间,在单行 error 显示位上把修法推到 222 字符之后(#5762 实测) #5955

Description

@os-zhuang

#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 显示位。

为什么没在 #5762 内修

四条路都不干净,PR #5952 正文已记录:

  1. 消费者侧正则剥离 —— 要在 packages/lint 里重述生产者的消息结构(Unrecognized key(s) on …: …. + history + Did you mean …?),正是 validate-flow-trigger-readiness.ts 模块注释声明要避免的「契约的第二份拷贝」,也违反 contract-first(消费者容忍换不来正确性)。
  2. 按长度截断 —— Did you mean 在末尾,截断恰好切掉最有用的部分,比不截更糟。
  3. 消费者查生产者拿 history 原文 —— 拿不到:strictObjectDeclarations()(shared/strict-object.ts)与 directAliasTables()(shared/alias-table-registry.ts)都刻意不进 barrel,是内部审计接缝,且前者无 schema 身份可匹配。
  4. 生产者侧改渲染 —— 干净,但影响面是 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 直调点迁移 —— 不同关切:机制迁移,非消息版式)。

Activity

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