# 账号消息补齐

原始实现基线为 dev 1162d344；2026-10-15 整合到 dev eb306f54，并保留同期录音实体等基线能力。私聊/群聊复用 `/api/v1/chat/sync/account` 的 unread_manifest/bodies；消息读取、未读 ACK、通知和发送的 owner 不变。

同步 owner、banner、自动续页和持久阅读恢复必须作为同一运行产物交付。旧发送任务位置修复也包含在该产物中：已确认的历史附件任务不再作为最新消息追加，正在发送和失败任务仍保留反馈；消息的 canonical 标识和顺序不变。下方各轮验收数据属于当时制品的历史记录，不能替代整合制品的重新测试和客户端验收。

| 能力面 | 实现与验证合同 |
| --- | --- |
| Host | 唯一账号任务、持久清单/正文/进度、单一 Worker；失败不虚报提交 |
| UI | 搜索框下方、DeepSeek Harness 上方的浅灰 banner，只显示“同步中 n/m”；300ms 防闪、350ms 完成保留；隐藏时不占空间 |
| SDK | 能力发现、状态查询、重新检查、事件订阅和清理；仓外 Consumer 验证 |
| Tools | 状态查询与重新检查；正式 DSH 会话注册、授权与调用验证 |
| 客户端 | 既有 Browser/Host 承载，独立 App Data 实测；本轮不发布或替换其他常驻实例 |

清单收齐才固定分母。正文与账本同事务提交才增加完成数；撤回/撤权单列终态；missing/pending 继续保留。发现检查点只随清单事务推进。新消息不扩张已冻结批次，不主动标记已读或通知历史消息。

后台最多两个正文请求，每次100条/50会话，预取身份最多400；复用现有总并发4的消息池。UI 不轮询或创建同步任务。账号切换取消旧任务；重启恢复未完成批次。Worker、数据库、正文和账本都有容量限制；未读内容不因窗口淘汰而失去保留引用，容量耗尽暂停，不报告完成。

本地消息保留与统一窗口共享 canonical contents；缺少其他来源覆盖时明确为部分窗口，不伪造服务端 coverage token。UI 的数值订阅不刷新目录/时间线，隐藏时不保留槽位，左侧目录恢复原来的高度；搜索框和右侧消息区域不变。

本轮验证使用独立 macOS Electron 客户端、官方 DSH 0.1.5-rc.2 和测试环境；客户端与 DSH 源码保持原样。具体结果见末尾验收记录。

## 消费方式

能力开关是 `capabilities().features.accountMessageSync === true`。Browser SDK 的 `messageSyncStatus(signal)` 返回当前 Host 进度，`retryMessageSync(signal)` 合并到既有任务；`observeMessageSync(listener, {signal, onError})` 返回清理函数，同一 SDK 实例最多64个监听器，共享一个事件连接，退出页面时必须清理。旧 Provider 返回 `CAPABILITY_UNSUPPORTED`，不要自建同步循环。

DSH Tools 为 `arkme_message_sync_status` 和 `arkme_message_sync_retry`；重新检查使用既有会话确认授权。它们与 UI/SDK 共用 `ChatMessageSyncService`。公共结果只含账号作用域和统计，不返回上游 checkpoint、关系 UID 或凭据。

账号账本最多100000条工作身份、10000个会话。补齐独立额度192 MiB，后台提交不淘汰前台阅读窗口；前台保留原有64 MiB/256窗口额度。后台在整体物理占用288 MiB时暂停，为前台保留96 MiB物理额度；有补齐保留引用时整体物理占用384 MiB、SQLite文件硬上限512 MiB，空同步库仍按原有96 MiB占用修剪。正文仍只保留一份，计量用同事务增量账本而非全表扫描。已确认的已读事件只释放补齐保留引用，不产生新的已读请求。

搜索框恢复原来的完整样式。同步 banner 独立于搜索工具栏与会话滚动区域，高28px、左右留白10px、底部留白8px；隐藏时移除整个槽位及边距，左侧目录自然上移36px；不挤压搜索框，不改变右侧消息区域的尺寸。banner 无点击弹层、重试按钮或阶段切换文案，使用原有账号同步进度，不增加请求或定时任务；Tools/SDK 的状态与重试能力保持原样。保留300ms防闪和350ms完成保留，不增加折叠动画或展示定时器。

## 验收记录（2026-10-14）

- 测试账号首次固定清单为46,456条，全部正文成功落盘；重启从6,200条继续，分母没有重置。后续批次单独统计。
- 同步期间用真实滚轮停留历史消息：31次采样、约60秒，完成数从33,000增至45,200，可见锚点与相对偏移保持一致；切换会话返回也恢复一致。
- 初版搜索旁胶囊已按验收反馈撤掉；改用搜索框下方的独立 banner，恢复完整搜索文案。最终布局和客户端复验见下方补充记录。
- 聊天上游不可达时重启最终制品：本地40条消息仍可读；持久锚点一致，偏移差0px。恢复网络后仍停留原锚点。
- 测试账号“发给自己”的一条验收消息已通过真实通道确认，仅显示一次；后台调度专项验证两条补齐请求占用时前台实时请求仍能进入。
- 正式DSH会话能发现两项同步Tools并成功调用状态查询；仓外SDK通过编译、真实Host调用与旧Provider能力缺失检查。
- 冷启动恢复专项通过：单次调用5秒到期仍降级，初始化最多等待30秒，保留排队计量；初始化就绪后后续请求恢复，不永久关闭缓存。已有预算账本启动时不重扫全部正文。
- 最终全量：988文件，972通过、1失败、15跳过；11,462项通过、3项失败、20跳过。3项均为原始1162d344基线已复现的service-architecture检查：遗漏已有arrangementSourceOpen、遗漏arrangement-source-recovery.ts、facade已有/api传输。类型生成、构建、Worker执行入口校验、打包通过。
- 串行两轮基线对照（256窗口×40事件、各路径20次）：锚点读取p95基线0.31–0.38ms，最终0.27–0.38ms；窗口更新p95基线4.27–6.90ms，最终5.45–7.59ms；淘汰p95基线4.34–4.68ms，最终3.58–4.44ms。短样本有波动，未观察到一致的延迟回退；这些是本机缓存测量，不代表线上SLA。

