docs: add replayable P4 audit evidence
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -6,7 +6,7 @@
|
||||
<meta name="theme-color" content="#f6f7fb" />
|
||||
<meta name="description" content="WxAgent 普通用户工作台" />
|
||||
<title>WxAgent 工作台</title>
|
||||
<script type="module" crossorigin src="/assets/index-Ihi-UMyS.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-PxzqqEA7.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-6ql7JGil.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -251,7 +251,7 @@ control-plane-data/
|
||||
- [x] 设置保留期/容量预算、连接上限、清理与 checkpoint,验证慢查询和写入积压。
|
||||
- [x] 演练账号库备份恢复、平台回退后游标对账、撤销授权和备份保留处理。
|
||||
- [x] 完成 §10 的专项最小验证并记录未测范围,不把单次 smoke 当作长期稳定性结论。
|
||||
- [x] 更新协议、部署说明、操作手册和脱敏验收证据;已验收测试账号启用同步,其他账号保持默认关闭。
|
||||
- [x] 更新协议、部署说明、操作手册和脱敏验收证据;当前主机全部已验证账号均完成显式 opt-in 验收;全局 `EnableDataSync=false` 保持为安全不变量,而非未完成的自动上线。
|
||||
|
||||
P2–P4 脱敏验收记录:[`docs/validation/WxAgent-会话消息同步-P4-验收记录.md`](validation/WxAgent-会话消息同步-P4-验收记录.md)。
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"implementation_commit": "e534e46",
|
||||
"implementation_commit": "03f6764",
|
||||
"build_commands": [
|
||||
"cd control-plane && go build -o <control-plane> ./cmd/wxagent-control-plane",
|
||||
"dotnet publish node-agent/WxAgent.Tray -c Release -r win-x64 --self-contained true -p:EnableWindowsTargeting=true -p:PublishSingleFile=true -p:PublishTrimmed=false"
|
||||
],
|
||||
"artifacts": {
|
||||
"control_plane_sha256": "0bd1cd062a87561a6753062696f63a141ca41c89eaf9640e8f299319bf26edc7",
|
||||
"control_plane_sha256": "db8eb86706712f6a6b9de6bcc948ca6999e8cb06dee3664ca1ec76f1a65fc159",
|
||||
"tray_sha256": "72b4aae3347cc054f9787fb84924101346eceed4acc632cab555a754d7260b71",
|
||||
"windows_deployed_tray_sha256": "72b4aae3347cc054f9787fb84924101346eceed4acc632cab555a754d7260b71"
|
||||
},
|
||||
@@ -22,7 +22,7 @@
|
||||
"automated_validation": {
|
||||
"web_tests": "4/4",
|
||||
"core_tests": "173/173",
|
||||
"service_tests": "25/25",
|
||||
"service_tests": "26/26",
|
||||
"go_tests": "go test ./... passed",
|
||||
"solution_build": "Release with EnableWindowsTargeting passed",
|
||||
"diff_check": "passed",
|
||||
@@ -31,11 +31,25 @@
|
||||
"messages": 800,
|
||||
"reads": 16000,
|
||||
"result": "passed"
|
||||
},
|
||||
"platform_pagination": {
|
||||
"scheme": "keyset cursor",
|
||||
"message_key": "(chat_id, source_time, message_id)",
|
||||
"conversation_key": "(sort_time, chat_id)",
|
||||
"offset_sql_remaining": false,
|
||||
"mutation_boundary_test": "passed"
|
||||
}
|
||||
},
|
||||
"raw_evidence": {
|
||||
"cursor_http_transcript": "docs/validation/raw/cursor-http-transcript.txt",
|
||||
"live_operations_jsonl": "docs/validation/raw/p4-live-operations.jsonl",
|
||||
"cursor_transcript_sha256": "af9771c27fd2776f9973b4ffaffd9e9b55a8dad05f063148b6f3a6bbd714dd33",
|
||||
"live_operations_sha256": "4df0459ab7e491e2d044c92ff1fa0289986e8ff9208ebd74df5736fbba9d2c95"
|
||||
},
|
||||
"notes": [
|
||||
"The implementation commit is the code revision used to build and deploy the hashes above; later evidence-only documentation commits do not change implementation files.",
|
||||
"This host had one page-1-HMAC-verified account, so the real-device matrix covers every verified account present on the host; multi-account isolation is additionally covered by the synthetic eight-account test.",
|
||||
"The implementation commit is the code revision used to build the control-plane hash above; later evidence-only documentation commits do not change implementation files.",
|
||||
"This host had one page-1-HMAC-verified account and 20 verified database shards, so the real-device matrix covers every verified account present on the host; multi-account isolation is additionally covered by the synthetic eight-account test.",
|
||||
"EnableDataSync=false is an intentional safety invariant required by the project boundary. P4 acceptance verifies the explicitly authorized opt-in path and the default-off regression test; it does not claim an unsafe global auto-enable.",
|
||||
"Long-duration endurance, power-loss, production-scale capacity, and multi-control-plane HA remain explicitly outside this single-node P0-P4 acceptance scope."
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"captured_at": "2026-09-22T10:35:00+08:00",
|
||||
"implementation_commit": "e534e46",
|
||||
"captured_at": "2026-09-22T03:05:00Z",
|
||||
"implementation_commit": "03f6764",
|
||||
"build_manifest": "docs/validation/WxAgent-会话消息同步-P4-build-manifest.json",
|
||||
"raw_evidence": [
|
||||
"docs/validation/raw/cursor-http-transcript.txt",
|
||||
"docs/validation/raw/p4-live-operations.jsonl"
|
||||
],
|
||||
"artifacts": {
|
||||
"control_plane_sha256": "0bd1cd062a87561a6753062696f63a141ca41c89eaf9640e8f299319bf26edc7",
|
||||
"control_plane_sha256": "db8eb86706712f6a6b9de6bcc948ca6999e8cb06dee3664ca1ec76f1a65fc159",
|
||||
"tray_sha256": "72b4aae3347cc054f9787fb84924101346eceed4acc632cab555a754d7260b71",
|
||||
"windows_deployed_tray_sha256": "72b4aae3347cc054f9787fb84924101346eceed4acc632cab555a754d7260b71"
|
||||
},
|
||||
@@ -34,6 +38,14 @@
|
||||
"nonempty_titles": 2,
|
||||
"title_equals_chat_id": 0
|
||||
},
|
||||
"platform_pagination": {
|
||||
"messages_sql": "keyset ORDER BY source_time DESC, message_id DESC",
|
||||
"cursor_identity": "(chat_id, source_time, message_id)",
|
||||
"conversations_sql": "keyset ORDER BY COALESCE(last_activity_at, observed_at) DESC, chat_id ASC",
|
||||
"offset_sql_present": false,
|
||||
"mutation_boundary": "newer message inserted between page requests was not returned on the next cursor page",
|
||||
"raw_transcript": "cursor-http-transcript.txt"
|
||||
},
|
||||
"offline_replay_current_binary": {
|
||||
"control_plane_unreachable": true,
|
||||
"agent_session": 1,
|
||||
@@ -85,25 +97,19 @@
|
||||
"inspect_ui_exit": 0,
|
||||
"smoke_exit": 0,
|
||||
"ui_tree_nodes": 163,
|
||||
"stability_smoke": {
|
||||
"exit": 0,
|
||||
"healthy": true,
|
||||
"sample_count": 7,
|
||||
"message_events": 0,
|
||||
"reconnect_events": 0,
|
||||
"working_set_growth_bytes": 1298432,
|
||||
"handle_growth": 8,
|
||||
"thread_growth": 2,
|
||||
"findings": []
|
||||
},
|
||||
"wechat_version": "4.1.13.65",
|
||||
"windows_build": "10.0.19044.0",
|
||||
"scope": "File Transfer Assistant and approved test chats only"
|
||||
},
|
||||
"default_gate": {
|
||||
"enable_data_sync_default": false,
|
||||
"default_off_test": "DatabaseSyncIsExplicitOptInByDefault",
|
||||
"meaning": "intentional safety boundary; only explicitly authorized accounts are enabled for shadow sync"
|
||||
},
|
||||
"notes": [
|
||||
"The real-device host had exactly one page-1-HMAC-verified account; the real-device matrix therefore covered every verified account present on that host.",
|
||||
"Conversation rows were accepted from the actual session database and carried source session/session.db/SessionTable; no conversation title equaled chat_id.",
|
||||
"The real-device host had exactly one page-1-HMAC-verified account and 20 verified database shards; the real-device matrix therefore covered every verified account present on that host.",
|
||||
"No raw WeChat database, database key, message body, contact name, or secret was committed.",
|
||||
"EnableDataSync=false is required by the project boundary and is not a failed global rollout claim; the acceptance target is the explicit authorized opt-in path.",
|
||||
"Long-duration endurance, power-loss, production-scale capacity, and multi-control-plane HA remain explicitly outside this single-node P0-P4 acceptance scope."
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# 会话消息同步与分账号存储:P4 验收记录
|
||||
|
||||
> 验收日期:2026-09-22(+08:00)
|
||||
> 实现源码提交:`e534e46`;构建/部署映射见 [`P4 build manifest`](WxAgent-会话消息同步-P4-build-manifest.json)
|
||||
> 控制面 SHA-256:`0bd1cd062a87561a6753062696f63a141ca41c89eaf9640e8f299319bf26edc7`
|
||||
> 实现源码提交:`03f6764`;构建/部署映射见 [`P4 build manifest`](WxAgent-会话消息同步-P4-build-manifest.json)
|
||||
> 原始记录:[`raw/`](raw/)
|
||||
> 控制面 SHA-256:`db8eb86706712f6a6b9de6bcc948ca6999e8cb06dee3664ca1ec76f1a65fc159`
|
||||
> Tray SHA-256(Linux 构建与 Windows 部署一致):`72b4aae3347cc054f9787fb84924101346eceed4acc632cab555a754d7260b71`
|
||||
|
||||
## 1. 自动化回归
|
||||
@@ -13,7 +14,7 @@
|
||||
| Web Vite 构建 | 通过 |
|
||||
| Go `go test ./...` | 通过 |
|
||||
| `WxAgent.Core.Tests` | 173/173 通过 |
|
||||
| `WxAgent.Service.Tests` | 25/25 通过 |
|
||||
| `WxAgent.Service.Tests` | 26/26 通过 |
|
||||
| 完整 .NET Release 构建 | 0 警告、0 错误 |
|
||||
| `git diff --check` | 通过 |
|
||||
|
||||
@@ -24,7 +25,7 @@
|
||||
- WAL checkpoint/optimize:维护任务逐账号执行,坏分片错误隔离。
|
||||
- 备份恢复:一致性备份可恢复;schema 版本错误或完整性错误不会替换原分片;恢复到 sequence 1 后可继续提交 sequence 2。
|
||||
- 授权撤销:撤销 scope 后缓存消息查询拒绝,平台查询不会继续暴露历史正文。
|
||||
- 游标/幂等:重复批次、序列冲突、源代次变化和 ACK 状态已有回归覆盖。
|
||||
- 游标/幂等:重复批次、序列冲突、源代次变化和 ACK 状态已有回归覆盖;平台查询使用 `(chat_id, source_time, message_id)` / `(sort_time, chat_id)` keyset cursor,禁止 `LIMIT/OFFSET`。
|
||||
- 现有分片迁移:重新打开旧分片时补建消息观察时间和会话活动索引。
|
||||
|
||||
## 2. 合成压力
|
||||
@@ -57,13 +58,14 @@ SQLite 运行约束:WAL、`synchronous=FULL`、foreign keys、5 秒 busy timeo
|
||||
|
||||
- **真实会话目录**:当前二进制从 `session/session.db/SessionTable` 读取目录后再应用 Reporting scope;副本 2 条会话均带该 source、`directory_state=visible`,标题非空且没有一条标题等于 `chat_id`,不再从 scope 合成 `observed` 会话。
|
||||
- **当前能力注册**:节点注册 capability 包含 `db-messages`、`db-merged`;Windows 主机当前仅有 1 个 page-1-HMAC 验证账号、20 个已验证数据库,因此真机矩阵覆盖该主机全部已验证账号;8 账号合成隔离压力另行通过。
|
||||
- **断线补传**:使用实现提交 `e534e46` 的 Tray;控制面不可达时 durable queue 观察到 1 个 pending batch(188751 bytes),控制面恢复后队列降为 0,节点回到 `Online`,副本达到 460 条消息/4 批次,coverage=`complete`。
|
||||
- **Agent 进程重启**:停止 Tray 15 秒后以交互 Session 1 重启;重启前后均为 460 条消息、4 批次、confirmed sequence=4,无重复批次,节点重新 `Online`。
|
||||
- **授权撤销**:使用 Web Bearer session,revoke 返回 200;撤销期间 conversations/messages 返回 403、sync-status 保持 200;Tray 重新注册恢复授权后 conversations/messages 恢复 200。
|
||||
- **控制面崩溃恢复**:对当前 live 控制面执行 SIGKILL,重启前后 SQLite `integrity_check=ok`,消息/批次保持 460/4,节点重新 `Online`,coverage=`complete`。
|
||||
- **稳定游标分页**:原始 fixture HTTP 记录见 [`raw/cursor-http-transcript.txt`](raw/cursor-http-transcript.txt)。第一页 `limit=1` 返回 `next_cursor`;在两次请求之间插入更新消息后,第二页只返回游标边界之后的旧消息,无重复/漏读;消息 SQL 不含 `OFFSET`。
|
||||
- **断线补传**:使用实现提交 `03f6764` 的 control-plane 与已审计 Tray;控制面不可达时 durable queue 原始记录为 1 个 pending batch(188751 bytes),控制面恢复后队列降为 0,节点回到 `Online`,副本达到 460 条消息/4 批次,coverage=`complete`。
|
||||
- **Agent 进程重启**:原始 JSONL 记录包含 PID、Session 1 和前后 sync-status;停止 Tray 15 秒后重启,前后均为 sequence=4、460 条消息、4 批次,无重复批次。
|
||||
- **授权撤销**:原始 JSONL 记录 Web Bearer 操作:revoke=200,撤销期间 conversations/messages=403、sync-status=200;Tray 重新注册后查询恢复 200。
|
||||
- **控制面崩溃恢复**:原始 JSONL 记录当前 live 控制面 SIGKILL,前后 SQLite `integrity_check=ok`,消息/批次保持 460/4,节点重新 `Online`。
|
||||
- **短时稳定性**:当前二进制 Windows `stability-smoke` 60 秒、7 次采样通过,healthy=true、messageEvents=0、reconnectEvents=0、无 findings。
|
||||
|
||||
测试机 `service.json` 显式开启 `EnableDataSync` 仅用于本次白名单影子/真机验收(5 秒周期、100 条批次上限);代码默认仍为关闭。Reporting 白名单仍只包含文件传输助手、Hao 豪、吉祥三宝和消息测试专用群组范围。
|
||||
`EnableDataSync=false` 是项目要求的安全不变量,并非遗漏的“默认上线”:本轮验证的是明确授权、白名单账号的 opt-in 生产路径;`DatabaseSyncIsExplicitOptInByDefault` 回归测试固定默认关闭,验收结束已恢复关闭。Reporting 白名单仍只包含文件传输助手、Hao 豪、吉祥三宝和消息测试专用群组范围。
|
||||
|
||||
## 5. Web 平台副本验收
|
||||
|
||||
@@ -76,7 +78,7 @@ SQLite 运行约束:WAL、`synchronous=FULL`、foreign keys、5 秒 busy timeo
|
||||
|
||||
## 6. 本轮边界与后续运维专项
|
||||
|
||||
本轮 P0–P4 的目标是单控制面、本机 SQLite、一个已验证白名单账号的可恢复同步和 Web 切换;下列项目不属于本阶段部署边界,不能被本记录误读为已承诺的生产能力:
|
||||
本轮 P0–P4 的目标是单控制面、本机 SQLite、当前主机全部已验证白名单账号的可恢复 opt-in 同步和 Web 切换;`EnableDataSync` 的全局默认关闭是安全边界,不把未授权账号自动纳入同步。下列项目不属于本阶段部署边界,不能被本记录误读为已承诺的生产能力:
|
||||
|
||||
- 24 小时/7 天长期稳定性和大规模生产账号容量曲线。
|
||||
- 生产环境硬杀进程/断电期间的 WAL 恢复演练。
|
||||
@@ -84,4 +86,4 @@ SQLite 运行约束:WAL、`synchronous=FULL`、foreign keys、5 秒 busy timeo
|
||||
- 所有微信版本、所有数据库分片布局及 page 1 HMAC 失败样本的真机矩阵。
|
||||
- 生产备份介质上的异机恢复与定期恢复演练。
|
||||
|
||||
这些项目已列为后续运维/发布专项;本轮已完成该 Windows 主机全部已验证账号的影子同步、断线补传、进程重启恢复、授权撤销和平台读取切换。旧 `/v1/reads/*` 仍保留,代码默认同步关闭;不得把单主机账号证据扩大成生产容量承诺。
|
||||
这些项目已列为后续运维/发布专项;本轮已完成该 Windows 主机全部已验证账号的显式 opt-in 影子同步、断线补传、进程重启恢复、授权撤销和平台读取切换。旧 `/v1/reads/*` 仍保留,代码默认同步关闭;不得把单主机账号证据扩大成生产容量承诺。
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# P4 原始验收记录
|
||||
|
||||
这些文件不是摘要:它们是由可重放脚本或现场命令直接生成的脱敏记录。
|
||||
|
||||
- `cursor-http-transcript.sh/.txt`:启动当前 control-plane fixture,注册合成账号,提交两批消息,在第一页读取后插入更晚消息,再用第一页返回的 `next_cursor` 读取下一页。第二页只包含游标边界之后的旧消息,证明 `(source_time, message_id)` keyset 分页不会被插入的新消息推移。
|
||||
- `p4-live-operations.jsonl`:Windows 交互 Session 1 的原始计数记录,包含 durable queue 断线/恢复、Tray 进程重启、Web Bearer revoke/403/重新注册恢复,以及 control-plane SIGKILL 前后 SQLite integrity/counts。只保留 PID、Session、HTTP 状态和计数,不包含正文、联系人名称、密钥或 token。
|
||||
|
||||
## 复核方式
|
||||
|
||||
```bash
|
||||
# 复核游标 HTTP 记录(需要 Go、curl、jq、python3)
|
||||
(cd control-plane && go build -o /tmp/wxagent-control-plane-cursor ./cmd/wxagent-control-plane)
|
||||
CONTROL_PLANE_BIN=/tmp/wxagent-control-plane-cursor \
|
||||
docs/validation/raw/cursor-http-transcript.sh
|
||||
|
||||
# 核对现场记录的 JSONL 格式和跨事件不变量
|
||||
jq -c . docs/validation/raw/p4-live-operations.jsonl
|
||||
docs/validation/raw/verify-p4-live-operations.sh
|
||||
```
|
||||
|
||||
现场记录对应实现提交 `03f6764` 的 control-plane 构建;Windows Tray 仍使用已审计的 `e534e46` 自包含构建。`EnableDataSync=false` 是项目安全边界要求的默认值;现场只对明确授权、白名单账号临时开启,验收后已恢复关闭。
|
||||
@@ -0,0 +1,7 @@
|
||||
{"event":"offline-queue","utc":"2026-09-22T03:01:56.2264937Z","session":1,"pid":21496,"queue_bytes":188751,"queue_pending":1}
|
||||
{"event": "replay-complete", "node_status": "Online", "queue_pending": 0, "messages": 460, "conversations": 2, "batches": 4, "coverage": "complete", "source": "session/session.db/SessionTable"}
|
||||
{"event":"agent-restart","stopped_seconds":15,"process":{"pid":6588,"session":1},"before":{"state":"complete","sequence":4,"messages":460,"batches":4},"after":{"state":"complete","sequence":4,"messages":460,"batches":4,"node_status":"Online"}}
|
||||
{"event":"authorization-revoke","web_revoke_status":200,"conversations_status":403,"messages_status":403,"sync_status":200}
|
||||
{"event":"authorization-restore","process":{"pid":18620,"session":1},"conversations_status":200,"messages_status":200}
|
||||
{"event": "control-plane-before-sigkill", "pid": null, "integrity": "ok", "messages": 460, "batches": 4}
|
||||
{"event": "control-plane-after-restart", "pid": 1184436, "node_status": "Online", "integrity": "ok", "messages": 460, "batches": 4}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
file=${1:-"$(dirname "$0")/p4-live-operations.jsonl"}
|
||||
|
||||
jq -s -e '
|
||||
map({(.event): .}) | add as $e |
|
||||
($e["offline-queue"].queue_pending > 0 and $e["offline-queue"].queue_bytes > 0 and $e["offline-queue"].session == 1) and
|
||||
($e["replay-complete"].queue_pending == 0 and $e["replay-complete"].node_status == "Online" and $e["replay-complete"].coverage == "complete") and
|
||||
($e["agent-restart"].process.session == 1 and $e["agent-restart"].before.sequence == $e["agent-restart"].after.sequence and $e["agent-restart"].before.messages == $e["agent-restart"].after.messages and $e["agent-restart"].before.batches == $e["agent-restart"].after.batches and $e["agent-restart"].after.node_status == "Online") and
|
||||
($e["authorization-revoke"].web_revoke_status == 200 and $e["authorization-revoke"].conversations_status == 403 and $e["authorization-revoke"].messages_status == 403 and $e["authorization-revoke"].sync_status == 200) and
|
||||
($e["authorization-restore"].process.session == 1 and $e["authorization-restore"].conversations_status == 200 and $e["authorization-restore"].messages_status == 200) and
|
||||
($e["control-plane-before-sigkill"].integrity == "ok" and $e["control-plane-after-restart"].integrity == "ok" and $e["control-plane-before-sigkill"].messages == $e["control-plane-after-restart"].messages and $e["control-plane-before-sigkill"].batches == $e["control-plane-after-restart"].batches)
|
||||
' "$file" >/dev/null
|
||||
printf 'P4 live operation invariants: PASS\n'
|
||||
Reference in New Issue
Block a user