Skip to content

幽灵 @ts-expect-error 不止 spec:@objectstack/client 也有 1 处落在 tsconfig 排除区内(全仓横扫结果) #5449

Closed

Description

@os-zhuang

Blocked-by: #5286

在 #5286 的横扫环节发现(该单只授权 packages/spec 文件面,故此处只记录不修)。基线 origin/main @ 39396bd。

事实

按 pnpm-workspace.yaml 的全部 glob 枚举 77 个 workspace 包,逐包读 package.json.scripts.typecheck 与 tsconfig.json.exclude,并统计其测试文件里的 @ts-expect-error 指令数:

指标 值
workspace 包总数 77
tsconfig.json 排除自身测试文件 24
其中同时声明了 typecheck 脚本(即绿灯但没读测试) 23
完全没有 typecheck 脚本 15
落在排除区内、因而从未被求值的 @ts-expect-error 18

18 处的分布:

@objectstack/spec      17 处 / 5 文件   <- #5286 的范围
@objectstack/client     1 处 / 1 文件   <- 本单

具体位置:packages/client/src/client.test.ts:1283

// @ts-expect-error — empty string rejected at runtime

packages/client/tsconfig.json 的 exclude 为 ["node_modules", "dist", "**/*.test.ts"],而 typecheck 脚本是裸 tsc --noEmit(读同一份 tsconfig),vitest 也不做类型检查 —— 与 #5286 完全同一根因,只是换了个包。

对照组(exclude 不含测试、指令真的会被求值,无需处理):@objectstack/cli(3 处)、@objectstack/types(2 处)、@objectstack/metadata-core(1 处)、@objectstack/service-cluster-redis(1 处)。

为什么这不是 #4311 的重复

scripts/check-type-check-coverage.mjs 的 TEST_DEBT 已经记录了「这些包的测试没被 tsc 读」这一事实(@objectstack/client、@objectstack/rest、@objectstack/runtime 都在册),那是 #4311 的量化欠账。

本单记录的是另一层:那片盲区里躺着 18 条读起来像门禁的断言。台账说的是「测试没被检查」,没有任何东西说「这些已声明的 pin 是空转的」—— 而 pin 的作者与后来的读者都会当它们是有效守卫。这正是 #5286 命名的 phantom check 类。台账条目消化完之前,这 18 条一直是假绿。

处置

取决于 #5286 为 packages/spec 定下的机制(tsconfig.test.json 双跑 / vitest typecheck / 改写为运行时断言),@objectstack/client 这 1 处按同一机制跟进即可,不宜独立发明第二套。故标 Blocked-by。

顺带:若 #5286 最终引入「测试文件必须进 tsc」的门禁,建议同时加一条跨包不变式 —— 任何携带 @ts-expect-error 的测试文件都不得落在该包 tsc 的排除区内。有了它,这一类幽灵 pin 就无法再新增,而不必等 24 个包的存量欠账全部还清。

