feat: add account-scoped data synchronization

This commit is contained in:
2026-09-22 09:58:58 +08:00
parent ab9ff389f2
commit 72e040546a
37 changed files with 4252 additions and 159 deletions
@@ -0,0 +1,95 @@
{
"captured_at": "2026-09-22T09:40:00+08:00",
"source_revision": "ab9ff38",
"artifacts": {
"control_plane_sha256": "f7695475efcf6742a2c6d8e7355029916093ad03c93fa3127c6e0534d8487fb2",
"tray_sha256": "f649cb55335e668850769e9c13e4bea5add5839e7a4a08179ede4c8895ee2bf7"
},
"account": {
"account_id_prefix": "a2e8a1ea",
"authorized_chat_count": 2,
"raw_message_content_included": false,
"database_keys_included": false
},
"offline_replay": {
"control_plane_unreachable": true,
"agent_process_session": 1,
"queue_pending_while_unreachable": 1,
"queue_bytes_while_unreachable": 3317,
"control_plane_restarted": true,
"queue_pending_after_reconnect": 0,
"node_status_after_reconnect": "Online",
"messages_before_replay": 458,
"messages_after_replay": 459,
"confirmed_batches_after_replay": 5,
"coverage_after_replay": "complete"
},
"agent_restart_recovery": {
"messages_before": 459,
"batches_before": 5,
"confirmed_sequence_before": 5,
"tray_stopped_seconds": 15,
"tray_restarted_session": 1,
"node_status_after_restart": "Online",
"messages_after": 459,
"batches_after": 5,
"confirmed_sequence_after": 5,
"duplicate_batch_observed": false
},
"control_plane_crash_recovery": {
"termination": "SIGKILL",
"integrity_before_restart": "ok",
"messages_before_restart": 460,
"batches_before_restart": 6,
"confirmed_sequence_before_restart": 6,
"integrity_after_restart": "ok",
"node_status_after_restart": "Online",
"messages_after_restart": 460,
"batches_after_restart": 6,
"coverage_after_restart": "complete"
},
"authorization_revoke": {
"revoke_http_status": 200,
"conversations_http_status_while_revoked": 403,
"messages_http_status_while_revoked": 403,
"sync_status_http_status_while_revoked": 200,
"authorized_test_send_exit": 0,
"pending_batches_after_revoked_send": 1,
"agent_rejection_http_status": 403,
"agent_rejection_code": "AccountNotAuthorized",
"restored_by_agent_reregistration": true,
"node_status_after_restore": "Online",
"messages_after_restore": 460,
"confirmed_batches_after_restore": 6,
"coverage_after_restore": "complete",
"conversations_http_status_after_restore": 200,
"messages_http_status_after_restore": 200
},
"windows": {
"session": 1,
"interactive": true,
"doctor_exit": 0,
"inspect_ui_exit": 0,
"smoke_exit": 0,
"wechat_version": "4.1.13.65",
"windows_build": "10.0.19044.0",
"ui_tree_nodes": 166,
"scope": "File Transfer Assistant and approved test chats only",
"stability_smoke": {
"exit": 0,
"success": true,
"healthy": true,
"sample_count": 7,
"message_events": 0,
"reconnect_events": 0,
"working_set_growth_bytes": 1273856,
"handle_growth": 12,
"thread_growth": 2
}
},
"notes": [
"The offline batch was observed in the durable queue before the control plane was restarted and drained after reconnect.",
"The authorization-revoked message was accepted by the test WeChat UI but remained in the Agent queue; the platform rejected it and did not ACK it until authorization was restored.",
"Long-duration endurance, power-loss, multi-control-plane HA, and production-sized capacity are separate follow-up validation, not represented as passed by this artifact."
]
}
@@ -0,0 +1,86 @@
# 会话消息同步与分账号存储:P4 验收记录
> 验收日期:2026-09-22(+08:00)
> 源码基线:`ab9ff38`(实现变更已在本地提交)
> 控制面验收二进制:`/tmp/wxagent-control-plane-final5`,SHA-256:`f7695475efcf6742a2c6d8e7355029916093ad03c93fa3127c6e0534d8487fb2`
> Tray 验收二进制:`/tmp/wxagent-tray-publish-final8b/WxAgent.Tray.exe`,SHA-256:`f649cb55335e668850769e9c13e4bea5add5839e7a4a08179ede4c8895ee2bf7`
## 1. 自动化回归
| 范围 | 结果 |
| --- | --- |
| Web 单元测试 | 4/4 通过 |
| Web Vite 构建 | 通过 |
| Go `go test ./...` | 通过 |
| `WxAgent.Core.Tests` | 173/173 通过 |
| `WxAgent.Service.Tests` | 25/25 通过 |
| 完整 .NET Release 构建 | 0 警告、0 错误 |
| `git diff --check` | 通过 |
专项 Go 测试覆盖:
- 保留期清理:旧消息、无保留消息的会话预览和已确认批次可清理。
- 容量预算:批次/账号分片超限返回 `ErrAccountCapacityExceeded`,HTTP 映射为 507。
- WAL checkpoint/optimize:维护任务逐账号执行,坏分片错误隔离。
- 备份恢复:一致性备份可恢复;schema 版本错误或完整性错误不会替换原分片;恢复到 sequence 1 后可继续提交 sequence 2。
- 授权撤销:撤销 scope 后缓存消息查询拒绝,平台查询不会继续暴露历史正文。
- 游标/幂等:重复批次、序列冲突、源代次变化和 ACK 状态已有回归覆盖。
- 现有分片迁移:重新打开旧分片时补建消息观察时间和会话活动索引。
## 2. 合成压力
`TestAccountStoreSyntheticConcurrentReadWrite`:
- 8 个账号、800 条合成消息、16,000 次并发读取。
- 总耗时约 47.8 ms,最慢账号约 44.6 ms。
- 账号 scope 隔离和分片查询均通过。
SQLite 运行约束:WAL、`synchronous=FULL`、foreign keys、5 秒 busy timeout、每分片单写连接;消息查询使用 chat/time、observed-time 索引,会话查询使用 activity 索引。
## 3. Windows 交互桌面真机
通过 Windows UI 将微信切换到白名单测试群“消息测试专用群组”,再以交互 Session 1 执行:
- `doctor=0`
- `inspect-ui=0`
- `smoke=0`
- `session_id=1`, `interactive=True`
- WeChat `4.1.13.65`,Windows build `10.0.19044.0`
- Doctor:`MainView`、`session_list`、`chat_message_page`、`chat_message_list`、`chat_input_field`、`tool_bar_accessible` 全部存在。
- UI 快照 166 个节点,已脱敏;smoke 发送确认目标为 File Transfer Assistant,未启用 Web 发送。
非交互 SSH Session 0 的失败结果不作为真机验收结果;它只证明 Session 0 必须拒绝 UIA 操作。
## 4. 真机同步、断线补传、进程重启与授权撤销
完整、脱敏的事件计数见 [`WxAgent-会话消息同步-P4-live-evidence.json`](WxAgent-会话消息同步-P4-live-evidence.json)。关键结果:
- **断线补传**:控制面不可达时 Agent durable queue 观察到 1 个 pending batch(3317 bytes);控制面恢复后队列降为 0,节点回到 `Online`,副本从 458 条消息/4 批次推进到 459 条消息/5 批次,coverage=`complete`。
- **Agent 进程重启**:停止 Tray 15 秒后以交互 Session 1 重启;重启前后均为 459 条消息、5 批次、confirmed sequence=5,无重复批次,节点重新 `Online`。
- **授权撤销**:控制面 revoke 返回 200;撤销期间 conversations/messages 均返回 403,sync-status 仍可读。测试消息发送成功但平台拒收,Agent 日志记录 `AccountNotAuthorized/status=403`,队列保留 1 个批次;Tray 重新注册恢复授权后队列清空,副本到 460 条消息/6 批次,查询恢复 200。
- **控制面崩溃恢复**:对 live 控制面执行 SIGKILL,重启前后 SQLite `integrity_check=ok`,消息/批次保持 460/6,节点重新 `Online`,coverage=`complete`。
- **短时稳定性**:Windows `stability-smoke` 60 秒、7 次采样通过,healthy=true、messageEvents=0、reconnectEvents=0、无 findings。
- **副本最终状态**:2 个已授权会话,coverage=`complete`,`idx_conversations_activity`、`idx_messages_chat_time`、`idx_messages_observed_at` 均存在。
测试机 `service.json` 显式开启 `EnableDataSync` 仅用于本次白名单影子/真机验收(5 秒周期、100 条批次上限);代码默认仍为关闭。Reporting 白名单仍只包含文件传输助手、Hao 豪、吉祥三宝和消息测试专用群组范围。
## 5. Web 平台副本验收
使用 Browser Harness 登录控制面后,页面默认读取 `/v1/data/accounts/{id}` 平台副本:
- 显示两个会话及平台同步状态。
- 打开消息测试专用群组后显示历史消息。
- 页面显示 complete/freshness 信息;backlog 无 Agent 队列上报时保持未知,不伪造为 0。
- 旧 `/v1/reads/*` 路径仍存在,作为诊断/回退;发送仍独立禁用。
## 6. 本轮边界与后续运维专项
本轮 P0–P4 的目标是单控制面、本机 SQLite、一个已验证白名单账号的可恢复同步和 Web 切换;下列项目不属于本阶段部署边界,不能被本记录误读为已承诺的生产能力:
- 24 小时/7 天长期稳定性和大规模生产账号容量曲线。
- 生产环境硬杀进程/断电期间的 WAL 恢复演练。
- 多控制面高可用、跨节点故障转移和生产网络分区长时间补传。
- 所有微信版本、所有数据库分片布局及 page 1 HMAC 失败样本的真机矩阵。
- 生产备份介质上的异机恢复与定期恢复演练。
这些项目已列为后续运维/发布专项;本轮已完成授权测试账号的影子同步、断线补传、进程重启恢复、授权撤销和平台读取切换。旧 `/v1/reads/*` 仍保留,其他账号仍保持默认关闭,避免把本轮单账号证据扩大成生产容量承诺。