观察类发现,来自 #5285(PR 中新增 FilterArray 声明时反向验证撞上)。不是回归,今天没有用户会踩到;记录的是一类断言的可信度问题。
事实
packages/spec/tsconfig.json 的 exclude 含 ** + /*.test.ts,并在 scripts/check-type-check-coverage.mjs 的 TEST_DEBT 里有实测条目(@objectstack/spec: 272 个测试文件 / 902 个错误)。所以 pnpm --filter @objectstack/spec typecheck 从不读取任何测试文件。
TEST_DEBT 记录的是「隐藏了 902 个错误」——假阴性。但同一个排除还有第二种、性质不同的后果,账本没有覆盖:
它静默地解除了 @ts-expect-error 这类「负向断言」的武装。
一个 @ts-expect-error 的全部价值就在编译期。当文件根本不被 tsc 读取时,它既不会因为「该错误消失了」而报 TS2578,也不会以任何方式失败——测试照常全绿,而它声称钉住的契约一行都没被检查。这与「隐藏了已有错误」是相反的失败方向:那些是真 bug 被藏起来,这些是假装存在的检查。
计量
packages/spec/src/ 测试层共 17 处 @ts-expect-error,分布在 5 个文件:
未逐一核实前 4 个文件里每一处当前是否仍然成立——那正是问题所在:没有任何东西在核实它们。
复现(在 #5285 分支上实测)
在 packages/spec/ 下建一个继承自本包 tsconfig、"exclude": []、只 include 单个测试文件的临时配置:
npx tsc -p tsconfig.typetest.tmp.json
- 排除解除后:
exit 0,两处 @ts-expect-error 均为「活的」;
- 把
AST_OPERATOR_MAP 的 satisfies 改回 Record< string, string > 注解(即把算子类型放宽回 string)后:报出且仅报出 src/data/filter-array-declaration.test.ts(215,5): error TS2578: Unused '@ts-expect-error' directive.
同一改动在 pnpm --filter @objectstack/spec typecheck 下全绿——这就是幻影。
为什么值得单独记一笔
TEST_DEBT 的注记把这批包描述为「隐藏的是测试层,而 #4311 正是在测试层发现缺陷的」,读起来像是纯粹的「欠债规模」问题,毕业即可清偿。但对负向断言而言,后果不是「错误暂时看不见」,而是一个写下来就永远不会失败的检查——它会被后来的读者(以及 agent)当作既有保护读,这正是仓规反复付代价的 declared ≠ enforced 形状,只不过这次落在测试基础设施上。
可能的处理方向(未定调,交 PM 分诊)
- 随
TEST_DEBT 毕业自然消解 —— 最干净,但 902 个错误的清理不是小工程,期间幻影一直存在;
- 把负向断言挪到已被类型检查的包(spec 的类型可从任意包 import),代价是契约与其断言不同居;
- 给 spec 加一个只含
@ts-expect-error 测试文件的窄 tsconfig 门禁(check:type-level-pins 之类),先把这一类断言救回来,与 TEST_DEBT 的整体毕业解耦——增量成本最小;
- 若判定不值得,至少让排除本身变响亮:在
TEST_DEBT 注记里写明「本包内的 @ts-expect-error 不生效」,使下一个作者不会误读。
倾向 3 或 4:两者都不要求先还清 902 个错误,而 3 恰好把「负向断言」这一最容易被误读为已验证的子集单独救出来。请 PM 分诊定级。
Refs: #5285(发现现场 + 手工验证)、#4311(类型检查覆盖率棘轮的由来)、scripts/check-type-check-coverage.mjs 的 TEST_DEBT。
由 #5285 的 os-dev 会话 session_01ErbEDVAg1No9gdg1pgDAGB 按 Prime Directive #10 立单,不认领。
观察类发现,来自 #5285(PR 中新增
FilterArray声明时反向验证撞上)。不是回归,今天没有用户会踩到;记录的是一类断言的可信度问题。事实
packages/spec/tsconfig.json的exclude含**+/*.test.ts,并在scripts/check-type-check-coverage.mjs的TEST_DEBT里有实测条目(@objectstack/spec: 272 个测试文件 / 902 个错误)。所以pnpm --filter @objectstack/spec typecheck从不读取任何测试文件。TEST_DEBT记录的是「隐藏了 902 个错误」——假阴性。但同一个排除还有第二种、性质不同的后果,账本没有覆盖:一个
@ts-expect-error的全部价值就在编译期。当文件根本不被tsc读取时,它既不会因为「该错误消失了」而报 TS2578,也不会以任何方式失败——测试照常全绿,而它声称钉住的契约一行都没被检查。这与「隐藏了已有错误」是相反的失败方向:那些是真 bug 被藏起来,这些是假装存在的检查。计量
packages/spec/src/测试层共 17 处@ts-expect-error,分布在 5 个文件:data/object.test.tsdata/hook.test.tssystem/translation-typegen.test.tscontracts/sharing-service.test.tsdata/filter-array-declaration.test.ts([spec] 声明FilterArray为仅输入的授权糖(#5158 拍板 C 第 1 步,拆单移交 spec 车道) #5285 新增,已在文件内显式标注「CI 不做类型检查」并附手工验证记录)未逐一核实前 4 个文件里每一处当前是否仍然成立——那正是问题所在:没有任何东西在核实它们。
复现(在 #5285 分支上实测)
在
packages/spec/下建一个继承自本包 tsconfig、"exclude": []、只 include 单个测试文件的临时配置:exit 0,两处@ts-expect-error均为「活的」;AST_OPERATOR_MAP的satisfies改回Record< string, string >注解(即把算子类型放宽回string)后:报出且仅报出src/data/filter-array-declaration.test.ts(215,5): error TS2578: Unused '@ts-expect-error' directive.同一改动在
pnpm --filter @objectstack/spec typecheck下全绿——这就是幻影。为什么值得单独记一笔
TEST_DEBT的注记把这批包描述为「隐藏的是测试层,而 #4311 正是在测试层发现缺陷的」,读起来像是纯粹的「欠债规模」问题,毕业即可清偿。但对负向断言而言,后果不是「错误暂时看不见」,而是一个写下来就永远不会失败的检查——它会被后来的读者(以及 agent)当作既有保护读,这正是仓规反复付代价的 declared ≠ enforced 形状,只不过这次落在测试基础设施上。可能的处理方向(未定调,交 PM 分诊)
TEST_DEBT毕业自然消解 —— 最干净,但 902 个错误的清理不是小工程,期间幻影一直存在;@ts-expect-error测试文件的窄 tsconfig 门禁(check:type-level-pins之类),先把这一类断言救回来,与TEST_DEBT的整体毕业解耦——增量成本最小;TEST_DEBT注记里写明「本包内的@ts-expect-error不生效」,使下一个作者不会误读。倾向 3 或 4:两者都不要求先还清 902 个错误,而 3 恰好把「负向断言」这一最容易被误读为已验证的子集单独救出来。请 PM 分诊定级。
Refs: #5285(发现现场 + 手工验证)、#4311(类型检查覆盖率棘轮的由来)、
scripts/check-type-check-coverage.mjs的TEST_DEBT。由 #5285 的 os-dev 会话
session_01ErbEDVAg1No9gdg1pgDAGB按 Prime Directive #10 立单,不认领。