# 云端历史缓存刷新与性能验证

## 行为

- 云端会话每次打开前仍验证账号归属与服务端历史修订；持续打开的订阅沿用原有 20 秒检查周期和 64 个流的上限。
- 历史正文修订包含历史代次、存储类型与进度。标题、源更新时间不参与正文失效判断；标题及创建时间作为单独显示修订。
- 缓存命中时直接返回本地页，不重写快照与事件。LRU 访问时间仍有小型元数据更新，不宣称整个 SQLite 完全无写入。
- 标题变化仅更新快照，不下载正文、不删除旧页。仅源更新时间变化不会重新发布历史快照。
- 同一历史代次的进度推进保留已缓存旧页；下载到的相同事件跳过 SQL 更新，只持久化新增或不同的内容。网络仍按既有五轮分页读取，未把它改成全量下载，也未宣称只下载新增事件。
- 迁移/历史代次变化、序号回退、显式 reset 继续原子清理残留。服务端提供有效修订时，无修订以及旧版混合修订的缓存先完成一次验证和重建；旧服务端不提供修订时仍不能信任持久缓存。
- 权限、下载前后版本校验、取消、分页连续性、256 会话与 128 MiB 缓存上限保持原 owner。

## 可复现的 Host/SQLite 对比

```sh
node --expose-gc scripts/profile-dsh-cloud-cache.mjs --baseline d1015458 --output /tmp/cache-before.json
node --expose-gc scripts/profile-dsh-cloud-cache.mjs --output /tmp/cache-after.json
```

同机顺序运行两个独立进程。基线通过 Git 只读加载指定提交的传输和缓存模块，不修改工作区。负载为 50000 个原始事件、500 轮对话，每个事件含 512 字节文本；1/4/8 个并发消费者代表多个窗口共用一个 Host。暖打开、无变化轮询、改名及仅更新时间变化各测 20 轮；另外覆盖冷打开、追加一轮、迁移换代和关闭清理。

记录元数据/历史请求数、传输字节、真实 SQLite INSERT/UPDATE/DELETE 次数、耗时样本和堆变化。两版本使用相同的 SQL 计数触发器、2 ms 模拟请求延迟、事件数据和校验代价。迁移换代前重新填充相同数量的缓存事件，避免把两个缓存规模不同的场景比较为性能改善。

这项对比测量 Host/SQLite 路径，不是公网延迟或页面 FPS。原生格式校验在该可控负载中替换为相同的空校验函数；真实官方 DSH 兼容和真实 Electron 窗口验收须另外报告。机器竞争负载会影响耗时，不能仅用单次 p95 声称所有设备无性能回归。

2026-10-13 在 macOS arm64 / Node 25.5.0 上，以 `d1015458` 为基线顺序重复三轮，1/4/8 消费者均完成。下表为 8 消费者 × 20 轮的计数，三次结果一致：

| 场景与指标 | 修改前 | 修改后 |
| --- | ---: | ---: |
| 暖打开：正文 SQL UPDATE | 80000 | 0 |
| 暖打开：快照写入 | 160 | 0 |
| 改名：历史分页请求 | 800 | 0 |
| 改名：历史响应字节 | 47858400 | 0 |
| 改名：快照写入 | 160 | 20 |
| 改名后保留的缓存事件 | 500 | 50000 |
| 仅更新时间：历史请求 / 快照写入 | 800 / 160 | 0 / 0 |

同代追加 100 条后的三次结果均为只插入 100 条、更新和删除均为 0，全部 50100 条缓存与源数据逐条规范化摘要一致。换代仍删除 50100 条旧缓存并保存正确的最近五轮 500 条，关闭后的订阅数均为 0；正文的其余历史仍由原分页接口读取。

暖打开每轮 160 个耗时样本，三个 p95 分别为基线 403.35 / 876.96 / 697.87 ms、修改后 83.94 / 254.43 / 83.78 ms。期间开发机有明显 CPU/内存竞争；这些是该机器上的观测值，不是跨机器 SLO。冷读、追加和换代仍有网络与 SQLite 成本，未宣称所有路径或页面 FPS 都有同等改善。

## 确定性门禁

`tests/dsh-cloud-cache-refresh.test.ts` 使用真实 SQLite 和事件写入计数，验证缓存命中不重写、四窗口改名只更新一次小型快照、反复轮询不写正文、追加保留旧页、迁移清理、旧缓存升级、序号回退以及缓存不绕过账号归属校验。