本轮实际平台为macOS；Windows/Linux未做客户端实机验收。未执行发布。存储容量有界，超过额度会暂停并保留未完成计数。

## A 方案补充验证（r5，固定槽位历史记录）

- 撤掉搜索旁的胶囊、窄栏隐藏搜索文案规则及点击详情；搜索工具栏恢复基线实现。banner 固定在搜索框下方、DeepSeek Harness 上方，位于会话滚动区域之外。
- 相关测试7文件、76项通过，覆盖搜索与banner的独立位置、账号切换、完成淡出及槽位保持；类型检查通过。
- 最终全量989文件：973通过、1失败、15跳过；11,464项通过、3项失败、20跳过。3项失败仍是前述原始基线问题，未新增失败。
- 最终r5不可变制品经官方DSH安装；安装后的两个UI模块sourcemap内容与任务源码一致。客户端、Host同步调度、Tools/SDK合同没有新增改动。
- 实机采用主题的浅灰模块背景，解析为rgb(245,246,247)。搜索框200×40px、banner槽260×28px，29次启动/同步/完成采样中搜索框、banner槽、会话目录与Harness入口坐标均未变化；消息区域建立后尺寸始终为936×609px。
- 新生成1000条测试私聊消息：1000个发送回执和接收方1000条正文逐条核验，接收方未读数为1000。清空消息SQLite缓存后重启，账号另有101条未读，本轮固定分母为1101并完成1101/1101；后台同步完成后新增的1000条私聊消息仍保持未读。
- 在另一测试会话记录阅读位置后清缓存重启：恢复后的消息锚点与重启前一致，相对偏移均为-55.25px，差值0px。启动时的窗口恢复完成后才据此判定，不把首次占位窗口当成恢复结果。

## 隐藏收起修订（r6）

- 根据验收反馈，隐藏时不再预留36px槽位。搜索框保持原样；同步完成保留350ms后整个banner及边距移除，左侧目录恢复原高度，右侧消息区域不变。沿用原300ms防闪，不修改Host调度、缓存或阅读恢复。
- 相关7文件77项通过；类型检查通过。全量989文件：973通过、1失败、15跳过；11,465项通过、3项失败、20跳过。3项仍为前述已复现的service-architecture基线失败，没有新增失败。

## 审核 loop 修订（r7）

- 修复并发计数：若较晚的前台请求写入旧正文版本，阻止较早后台响应落盘，后台必须回滚并重试，不得把未写入的新版计为完成。回归覆盖冲突、重启后仍未完成、重新提交后正文与计数一致。
- 本地阅读定位分别按事件、item key、Record UID 查询；Record UID 使用内容表的局部表达式索引，并固定先查身份再连接保留引用，避免对整段正文做 JSON 扫描。覆盖三种身份、账号隔离、重启和撤权。
- 空检查只把 pending 记录带入下一批次，保留已完成未读记录原样；没有释放保留引用时不扫描孤立正文。确定性测试确认空检查不重写已完成账本，失效消息仍进入下一批计数。
- 独立只读 SQLite 核验：发送清单1000条全部匹配，9000项身份/正文/顺序/版本/状态检查零差异；无缺失、重复、悬空引用或引用数偏差，integrity_check=ok，容量账本重算一致。最近空检查0/0表示没有新工作，已落盘1000条未读正文仍全部保留。
- macOS同机真实缓存副本（46,555条），每轮后台新增3000条、每批100条，前台读30次；索引修正前空闲定位p95约98ms，最终两轮约0.39–0.40ms；并发后台写入时前台读p95约8.69–12.53ms，正文全部提交且锚点一致。空检查begin由约233ms降到10.38–10.43ms。首次建立索引的Worker启动约129–131ms，后续索引复用；这些是本机受控测量，不代表跨平台或长期SLA。
- 最终相关测试8文件61项通过，类型检查通过；全量989文件：973通过、1失败、15跳过；11,468项通过、3项失败、20跳过。失败仍为原始基线的3项service-architecture检查，无新增失败。

## 审核 loop 冷缓存恢复修订（r8）

- 固定在历史消息的复验揭示：r5记录的清缓存锚点恰好处于最新窗口，不能证明任意历史阅读恢复。r7暖启动偏移差0px，但清缓存后会丢弃持久锚点并跳至最新。
- 初次网络读取现在沿用已保存的阅读意图；视口只额外保存有界的消息身份（Record UID与owner），复用现有around接口直接读取历史窗口，不扫描历史分页，不保存正文、凭据或服务端游标。旧版几何数据仍兼容；锚点不可用或请求失败时保留原位置，不用空错误页覆盖持久状态。
- 新增覆盖冷缓存单次定位、旧几何兼容、锚点缺失、账号/导航取消、最新跟随、64位owner无损和身份白名单；阅读恢复相关6文件85项通过，类型检查通过。
- r8完整测试989文件：973通过、1失败、15跳过；11,475项通过、3项失败、20跳过。失败仍为已在原始基线复现的3项service-architecture检查；未新增失败。
