从 #4001 批 13 拆出(发现,不是本批修复范围)。影响 #4001 整个战役已经落地的每一条 未知键错误信息的兜底建议质量。
缺陷
packages/spec/src/shared/suggestions.zod.ts 的 findClosestMatches():
const normalized = input . toLowerCase ( ) . replace ( / [ - \s ] / g, '_' ) ;
const scored = candidates
. map ( ( candidate ) => ( {
value : candidate ,
distance : levenshteinDistance ( normalized , candidate ) ,
} ) )
输入被小写化,候选没有。 于是候选键里的每一个大写字母都要额外付一次替换代价。而 strictUnknownKeyError 给的预算是长度相对的 Math.max(2, Math.floor(key.length / 3)) —— 对短键就是 2。两者一叠加,短的 camelCase 键上一个再普通不过的笔误就够不着了。
按 AGENTS.md Prime Directive #3 ,「TS config keys → camelCase」是全仓约定,所以受影响的是可授权面的大多数键 。
实测(origin/main fe83042 )
输入 预算 findClosestMatches 结果
hideOn 2 [] ← hiddenOn,真实距离 2,加大写罚分后 3,超预算
hiddenon 2 ["hiddenOn"] ← 同一个词全小写反而给得出建议
hiddenOnn 3 ["hiddenOn"]
maxLenght 3 ["maxLength"]
第二行是这个缺陷的完整刻画:把键写成全小写的作者,拿到的建议比写对了大小写、只错一两个字母的作者更好。
暴露面是「真实距离 + 候选里的大写字母数 超过预算」的那一类,集中在较短的 camelCase 键 上。hideOn → hiddenOn 就是标准样本:一个语义完全正确、只是少了个词尾的键,得到的信息里没有任何指向正确拼法的内容 —— 而这一战役的整个卖点,恰恰是「不只是大声,而是可修」。
建议修法
在同一处对候选做同样的归一化,并且只用于打分 、回显仍用候选的原始拼写:
const norm = ( s : string ) => s . toLowerCase ( ) . replace ( / [ - \s ] / g, '_' ) ;
const normalized = norm ( input ) ;
const scored = candidates
. map ( ( candidate ) => ( { value : candidate , distance : levenshteinDistance ( normalized , norm ( candidate ) ) } ) )
注意两点:
data/object.zod.ts 的 suggestKey 是同一套长度相对上界的另一处实现,要一起检查是否同病。
这会让一批原本没有建议的错误开始给建议,也可能改变个别已有建议的选中项 —— 所以它会动到 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 已落地各批的错误信息文案 ,那些批次里有直接断言 message 内容的 pin 测试(如 webhook.test.ts / etl.test.ts / control-flow 系列)。属于横切改动,应独立成 PR 并跑全 spec 套件,不适合搭在某一批收紧上。
修的时候顺便加一条自证测试:同一个键的全小写形式 不应比正确大小写形式得到更好的建议。这条不变量是本 issue 的实质。
现状缓解
#4001 批 13 在 ui/responsive.zod.ts 里为它自己遇到的那一例写了显式 alias(hideOn: 'hiddenOn'),并在注释里记了测量值。那是逐例绕行,不是修复 —— 每个 schema 都要为兜底够不着的短 camelCase 键手写 alias,正是 strictObject 引入时想消掉的那种重复。
相关
按 AGENTS.md Prime Directive #10 归档,未指派。
从 #4001 批 13 拆出(发现,不是本批修复范围)。影响 #4001 整个战役已经落地的每一条未知键错误信息的兜底建议质量。
缺陷
packages/spec/src/shared/suggestions.zod.ts的findClosestMatches():输入被小写化,候选没有。 于是候选键里的每一个大写字母都要额外付一次替换代价。而
strictUnknownKeyError给的预算是长度相对的Math.max(2, Math.floor(key.length / 3))—— 对短键就是 2。两者一叠加,短的 camelCase 键上一个再普通不过的笔误就够不着了。按 AGENTS.md Prime Directive #3,「TS config keys → camelCase」是全仓约定,所以受影响的是可授权面的大多数键。
实测(
origin/mainfe83042)第二行是这个缺陷的完整刻画:把键写成全小写的作者,拿到的建议比写对了大小写、只错一两个字母的作者更好。
暴露面是「真实距离 + 候选里的大写字母数 超过预算」的那一类,集中在较短的 camelCase 键上。
hideOn→hiddenOn就是标准样本:一个语义完全正确、只是少了个词尾的键,得到的信息里没有任何指向正确拼法的内容 —— 而这一战役的整个卖点,恰恰是「不只是大声,而是可修」。建议修法
在同一处对候选做同样的归一化,并且只用于打分、回显仍用候选的原始拼写:
注意两点:
data/object.zod.ts的suggestKey是同一套长度相对上界的另一处实现,要一起检查是否同病。webhook.test.ts/etl.test.ts/control-flow系列)。属于横切改动,应独立成 PR 并跑全 spec 套件,不适合搭在某一批收紧上。现状缓解
#4001 批 13 在
ui/responsive.zod.ts里为它自己遇到的那一例写了显式 alias(hideOn: 'hiddenOn'),并在注释里记了测量值。那是逐例绕行,不是修复 —— 每个 schema 都要为兜底够不着的短 camelCase 键手写 alias,正是strictObject引入时想消掉的那种重复。相关
ActionParamSchema,这套错误工厂的模板)packages/spec/src/shared/suggestions.zod.ts、packages/spec/src/data/object.zod.ts按 AGENTS.md Prime Directive #10 归档,未指派。