返回 DeepSeek-Reasonix
DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK.zh-CN.md
根目录 / docs / DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK.zh-CN.md
1 # Windows / Linux Desktop 崩溃诊断运行手册
2
3 <a href="./DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK.md">English</a>
4
5 本手册用于跨平台 Desktop 诊断链路的发布、隐私、性能和根因闭环。Windows build
6 `17763` 是重点实验环境,不是代码白名单;发布诊断版本本身不代表问题已经解决。
7
8 ## 本地 transcript 初始化失败
9
10 创建、打开会话或迁移旧会话时,如果 transcript 初始化失败,可在 Electron 壳的
11 `service.log`(以及轮转文件 `service.log.1`)中搜索
12 `session transcript initialization failed`。服务向 stderr 输出结构化的 `diagnostic`
13 字段组,由壳现有日志链路落盘。该诊断的 `version=1`、
14 `code=transcript_initialization_failed`;不改变迁移账本格式。下文另行说明不含内容的线上
15 报告。
16
17 | 字段 | 含义 |
18 | --- | --- |
19 | `diagnostic.session_key` | 会话 ID 的 SHA-256,用于关联该运行时会话的错误 |
20 | `diagnostic.covered_sequence` | 初始化 transcript 覆盖到的事件序号 |
21 | `diagnostic.baseline_message_count` | 本次选取的输入消息数,最多 96 条 |
22 | `diagnostic.baseline_total_message_count` | 选取尾部窗口前可用的候选消息数,不一定是完整历史总数 |
23 | `diagnostic.baseline.record_count` | 这些输入消息转换生成的 transcript 记录数 |
24 | `diagnostic.baseline.record_index` / `previous_record_index` | 当前基线内从 0 开始的出错记录位置 / 首次冲突位置,仅在已知时记录 |
25 | `diagnostic.baseline.role` | 白名单内的角色,其他值统一为 `other` |
26 | `diagnostic.baseline.record_key` / `message_key` / `tool_call_key` | 标识的 SHA-256 摘要,原标识缺失时为空 |
27
28 `diagnostic.baseline.code` 区分 `duplicate_record_identity`(重复标识)、
29 `missing_record_identity`(缺少标识)、`baseline_encode_failed`(编码失败)和
30 `baseline_decode_failed`(解码失败)。编码、解码失败不记录位置。
31 位置针对本次基线内的 transcript 记录,不是整个会话的消息偏移。
32
33 旧会话迁移还会输出 `desktop session migration transcript initialization failed`,
34 包含同一份诊断、`stage=legacy_import` 和 `source_key`。使用 `source_key` 对照
35 Reasonix 配置目录下 `desktop/session-migration-v5.json` 中已有的 `sourceKey`。
36 即使重试时生成了不同的目标会话 ID,也能据此关联同一迁移来源。每次失败输出一条运行时
37 诊断;在该迁移路径中再输出一条迁移诊断。失败来源保留,正常会话可继续迁移。
38
39 这些新增记录不包含聊天正文、思考正文、工具参数、原始路径、原始标识或任意错误原文。
40 摘要用于关联,本身不能证明用户原始数据的具体触发原因。旧 `1.38.10` 日志无法补回这些
41 字段,需要使用包含此改动的构建复现后重新收集日志。
42
43 同一个已处理的迁移失败还会进入本地待上传队列,作为 `exception` 上报;其
44 `source=desktop.session_migration`、`label=transcript.initialization`,fingerprint hint
45 只包含无正文的错误分类。报告投递到 `https://crash.reasonix.io/v1/report`,并在
46 `/stats/diagnostics` 中展示,继续遵守现有 Desktop telemetry 同意开关、失败重试和按版本
47 去重规则。会话/迁移来源摘要以及记录标识摘要只保留在本地。旧版本未捕获的 panic 继续走
48 已有的 `go.runtime` / `go.fatal` 上报链路,并按高等级崩溃展示。
49
50 ## 发布顺序
51
52 1. Firebase 项目保持 Spark 且不关联 Cloud Billing;只在
53 `asia-southeast1` 创建 Realtime Database,并部署
54 `workers/crash-report/firebase/database.rules.json`,确认客户端读写均被拒绝。不得启用
55 Functions、Firestore、BigQuery、Hosting、Storage 或 Secret Manager。
56 2. 配置仓库 Secret:`FIREBASE_DATABASE_URL`、`FIREBASE_CLIENT_EMAIL` 和
57 `FIREBASE_PRIVATE_KEY`。服务账号必须专用于 crash 投递且仅授予 Realtime Database
58 权限。不得使用 Web Firebase 配置,也不得在 Desktop 产物中包含 Firebase SDK 或配置。
59 3. 冻结唯一候选 SHA,已发布 tag 不得移动或重建。
60 4. 备份 D1,并运行 `npm run migrate:diagnostics-v2`。命令会先检查完整 schema,写入前
61 记录新的 Time Travel bookmark。已退休的 `metric_users` 和 `cli_metric_users` 不再
62 是必需表;活跃 diagnostics 表出现任何 partial 状态时仍然 fail closed。
63 5. 运行 `npm run migrate:firebase-crash`。命令先记录 D1 Time Travel bookmark,再依次
64 应用第一阶段 `migrate-firebase-crash.sql` 与第二阶段
65 `migrate-firebase-crash-capacity.sql`;任一阶段部分完成时 fail closed。验证 outbox、
66 receipt、兼容 lease 表、`firebase_crash_group_state` 及全部投递/生命周期索引。旧 lease
67 表只用于滚动部署兼容。
68 6. 验证 `report_daily`、`report_installations`、
69 `report_event_dimensions`、`diagnostics_meta`、fingerprint/date 索引、ping
70 窗口索引,以及 `installation_linked_since`。
71 7. 在 **Actions > Deploy crash worker > Run workflow** 中选择 `main-v2`,并将
72 **Firebase crash history operation** 设为 `dry-run`。该任务使用现有仓库 Secret,经过
73 `canary` environment 审批,不会部署 Worker;脚本按每页 200 个 fingerprint 的 keyset
74 分页,预计预留必须不超过 700 MiB。确认结果后选择 `apply`,并输入精确确认短语
75 `APPLY_FIREBASE_CRASH_DATA`;任务会在同一 runner 内依次执行 `--apply` 和
76 `--verify-only`。后续独立核验可选择 `verify-only`。已认证的运维人员仍可在本机运行
77 `npm run migrate:firebase-data`、`npm run migrate:firebase-data -- --apply` 和
78 `npm run migrate:firebase-data -- --verify-only`。默认 checkpoint 为权限 `0600` 且已
79 gitignore 的 `.firebase-crash-migration-state.json`;可用 `--checkpoint=<path>` 改路径,
80 只有明确重跑时才用 `--reset-checkpoint`。日志只输出计数、fingerprint 前缀和摘要。
81 8. Worker 先使用 `dual` 模式;用旧 Report/Ping/Metrics、legacy `webview2`、Windows/Linux
82 `webRuntime` payload 做 `channel=test` smoke。
83 9. 连续比较 7 个完整 UTC 日;fingerprint、计数、样本和脱敏结果一致后,才将
84 `CRASH_STORAGE_MODE` 从 `dual` 切换为 `firebase`。Firebase 模式下 D1 只保留聚合、
85 索引和有界 outbox,不再写入新 `reports` 原文。
86 10. 用同一 SHA 生成签名 Windows/Linux 构建;能力矩阵和性能门禁通过后才发布 feature
87 release。
88 11. 再稳定观察 7 天后归档 D1 旧原始样本。保留 `d1`、`dual`、`firebase` 三种回滚
89 模式;Worker 回滚不要求客户端升级。
90 12. 通过管理界面保留审计地整理历史数据:忽略 `[go panic] safe` / `v9.9.9`,将
91 `72daba81` 标记为在 `desktop-v1.19.3` 解决,忽略旧
92 `desktop.abnormal_exit` replay 分组。
93
94 ## Spark 容量、生命周期与回滚
95
96 Worker 固定执行 700 MiB 预留上限:active 每组 640 KiB、compacted 128 KiB、
97 archiving 32 KiB、archived 为 0。达到 80% 时复用现有 webhook 告警并在后台提示;新组或
98 扩容会越过上限时,必须在创建 outbox 前返回 `503`。不得把该上限改为可配置项。
99
100 只有 resolved/ignored 分组参与生命周期:30 天无新事件后,把最近 5 个样本替换为带
101 fencing 的 marker,并保留当前周期首个样本;60 天后 tombstone 全部样本路径,24 小时后
102 条件删除 Firebase group。D1 的计数、状态、备注、聚合和审计继续保留。archived
103 fingerprint 再出现时进入新 sample epoch,累计 count 与 lifetime first-seen 不重置。管理员
104 删除复用同一 tombstone 窗口,并原子删除对应 D1 分组数据。
105
106 回滚只改配置:设置 `CRASH_STORAGE_MODE=d1` 并重新部署。回滚时不要删除 outbox、receipt、
107 group-state 或 Firebase 数据。修复 migration/容量/ETag 问题后,重新执行 dry-run 与
108 `--verify-only`,再切回 `dual`;Desktop/CLI 无需升级。
109
110 ## 隐私与兼容 smoke
111
112 旧 payload 可以缺失所有新增字段;legacy `webview2` 必须归一化为 `webRuntime`。
113 同一 engine/kind/reason/exit code 的恢复成功与失败必须属于同一 fingerprint。还需验证:
114
115 - 原始 install ID 不进入样本、HTML、应用/审计日志、导出和 pending 文件;
116 - 模块只保留 basename;不包含内容、密钥、账号、hostname、完整路径、GPU 型号和驱动;
117 - 重复事件正确累加 daily/install/event-dimension,且早期环境组合不被覆盖;
118 - 删除测试分组会删除三张诊断聚合表的对应数据;
119 - 诊断事实、ping、metric user 按 30 天分块清理;
120 - `channel=test` 始终位于 development namespace。
121 - 重复 `eventId` 返回 `202` 且不重复增加聚合;
122 - Firebase timeout、401、429 或 5xx 会保留 projected outbox,交给每 6 小时重试;
123 outbox 满时返回 `503`,客户端必须保留 pending;
124 - Desktop 自动报告按版本和 dedup key 只成功上传一次,失败不进入 512 条/180 天账本;
125 用户主动提交的 Desktop/CLI 报告不受本地 fingerprint 去重限制。
126
127 ## 正常体验门禁
128
129 候选版本在壳启动前只允许一次本地配置读取、一次非阻塞归属锁和一次小型原子生命周期
130 写入。Runtime 探测及报告/指标落盘必须在壳启动后或有界后台消费者中执行;COM/GTK
131 回调只能非阻塞入队或递增原子丢弃计数。任何诊断失败都必须 fail-open。
132
133 使用同一 SHA 与关闭诊断的基线比较:诊断初始化 p95 不超过 10 ms、p99 不超过 25
134 ms;DOM-ready p95 回归不超过 `max(20 ms, 2%)`;shutdown p95 回归不超过 20 ms;
135 空闲 CPU 增幅小于 0.1 个百分点;RSS 增幅不超过 2 MiB。30 分钟正常使用期间必须是:
136 0 次诊断 reload、0 个轮询 timer、0 个新增弹窗,除已有 ping/metrics 外 0 个额外请求。
137
138 ## 能力认证矩阵
139
140 全过程使用同一候选 SHA。Runtime、GPU 与驱动信息只记录在私密实验表,客户端不采集驱动。
141
142 | 平台 | 必须覆盖 |
143 | --- | --- |
144 | Windows 10 LTSC 2019 `17763` | VM + 实体 GPU;系统及最新 Evergreen WebView2;GPU 开/关 |
145 | Windows 10 `19045` | x64 对照;系统及 Evergreen WebView2 |
146 | Windows 11 稳定版 | x64 实机及当前稳定 Runtime |
147 | Windows arm64 | 正式交叉构建 + 一台设备 smoke |
148 | Ubuntu 22.04 | WebKitGTK 4.0、X11 |
149 | Ubuntu 24.04 | WebKitGTK 4.1、X11、Wayland |
150 | Debian 12、Fedora stable、Arch rolling | 能力 smoke;本地会话;Intel/AMD/NVIDIA 代表性覆盖 |
151 | 远程会话 | Windows RDP 与 Linux remote/xrdp |
152
153 每个环境执行 20 次冷启动/正常退出、10 次更新重启、60 分钟工作负载、50 次最小化/
154 恢复,以及休眠、显示器/DPI、远程连接切换。测试构建可定向终止 renderer/web process,
155 验证只恢复一次。Windows 收集 WER/可靠性监视器,Linux 收集 journal/coredump 元数据。
156 dump/core 仅在用户明确授权后私密传输,并在分析后删除。
157
158 ## 根因和观察闭环
159
160 环境关联至少满足一项:两台同类实验节点复现且对照不复现;或三个不同线上安装命中同一
161 fingerprint,同时该环境至少有 30 个活跃安装,影响率达到对照 3 倍。GPU workaround
162 要求每台 GPU-on 至少 `2/20`、两台 GPU-off 合计 `0/40`,且每台两小时长测为 0。
163 workaround 必须按已有能力/Runtime 证据限定,不能只按发行版名称或 Windows build。
164
165 Integrity failure 转签名、注入和安全软件调查;OOM 转内存与会话资源调查;Runtime
166 聚集才支持后续最低版本或升级策略。renderer 恢复成功不算应用崩溃;只有 lifecycle
167 abnormal exit 时,必须拿到 WER、journal、dump 或 core 之一才能结案。
168
169 上线后观察七个完整 UTC 日:身份覆盖率目标 95%;低于 90% 不展示精确影响率。每天检查
170 legacy replay、fatal/recovered/degraded 数量关系、recovery failure、平台/Runtime/GPU
171 影响率、D1 增长、retention 和查询耗时。证据不足就保持 open 并延长至 30 天。只有根因
172 被证实后才发布定向补丁,修复后实验室要求 `0/40` 复现。
173
173 lines MARKDOWN