现有 cloud/native/cache 测试继续验证版本切换期间拒绝提交、取消不写半成品、无界订阅保护、页连续性与失败回滚。大历史对比逐条规范化摘要验证改名和追加后的全部缓存内容，迁移后校验保留页与服务端内容一致，并检查全部流已释放。

业务入口未增加：Tools、公开 SDK、UI 继续共用原账号会话 owner；本次没有新增路由、授权或写入生产数据的入口。优化仅影响插件侧可重建历史缓存。

## 官方安装与真实多窗口复核

同日将最终运行时代码打包为 tgz，经官方 DSH CLI 安装到全新 Profile，使用未修改的 DSH 0.1.5-rc.2、Electron 43.2.0 和独立客户端实例连接测试服。四个真实 Electron 页面共用同一个 Host，三个辅助窗口通过 Electron 的公共 BrowserWindow API 创建；此场景覆盖并发页面消费，不代表客户端正式多窗口入口或多个 Host 进程验收。

在数据副本中放入旧标题、无修订快照和超出服务端游标的残留事件，真实页面首次读取后从 14 条旧缓存恢复为 seq=0..12 的 13 条原始记录，逐条一致。之后四窗口共 80 次暖打开，以及每窗口连续 3 次按原 20 秒等待周期执行的轮询，正文 INSERT/UPDATE/DELETE 均为 0，全部返回内容保持一致。仍有权限检查、元数据请求和 LRU 元数据写入。

首次轮询驱动在每轮设置跨窗口屏障，使较快窗口等待最慢窗口，出现既有 45 秒闲置租约到期。保留失败记录后，改为每窗口独立连续拉取，12 次检查全部完成；业务租约、轮询周期和错误断言没有调整。真实测试服延迟不受控，这组结果不作为响应时间 before/after 对比。

测试服只读复核确认主记录与唯一会话数相等，原始事件和轮次记录无缺失、无改写，旧来源地址与保留地址的全量分页摘要一致。本轮迁移仅执行默认 dry-run，没有生产操作。六组既有 v0 日志仍存在当前 DSH 原生格式兼容限制，原数据和原接口可读；本轮没有伪造格式版本或扩大历史修复范围。


## 本机与云端入口收敛

账号目录仅在完整分页成功后使用服务端 `deduplicated_runtime_refs` 建立候选关系，不按标题或相同 session ID 折叠。服务端原 `sessions/metadata` 接口接受可选 `verify_content`，通过账号权限检查后流式读取原始历史并返回版本化消息摘要；普通目录和元数据刷新不读取历史。摘要保留用户、系统、助手、工具调用和工具结果的完整消息内容、时间、顺序及重复次数，忽略格式升级重新生成的事件序号和流式分片。

插件通过 DSH 公开只读 persistence handle，每批最多 256 个事件校验本机历史。包含双方所有消息且可读取的一方提供唯一入口；双方相同时优先本机，避免将有效本机历史切换到不兼容的旧云端格式。云端更完整时还需通过原生历史可读性验证。真实分叉、校验未完成、账号切换、历史回退或证据失效时保留来源，绝不删除原文件或静默拼接历史。

一次 Host 最多排队 16 个校验、串行处理，最多缓存 256 个结果；后端最多同时校验 2 组，缓存 32 组且摘要总计最多 8 MiB，五分钟过期。错误有五分钟退避。多窗口、Tools 和 SDK 共用 Host 目录，不各自下载或扫描历史。本机已验证为完整方后继续追加事件，不触发重新扫描；云端 `content_revision` 改变才重新判断。每组最多验证三百万事件、五万条消息；超限时保留来源并停止校验。

| 能力面 | 路径 |
| --- | --- |
| Host | `DshAccountSessions` 的账号隔离目录及共享校验队列 |
| Tools | 现有 `arkme_dsh_sessions` 获取相同的目录和选择证据 |
| SDK | 现有 `listDshAccountSessions` 保留类型化可选证据，旧服务端缺少字段时保守保留来源 |
| UI | 原生会话列表和 workspace 使用同一选择，不增加历史来源切换 |
| Client | 现有插件装配与单账号 Harness，未增加桌面原生桥 |

