Skip to content

fix(web): web-v4 键家族收敛为唯一工厂,并修好一族「打不中任何缓存」的失效(#2261 残项,ADR-029) - #2317

Merged
DeliciousBuding merged 1 commit into
masterfrom
fix/web-v4-query-key-convergence
Sep 4, 2026
Merged

fix(web): web-v4 键家族收敛为唯一工厂,并修好一族「打不中任何缓存」的失效(#2261 残项,ADR-029)#2317
DeliciousBuding merged 1 commit into
masterfrom
fix/web-v4-query-key-convergence

Conversation

@DeliciousBuding

Copy link
Copy Markdown
Collaborator

为什么现在做(不是为统一而统一)

#2261 残项的 DEFERRED 触发条件是「下次真要动 web-v4 键形状时同 PR 收敛」。本 PR 之所以成立,不是因为键写法不统一,而是因为普查发现了一族打不中任何缓存的失效,其中数条有明确用户可见后果 —— 与 #2252 / #2310 同一个失效模式,只是换到 Web。

修掉的真实缺陷(6 条)

  1. 联系人列表永久陈旧contactQueries 的 6 处失效打在 hubQueryKeys.contacts.list,而 Web 的联系人列表实际缓存在 webQueryKeys.contacts.list(hubReady)。该查询只有 staleTime没有 refetchInterval ⇒ 接受/拒绝好友请求、删除、拉黑、取消拉黑、改备注之后,列表一直显示旧状态,直到窗口重新聚焦。
  2. 新建群聊延迟出现createGroupSession 失效打在 hubQueryKeys.threads.list(Web 无生产者)⇒ 最多等一个 10s 轮询。
  3. 登录后刷新是静默 no-opuseWebAuthrefetchQueries(hubQueryKeys.threads.root) 在 Web 匹配不到任何缓存条目。
  4. realtime 联系人帧不刷新webHubRealtime 的 CONTACT_EVENTS 打在一个少了 hub- 段的手写键上 ⇒ 另一标签页接受好友请求,本页不动。
  5. 裸字面量连命名空间都没有useWebWorkbenchModel 审批失效写着 ['agent-teams'] ⇒ 从来不命中;改为 hubQueryKeys.agentTeams.root(真命中 usageBoard)。这里选择retarget 而非删除:审批决定确实会改变 usage board 聚合的 runs,原作者的意图是对的,只是键写错了。
  6. 文档漂移即认知根源webPlatformMessageHelpers 的注释断言「['hub','threads'] 就是 Web 的会话列表键」——事实错误,正是 1/2/3 的共同根源,已改写为真实键。

消融(每条都有 0 生产者证据)

普查范围:app/web + app/workbench + app/shared 全量 queryKey: 生产点;workbench 完全不使用 react-query 键(0 处 hubQueryKeys/web-v4),所以不存在隐藏生产者。

  • DEVICE_EVENTS:Web 命名空间的 execution-targets 孪生键无生产者(真缓存只在 hubQueryKeys.executionTargets.list(context))⇒ 删死的那条,保留能命中的 family root。
  • NOTIFICATION_EVENTS 整个分支 + 事件集合删除:Web 根本没有通知查询,两条失效都命中 0 个缓存条目。它读起来像覆盖,实际不是。
  • TEAM_EVENTS:5 条 run 级键(两个命名空间的 runs/runDetail)在 Web 全部无生产者(Web 只有 usageBoard)⇒ 收敛为 1 条 family root,连带删掉只为拼死键而存在teamId/teamRunId 解析。

机制

  • app/shared/src/stores/queryKeys.ts 新增 webQueryKeys,成为 web-v4 命名空间唯一的字面量产出点app/web/src 非测试代码里的键数组字面量 58 → 0grep "\['web-v4'" = 0;残留 3 处 web-v4 字符串分别是 dispatch source 标签、client_msg_id 前缀、命名空间名的散文提及,都不是查询键)。
  • 键值逐字不变,只搬产出点 ⇒ 既有断言键值的测试无需改动(本 PR 只改了 2 处测试:删掉一条把 bug 钉死的 spy 断言、把 useWebAuth 的期望改成真键 + 反向断言)。
  • 新增 QueryKeyPointer:可空指针原样穿过工厂,不归一化成 ''。归一化会给 webHubMessagesFamily.sessionIdOf 造出一个字符串会话 id,被重连补发当成真会话去 sync —— 那是把一个死键 bug 换成一个更糟的活 bug。

证据

  • 新增 app/web/src/platform/webQueryCacheTargets.test.ts(8 例):按真实生产者键播种 QueryClient,跑真实失效代码路径,断言 getQueryState(...).isInvalidated。断言的是缓存真的变陈旧,不是「调用发生了」。
  • 变异测试 7 发,6 发翻红:M1 联系人失效回退 / M2 会话失效回退 / M3 realtime 联系人回退 / M4 device 回退 / M5 team 回退 / M7 useWebAuth 回退 —— 各自精确翻红对应的那一条测试。
  • M6 如实登记为不翻红:把已删的 NOTIFICATION 分支放回去,测试仍全绿。它证明的是该分支本来就惰性(这正是删除它的理由),不是「证明删除必要」。新增的通知测试钉的是「通知帧不得动任何 Web 缓存」,删前删后都成立。
  • 删掉的那条 spy 断言就是 bug 的护身符webHubRealtime.test.ts 原本 toHaveBeenCalledWith({ queryKey: ['web-v4','execution-targets'] }) —— 键没有任何生产者,但套件一直绿着,失效一直空转。已由 effect-based 测试取代。
  • 测试基线:web 275 例(含新增 8 例、删 1 例)/ workbench 169 文件 1710 例 / desktop-ci 52 文件 455 例 / shared 151 文件 2755 例 全绿;四包 typecheck 绿。
  • 门禁:本地复跑 checks.yml validate job 全 62 条命令 PASS=62 FAIL=0

Refs #2261

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 41d75e95-4a21-49a5-b9a2-a268eeb778ad

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@DeliciousBuding
DeliciousBuding enabled auto-merge (squash) September 4, 2026 08:07
残项裁决的触发条件已满足,不是为统一而统一:普查发现 Web 有 6 类失效/刷新
指向根本不存在生产者的键,用户可见后果实测存在。

真实缺陷(本次修掉的):
1. contactQueries 的 6 处联系人失效全部打在 hubQueryKeys.contacts.list 上,
   而 Web 的联系人列表实际缓存在 webQueryKeys.contacts.list(hubReady)。该查询
   只有 staleTime、没有 refetchInterval ⇒ 接受/拒绝好友请求、删除、拉黑、取消
   拉黑、改备注之后,联系人列表一直显示旧状态,直到窗口重新聚焦。
2. createGroupSession 失效打在 hubQueryKeys.threads.list(Web 无生产者)⇒ 新建
   群聊最多要等一个 10s 轮询才出现在会话列表里。
3. useWebAuth 登录后的「refetch threads」打在 hubQueryKeys.threads.root ⇒ 整个
   登录后刷新是静默 no-op。
4. webHubRealtime 的 CONTACT_EVENTS 打在一个少了 hub- 段的手写键上 ⇒ 别的标签页
   接受好友请求,本页联系人列表不刷新。
5. useWebWorkbenchModel 的审批失效写着裸 ['agent-teams'](连命名空间都没有)⇒
   从来不命中;改为 hubQueryKeys.agentTeams.root(真命中 usageBoard)。
6. webPlatformMessageHelpers 的注释断言「['hub','threads'] 就是 Web 的会话列表键」
   ——事实错误,正是 1/2/3 的认知根源,已改写为真实键。

消融(0 生产者,逐条普查 app/web + app/workbench + app/shared,workbench 完全不用
react-query 键,无任何隐藏生产者):
- DEVICE_EVENTS 里 Web 命名空间的 execution-targets 孪生键:真缓存只在
  hubQueryKeys.executionTargets.list(context) ⇒ 删死的那条,保留能命中的 root。
- NOTIFICATION_EVENTS 整个分支:Web 根本没有通知查询 ⇒ 两条失效都命中 0 个缓存
  条目,分支与事件集合一并删除;新增测试钉住「通知帧不得动任何 Web 缓存」。
- TEAM_EVENTS 里 5 条 run 级键(两个命名空间的 runs/runDetail):Web 只有
  usageBoard ⇒ 收敛为 1 条 family root,连带删掉只为拼死键而存在的
  teamId/teamRunId 解析。

机制:
- app/shared/src/stores/queryKeys.ts 新增 webQueryKeys,成为 web-v4 命名空间的
  唯一字面量产出点;app/web/src 非测试代码里的键数组字面量从 58 处降到 0
  (grep "\['web-v4'" = 0;残留 3 处 web-v4 字符串分别是 dispatch source 标签、
  client_msg_id 前缀、命名空间名的散文提及,都不是查询键)。
- 键值逐字不变,只搬产出点 ⇒ 既有断言键值的测试无需改动。
- 新增 QueryKeyPointer:可空指针原样穿过工厂,不归一化成 ''——否则会给
  webHubMessagesFamily.sessionIdOf 造出一个字符串会话 id,被重连补发当成真会话。

证据(不是只看调用,而是看缓存真的变陈旧):
- 新增 app/web/src/platform/webQueryCacheTargets.test.ts(8 例):按真实生产者键
  播种 QueryClient,跑真实失效代码路径,断言 getQueryState(...).isInvalidated。
- 变异测试 7 发,6 发翻红(M1 联系人失效回退 / M2 会话失效回退 / M3 realtime
  联系人回退 / M4 device 回退 / M5 team 回退 / M7 useWebAuth 回退)。
  M6(把已删的通知分支放回去)如实登记为不翻红:它证明的是该分支本来就惰性,
  不是证明删除必要——测试钉的是「通知帧不动任何 Web 缓存」,删前删后都成立。
- 删掉 webHubRealtime.test.ts 里那条把 bug 钉死的 spy 断言
  (toHaveBeenCalledWith(['web-v4','execution-targets'])):它断言的是「调用发生了」,
  而那个键没有任何生产者,所以套件一直绿着,失效一直空转。
- useWebAuth.test.tsx 同步改为断言真键 + 反向断言不再使用死键。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

裁决落盘:docs/decisions.md 新增 ADR-035(本 PR 的五条结论:唯一产出点 /
可空指针不得归一化 / 失效必须有生产者 / 无生产者一律删不留孪生键 /
断言看缓存效果不看调用),#2261 残项由 DEFERRED 转为 landed。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant