# 群成员分页与本地缓存

## 数据与生命周期

每个环境/账号/群只有一份 Browser 成员 Store。会话、成员抽屉、@ 候选和已读详情订阅它；页面只保存展开、选中和滚动等展示状态。memberRef 是成员身份，sourceKey 是群身份。账号只由运行时生命周期激活，订阅者不能切换全局账号。管理操作能力只取自 group.settings，不能从缓存角色推断。

首次订阅时，本地缓存恢复和服务端读取并行开始。缓存命中先显示，但不设置远端新鲜度、不跳过远端请求。第一份远端结果到达后，迟到缓存不能覆盖它。缓存过期、损坏、引用签名失效或读写失败均按缓存未命中处理；授权失败仍明确返回。

服务端基础名单每页 50 人、按稳定 user_id 游标读取。每页携带访问校验得到的 selfRole，管理入口不读取缓存中的角色，也不等待自己排到后续页。每页到达就按 memberRef 合并，不清空列表、不替换未变化成员对象。展示补齐最多两个批次并行，每批至多 50 人；基础分页不等待补齐空位。基础页不得抹去缓存中的姓名、头像和统计；展示结果只更新已有成员，不能创建或复活成员。公开资料失败按成员标记，仅保留其旧头像，已获取的姓名、备注和统计照常更新；统计尚未读取时留空并保持行高。跨页同一入群事件按 memberRef 合并 invitees，不按姓名合并身份。

分页遍历不是原子快照：某个成员没出现在一页或整个遍历中都不能直接视为退出。遍历结束后，对缺席缓存成员通过现有按用户查询接口明确核验，只应用 removedMemberRefs。移除成员的确认回执直接更新缓存；退出、解散、访问权限失效清掉对应缓存。运行时缓存代次同时隔离请求去重键和缓存写入，防止新读取复用变更前请求。

实际 Chat 成员缺失/退群（2001）和会话停用（2002）会同时清除 Browser 与 SQLite 缓存；这些错误只在成员读取合同内归类，不能把一般业务冲突当作删除。

成员加入通知 t=24 只触发公共名单回查；退群历史通知 t=27 另外触发历史窗口失效，两者都不携带可直接落地的成员或已读事实。

切后台、最后一个订阅者离开、账号或运行实例切换会取消相应旧工作；当前列表在一般网络失败时保留并可重试。权限拒绝还会取消迟到缓存恢复，避免恢复已清除的行。

## 接口与发布顺序

| 接口 | 合同 |
| --- | --- |
| Chat POST /api/v1/chats/members/page | chat_session_uid、after_user_id、limit（默认 50，最多 100）、active_only；返回 items、has_more、next_user_id。每页验证当前访问权；复用现有索引；不做统计或外部资料补齐 |
| source.members.page | Host 将游标封装为账号/群绑定的不透明引用，返回基础成员与入群事件 |
| source.members.presentation | 接收 1-50 个 memberRef，通过 members/by-user-ids 和公开资料批量接口补齐展示，明确返回已不活跃成员；不扫描整个联系人/会话目录 |
| source.members.cached | 仅返回当前账号/群的本地展示缓存及 cachedAtMillis，未命中返回 null；不是权限或服务端事实 |
| source.read-receipts.detail + basicOnly | 先返回服务端已读事实和本地已有展示资料，UI 再订阅成员 Store 更新展示；默认完整查询行为保持兼容 |

原有 members/list、source.members、SDK/Tools 默认完整已读查询继续服务其既有调用方。新成员 UI 只有分页路径，没有全量降级或双加载构造器；后端缺少新接口时保留已有缓存并明确报错。因此需先部署 Chat 新分页接口，再接入新插件。basicOnly 是同一已读接口的轻量投影选择，不是版本兼容回退。

## 本地缓存

复用插件现有 SQLite（环境目录 + user_id + 群 UID），不依赖浏览器端口。缓存格式版本为 1，只接受白名单展示字段；旧格式按未命中处理，不保留双格式读取。表内记录 payload_bytes，容量统计不扫描 JSON 内容。缓存只由 Host 的已授权查询结果写入，不提供 Browser 任意写接口。签名引用按当前账号/群/运行密钥校验；缓存不能授权写操作，所有操作仍经服务端校验。

内存最多保留 20 个无人订阅群、合计 20000 名闲置成员。磁盘缓存最多 100 群、合计 32 MiB；单群最多 20000 人/4 MB，14 天过期。超过容量按旧缓存淘汰，不能截断名单伪装完整。活动分页单次遍历最多 200 页，超过时保留已有数据并明确失败。容量与过期都只影响缓存命中，不改变服务端查询结果。

## 能力矩阵与验证

| 面 | 完成证据 |
| --- | --- |
| UI | 打包插件安装到新的官方 DSH Profile；真实 Host + SQLite，合成远端数据验证 500 人分页、重启先显、增量修改/移除 |
| Host | 分页、展示、缓存共用 ChatService / ServiceRuntime / LocalDatabase；游标校验、跨账号/群、失败和缓存代次测试 |
| Tools | arkme_source_members_page、arkme_source_members_presentation、arkme_source_members_cached；目标 DSH 0.1.1-rc.2 正式 ToolRuntime 注册、带会话上下文调用及卸载测试通过 |
| SDK | 从不可变 tgz 安装的仓外 Consumer 通过公开 SDK 编译并调用能力探测、缓存、分页、展示；使用 AbortSignal；缺少分页直接返回请求错误 |
| Client/Harness | 沿用现有 Browser module / Host 载体；无 Electron 或 DSH 源码修改；未发布运行环境或替换用户客户端 |

500 个缓存行不会立即触发 500 次头像读取：成员和已读详情行通过 IntersectionObserver 仅在可见时订阅头像。

最新逐场景合并前审查见 [member-directory-premerge-review.md](member-directory-premerge-review.md)，前轮记录见 [member-directory-review.md](member-directory-review.md)。下列时序是初版交付的历史观察，修复后的复验以该报告为准。

## 验证边界

在真实 Host/协调器的同负载模拟中，每次上游延迟固定 2 秒：旧 500 人完整读取在冷/暖公开资料缓存下都累计 42 秒；新流程首批 50 人在 2 秒可见、完整资料在 32 秒完成（10 个基础页、10 个成员资料批次、10 个公开资料批次），没有成员触发的全目录扫描。这是 fake-clock 对比，不能作为生产延迟保证。

打包插件的本机重启验收中，保留 SQLite、切换到新端口、让远端成员请求延迟 2 秒：缓存接口约 44ms 返回，500 名缓存成员约 89ms 可见，当时远端请求已发出但未返回。远端随后确认一人退出和一处备注修改，页面与 SQLite 均收敛到 499 人。这是单次隔离场景观察，不是跨平台 SLO。

Chat 后端新增分页的真实 Mongo 验证覆盖 500 人/10 页、无重复或跨群、索引命中且无 COLLSCAN/SORT；API 鉴权与参数测试、go vet 通过。全量 Go 测试中的 Docker E2E 在本机缺少 Docker；Mongo 全包另有既有失败，已在未修改的 master 9f649eac 上复现，新增分页测试通过。未把这些门禁记为全绿。

代码基线：插件 official/dev 121f68e9；后端 master 9f649eac；合同 meta master 8bb0b7a9。没有生产部署、PR、版本变更或 Windows/Linux 实机验收。

已存在内容时采用静默刷新：成员标题和已读详情不显示“更新中”或“正在更新”，不插入资料加载行；首次无数据与真实失败保留必要反馈。

## 成员统计合同

成员资料请求固定传入 `include_stats: true`，继续通过现有 `source.members.presentation` 服务 UI、Tools 和 SDK。Chat 后端按本批至多 50 个成员聚合 `extra.record_count` 与 `extra.mention_count`，复用完整名单的可见性、撤回及 @所有人语义；不传参数的身份查询不增加统计开销。

统计是可选展示数据：查询最多占用 2 秒，失败时保留已授权的身份与昵称，并从响应中移除两个统计字段，不采用持久化 extra 中可能过期的计数。请求取消仍按取消处理，不伪装成功。统计聚合与响应清理均不写业务数据。

插件只有在两个统计字段都是非负安全整数时设置 `statsKnown: true`。缺失或非法值保持未知并归一化为安全占位值；成员行、菜单及快记标题不会显示假零。成员目录在统计未知时保留可用身份，沿用既有失效与刷新入口恢复；真实零和未设置昵称都不等同于加载失败。

本次没有新增 Host 路由、版本探测、全名单兜底、轮询、状态机、提示词或插件版本变更。详见 [成员统计审查与验证](member-statistics-review.md)。