防止数据库再次重复由后端唯一逻辑会话索引、Host 写入代次和旧来源写入校验负责。目录收敛只改变展示，不授予被归并来源新的执行或写入权限。


2026-10-14 本次新增验证（与上面的旧缓存性能验收分别计数）：五组现有会话的原文件与数据库副本共有 687 条消息/工具记录。通过当前官方 DSH 的公开 persistence handle 再验收，三组可读且包含全部云端内容；两组旧压缩格式因 `tool-call-chunks` 的字段类型被官方迁移器拒绝，保守保留两份入口。本次未完成全部重复项的真实页面验收，不应将内容摘要相同等同于“当前客户端已经无重复”。

本机列表可能带有格式迁移前的序号或 -1；内容证明读取公开 handle 的实际末尾，不把旧序号当作新的读取上限。每批 256 个事件，序号必须连续。工具 meta/error、usage 的差异不会被丢弃；新格式内嵌 stream 和重新编号的 turn/step 不参与消息身份。

此前版本（已由下节替代）对旧来源上报冲突按页顺序二分隔离，合法新会话仍可上报；失败来源不 ACK、不清理 outbox，也不把部分上传标记为快照完成。冲突身份缓存最多 10,000 项、五分钟重试，账号退出清除；正常批次仍为一次请求。此机制不授予旧来源新的云端写入权。


## 2026-10-14：续传与额外扫描修正

原接口 `sessions/sync` 可选 `report_conflicts` 返回完整的 accepted/rejected 会话引用回执。新插件每页只有一次请求；通用冲突、鉴权和网络失败不拆批、不猜测失败身份。只有服务端明确拒绝的身份进入五分钟退避；回执缺失、重复或包含未知身份则拒绝确认，失败快照不标为完成。兼容旧后端的整页成功响应；部署顺序为后端先上线，再更新插件。

后端已归并的旧来源可以继续向自己的事件和 OSS 地址上传。来源元数据保存在唯一主记录的有界 source_states 内，不恢复重复目录行。账号、原电脑 Host 和代次仍必须匹配；不知道来源的新 Runtime 不能仅靠 session ID 抢占写入。完整历史上传后的默认来源切换由后端独立验证，失败不影响已提交上传；真实分叉、正在运行的会话和执行权冲突不会覆盖主来源。

本机校验通过公开 DSH list 一次取得最多 10,000 个轻量摘要，所有页面和窗口共享快照及一个在途查询。会话事件或账号切换使其失效，30 秒兜底过期兼容旧事件协议；变更中的过期结果不会发布。验证摘要不再用分页循环重复调用本来已返回全目录的 DSH list；目录上报也使用一次读取的请求内快照，再进行账号过滤与分页发送。事件密集到达时也只允许一个目录查询在途。

内容证明仅随实际持久化的历史内容、历史代次变化失效；改名、source_updated_at、纯元数据水位和重复事件/OSS 提交不再触发历史重读。验证仍是后台只读工作；错误保留来源，不清正文缓存或 outbox。

确定性门禁覆盖：10,000 会话目录、400 次并发及暖查询只调用一次 DSH list；10,000 消息按 256 条读取一次，400 次改名/时间变化不重读历史；100 条中有 1/50/100 条身份冲突都只发一次请求；事件风暴、账号切换、旧后端及损坏回执保持失败语义。此前真实 SQLite 缓存与多窗口用例继续运行。本节是代码/本地集成验证，不替代独立 Arkme 页面或生产验收。


### 归并来源的上传恢复

原 `sessions/sync` 的可选逐项回执携带 `source_bindings`。只有后端确认的原来源才触发一次本地回执修复：保留待上传 spool/object 文件和 OPEN 轮次，待正在提交的上传结束后丢弃可重建的 COMMITTED 索引、扫描及完成检查点，解除旧冲突暂停，并从本机日志补传。绑定和检查点修复在同一事务中落盘，相同绑定跨窗口/重复响应/重启均不再清理。修复在后台执行，不阻塞目录 ACK；后端不可用也不会删除待上传内容。

本次验证覆盖 40 个并发绑定回执、上传提交中的串行恢复、跨重启幂等、当前 OPEN 轮次和原始待上传内容保留。5 组真实历史副本中，3 组完整本机日志补传后覆盖原云端并完成默认来源切换；2 组旧格式仍不能按当前原生格式完整读取，原历史保留。此处不声称完成这两组原生页面验收。