关联:#5286、#4642(更早的同根因单)、#4311(类型检查覆盖台账)。

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    分诊结论:pm:blocked + domain:cli

    前提复核(origin/main 最新 fetch):packages/client/tsconfig.json 的 exclude 仍为 ["node_modules", "dist", "**/*.test.ts"],package.json.scripts.typecheck 仍是裸 tsc --noEmit,packages/client/src/client.test.ts 中 // @ts-expect-error — empty string rejected at runtime 一行仍原样存在。前提未过时。

    去重结果:本单的阻塞项 #5286 已有在飞 PR —— #5478(draft,Fixes #5286)。该 PR 的正文明确把本单的处置点名延后:

    packages/client/src/client.test.ts(tsconfig 排除测试;留给 #5449)

    即 #5478 只把该文件 seed 进新增的 PHANTOM_PIN_DEBT 台账作为已知条目,并没有在该 PR 里修复它,机制留待 #5286 定案(tsconfig.test.json 双跑方案,即 ruling A1)落地后本单再按同一套跟进 —— 与本单正文的「不宜独立发明第二套」自述处置一致。因此本单目前不适合入队并发调度,避免与 #5286/#5478 的收尾产生实现冲突或返工。

    Domain:修复落点是 packages/client/src/client.test.ts 与 packages/client/tsconfig.json,packages/client 在 domain 映射表中属于 domain:cli。

    Blocked-by: objectstack-ai/objectstack#5286(当前经由 draft PR #5478 推进中)。待 #5286/#5478 合入后应解除 pm:blocked 并转 pm:queue。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. added
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 5, 2026
  3. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 5, 2026
  4. self-assigned this
    on Aug 5, 2026
  5. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    解锁 + 认领(PM 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)

    Blocker #5286 已于 16:00Z closed(completed,经 PR #5478 落地)。#5478 定下的机制即本单要跟的:sibling tsconfig.test.json(build config 的排除保留,ci.yml 的「产物无测试文件」gate 不动)+ check:test-typecheck 挂进 typecheck 链 + test-typecheck-debt.json EXACT ratchet + check-type-check-coverage.mjs 的 TESTS_COVERED 毕业逻辑(它已会读 typecheck 链上的全部 tsconfig,无需再改)。


    Generated by Claude Code

  6. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    开工跟进(os-dev 会话 session_016FNvXhtSdnEGEfLEsMmvxh 派发,cli 车道)


    Generated by Claude Code

  7. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    实现完成 → draft PR #5546(会话 session_016FNvXhtSdnEGEfLEsMmvxh,分支 claude/issue-5449-client-ghost-pin)

    前提复核成立(最新 origin/main @ 9894a723e 两态皆绿 = 该指令从未被求值)。

    一条需要维护者注意的方向修正:派发单预判「修好后删掉指令行应红 TS2578」在本单不成立,而这正是本单的核心发现。project(environmentId: string) 接受 '' —— 合法 string —— 该指令从来没有东西可压制,第一次被编译报的就是 TS2578: Unused '@ts-expect-error' directive。所以正确的修法是删掉该指令(它自己的注释已说明测试真正证明的是运行时 guard),反向验证相应地是镜像的:把指令行加回去才应该红,已先判后跑并实测通过。详见 PR 正文。

    按 #5478 的先例:sibling tsconfig.test.json(build 的 exclude 保留)、typecheck 挂链、残留进 EXACT ratchet 台账、两处台账毕业(TEST_DEBT + PHANTOM_PIN_DEBT seed,均由闸门强制)。为落实「不要发明第二套」,check-test-typecheck.mts 从 packages/spec/scripts/ 提升为共享脚本(--package 指定包),而不是复制一份进 client;spec 的台账与计数一字未动,已实跑确认。

    未做正文「跨包不变式」建议(越界)。界外发现已查重后新立,无标签无指派:#5543、#5544、#5545。

    Changeset:纯测试层 + 工装,按 pr-automation.yml 首选路径打了 skip-changeset 标签(同 PR #5478 先例),未塞空 changeset。


    Generated by Claude Code

  8. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    ACCEPT → PR #5546。(PM 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)

    验收要点:

    1. 前提复核成立:main 上两态皆绿(指令在/不在 typecheck 都过)= 从未求值,幽灵坐实。
    2. 反向验证方向与派发模板相反,且这正是本单的核心发现——project(environmentId: string) 类型层本就接受 '',该指令从来无物可压,第一次被编译就是 TS2578。正确修法是删指令、保留运行时断言(下面的 guard 已测);镜像反向验证(加回指令 → 红 TS2578)先判后跑成立。采纳 open_question 1 的 A 案:B 案(non-empty branded string)是把运行时守卫编进公共签名,越出本单契约面,且运行时已守住,不买任何东西。
    3. 机制零发明:check-test-typecheck.mts 从 packages/spec/scripts/ 提升为共享脚本 + --package 参数,而非复制第二份——这是对「不要发明第二套」的正确字面执行;spec 侧仅两行脚本接线,台账字节未动,79 文件 / 691 错误 实跑复核不变。spec 面这两行若与在飞 spec 单冲突,merge queue 会裁,风险已知可受。
    4. 双台账毕业闸门齐:TEST_DEBT 删 client 陈旧条目(coverage 台账实跑 19 包,由 20 减一),PHANTOM_PIN_DEBT 删 fix(spec): 让 spec 测试层真的进 tsc,@ts-expect-error 退役 pin 不再是幽灵检查 #5478 点名「留给 幽灵 @ts-expect-error 不止 spec:@objectstack/client 也有 1 处落在 tsconfig 排除区内(全仓横扫结果) #5449」的 seed 条目。pin 所在文件零错误不入账,反向验证正是靠它成立。
    5. 残留 6 错单根因入 EXACT shrink-only 台账,根因(objectql registerObject 用 OUTPUT 型而非 INPUT 型)另立 objectql registerObject 的参数标成 schema 的 OUTPUT 类型(z.infer),合法的作者态字面量一律 TS2740 —— 这是 spec/client 两份测试层欠账的同一根因 #5543——采纳 open_question 2 的 A 案:producer 公共签名修正属另一契约面,台账在 objectql registerObject 的参数标成 schema 的 OUTPUT 类型(z.infer),合法的作者态字面量一律 TS2740 —— 这是 spec/client 两份测试层欠账的同一根因 #5543 落地当天自动变红强制毕业,不会烂尾;interim 用 as any 反而会藏真拼错(本 PR 修的 reference_to 就是证据)。
    6. 界外发现 objectql registerObject 的参数标成 schema 的 OUTPUT 类型(z.infer),合法的作者态字面量一律 TS2740 —— 这是 spec/client 两份测试层欠账的同一根因 #5543/packages/client/tests/integration/ 不在任何 tsconfig 里,且其中的 client.discovery 是早已不存在的属性(集成套件对着一个不存在的 API 断言) #5544/client SDK 的 meta.getItem 没有声明返回类型(载荷为 unknown),而并排的 meta.getItems 有 —— 同一表面上相邻两个方法的类型化不对等 #5545 已查重(含 closed)后无标签落池,留 triage 席分域。

    Check Changeset 首跑红是 opened 事件与 skip-changeset 标签的竞态(标签 16:54:27Z 打上,该跑 16:54:43Z 完成但检出的是无标签状态)——已摘/重打标签触发重评,走的正是 PR body 自述的 route 2(#5478 同先例,拒绝空 changeset 的理由即 #4898)。

    CI 全绿后转 ready 挂 auto-merge,跟到 MERGED。


    Generated by Claude Code

  9. added 2 commits that reference this issue on Aug 6, 2026
    d7e07c8
    f909fcb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions