# 故障排查（给 AI 助手读）

> **把这份文件交给你的 AI 助手，让它按步骤处理。**
> 本文件是给**AI** 读的，不是给人读的：不解释原理、不给截图、不让人"打开某个菜单看看"。
> 每一节都是「触发条件 → 可执行的检查 → 结果对应的处置」。
>
> 适用范围：**中国大陆网络下，某个预警源连不上、数据不更新、或提醒不响**。
> 本机开发环境的出口在日本，大陆可达性无法事先验证，所以这份文档不猜"哪个域名被墙"，
> 只让你用可执行的检查**分类**，再选正确的处置。

适用版本：dsh-quake-alert **1.0.2**（入口从 0.4.1 起逐步补齐，各节标注了引入的版本）。
文档里的 `http://127.0.0.1:3080` 是 DSH 默认 Web 端口，
若用户改过端口，用实际端口替换（可从 `dsh web` 的启动输出或浏览器地址栏读到）。

---

## 0. 先确定影响范围（永远从这一步开始）

本插件的十个源走**不同域名、不同通道**，互不牵连。不要笼统回答"连不上"。

| 链路 | 通道 | 域名 |
|---|---|---|
| 日本 地震 / EEW / 海啸 | WebSocket（浏览器直连） | `api.p2pquake.net` |
| 日本 气象灾害（泥石流 / 洪水 / 大雨 / 高潮） | Host 轮询 feed | `www.data.jma.go.jp` |
| 全球 地震（实时） | WebSocket（浏览器直连） | `www.seismicportal.eu` |
| 全球 地震（目录，含修订） | Host 轮询 feed | `earthquake.usgs.gov` |
| 全球 海啸 | Host 轮询 feed | `www.tsunami.gov` |
| 大陆 地震预警 / 速报 | Host 常连 WebSocket → SSE 推给页面 | Wolfx 中转（`ws`） |
| 大陆 气象（暴雨 / 地质灾害） | Host 轮询 + 详情页 | `www.nmc.cn` |
| 美国 洪水 / 山洪 / 沿海洪水 | REST（浏览器直连，按关注点） | `api.weather.gov` |
| 加拿大 降雨 / 洪水 / 风暴潮 | REST（浏览器直连，按关注点） | `api.weather.gc.ca` |

（十个源 = 上表九行里的 Wolfx 地震两路算两个源。0.6.2 补齐了 0.6.0 漏掉的两行——此前
正文说"十个源"而表里只有五条，排障时会漏掉海外那两条链路。）

**要交付给用户的结论必须长这样**：「日本地震与 EEW 正常；日本气象灾害的源在当前网络不可达，
这不是你能修的，影响范围是泥石流 / 洪水 / 大雨 / 高潮这四类」。

---

## 1. 读 Host 侧的健康计数（最快、最准的一步）

Host 半边为每个轮询源维护健康计数，再通过一个本地只读路由对外提供。

### 检查

```sh
curl -s "http://127.0.0.1:3080/dsh-quake-alert/feed?source=jma&since=tail&stats=1"
curl -s "http://127.0.0.1:3080/dsh-quake-alert/feed?source=usgs&since=tail&stats=1"
curl -s "http://127.0.0.1:3080/dsh-quake-alert/feed?source=noaa&since=tail&stats=1"
```

（`source` 只能是 `jma` / `usgs` / `noaa`；写错会返回 `400 {"error":"unknown source"}`——这是 0.4.1 起的行为，
**不会**悄悄退回 jma。）

Win32 上没有 curl 时用 PowerShell：

```powershell
Invoke-RestMethod "http://127.0.0.1:3080/dsh-quake-alert/feed?source=jma&since=tail&stats=1" | ConvertTo-Json -Depth 5
```

### 读结果的对照表

响应形如 `{ "source": "jma", "cursor": …, "entries": [], "stats": { … } }`。**只看 `stats`**：

| 现象 | 分类 | 处置 |
|---|---|---|
| 命令本身失败 / 连接被拒 | DSH 没在跑，或端口不对 | 让用户确认 `dsh web` 正在运行并核对端口。若换了端口，本节所有 URL 都要换 |
| `stats.polls === 0` 且 `stats.idleSkips` 在涨 | **按需轮询在节流**（10 分钟内没有浏览器页面读 `/feed`） | 打开 DSH 页面（插件会每 15 秒读一次），等 10–20 秒再查 |
| `stats.errors` 持续增长、`stats.lastPollAt` 不再前进 | **不可达 / 握手被中断**（第 2 节继续定位） | 见第 2 节 |
| `stats.errors` 为 0，`stats.lastPollAt` 在前进，`stats.feedEntries` 正常 | 链路正常 | **不要报故障。** `feedEntries` 里大多数电文与本插件无关（天气预报等），`received` 少是正常的 |
| `stats.stale === true` | **上游数据停更**（源在响应，但给的是旧数据） | 不可降级。如实说明，等上游恢复 |
| `stats.detailDropped > 0` | 详情电文反复抓取失败后已放弃 | 说明"有几条电文没取到"，并检查第 2 节 |
| `stats.lastError` 非空 | **当前**这条故障的原因（含 `HTTP 4xx/5xx`、`ECONNRESET`、`响应体过大` 等），`stats.lastErrorAt` 是它记下的时刻。语义是"现在还不正常"，不是"曾经出过错"：重连成功（onopen）与"一整帧正常解析"都会清零 | 按关键字对照第 2 节。**`lastError` 非空但 `messages` 在涨 = 一条已经自愈的旧文案**——1.0.1 之前这条字段只写不清，界面会永久停在「链路降级」（现在 Host 侧清零，`12c` 也只在 10 分钟内的错误上判降级） |
| `stats.lastSchemaNote` 非空 | 最近一次**结构观测**（个别条目字段缺失 / 事件时间认不出）。不是故障：上游常态，只作可见性留档 | 不要据此报故障；条数看 `itemSkipped` / `unparseableTime` |

> `stats` 里的 `cursor` / `dropped` / `seenSize` / `bufferSize` 是内部读取位置与缓冲状态，
> 只在判断"是否有增量缺口"时用，不要拿来当故障证据。

---

## 2. 判断"是网络不可达，还是源/中间层的问题"

### 2.1 本机能不能直达源站

```sh
curl -sS -m 20 -o /dev/null -w 'jma   %{http_code} %{time_total}s\n' https://www.data.jma.go.jp/developer/xml/feed/extra.xml
curl -sS -m 20 -o /dev/null -w 'usgs  %{http_code} %{time_total}s\n' https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson
curl -sS -m 20 -o /dev/null -w 'noaa  %{http_code} %{time_total}s\n' https://www.tsunami.gov/events/xml/PHEBAtom.xml
```

（Windows 用 `curl.exe`，Win10 1803+ 自带；或 `Invoke-WebRequest -TimeoutSec 20`。）

| 结果 | 分类 | 处置 |
|---|---|---|
| `200`，几百毫秒内 | 本机能直达 | 问题在 Host 侧（回第 1 节看 `stats.lastError`），或只是节流 |
| 卡住到超时（`28` / `Operation timed out`） | **连接被丢包 / 阻断** | 不可降级。见第 3 节的影响说明 |
| `Connection reset by peer`（`56`） | **连接被重置**（典型的 SNI / 中间设备行为） | 同上 |
| `Could not resolve host`（`6`） | **DNS 失败** | 先修 DNS：`nslookup www.data.jma.go.jp`、换公共 DNS 后重试 |
| `403` / `429` | **被限流 / 封禁** | 不要自动重试。JMA 因重复下载封 IP，插件的架构已防（Host 单点 + 不重复获取），若仍 403 请停手并如实告知 |
| `200` 但内容是 HTML | **响应被替换成拦截页** | 插件会把它判为"数据格式异常"（蓝点）。不要建议关闭证书校验 |

### 2.2 DNS 与 TLS 的分层确认

```sh
nslookup www.data.jma.go.jp
curl -v -m 20 https://www.data.jma.go.jp/developer/xml/feed/extra.xml 2>&1 | head -40
```

- `nslookup` 给不出地址、或给出明显异常地址 → **DNS 层**问题。
- 地址正常、`curl -v` 停在 `TLS handshake` / `Client hello` → **TLS 被中断**。
- 地址正常、TLS 完成、但响应码/内容不对 → **源侧或中间层改写**（第 2.1 节的 403/HTML 两类）。

---

## 3. 处置：能降级的降级，不能降级的如实说

**在给出建议前，先确认这条降级路径在当前版本确实可用**——不要建议尚未实现的能力。

### 3.1 现在就能用的：天然降级（分链路说明）

十个源互不牵连，所以正确回答是**逐条说明**：

- `api.p2pquake.net`（日本地震 / EEW / 海啸）不可达 → **日本的地震与 EEW 全没了**，这是最严重的一种；
  明确告知，不要淡化。
- `www.data.jma.go.jp`（日本气象灾害）不可达 → 地震 / EEW / 海啸仍然正常；受影响的是
  泥石流 / 洪水 / 大雨 / 高潮这四类。
- `earthquake.usgs.gov` 不可达 → EMSC 的实时推送可能仍然正常（不同域名）；USGS 的**修订版**会缺。
- `www.tsunami.gov` 不可达 → 全球海啸完全没有；日本海啸（P2PQuake）不受影响。
- `www.seismicportal.eu` 不可达 → 全球地震只剩 USGS 目录（延迟更大，2 分钟一轮）。
- `api.wolfx.jp`（中国大陆地震预警 / 速报）不可达 → 日本与全球链路不受影响；大陆的**两条**
  源会同时断（它们共用同一个域名与中继）。这时可以先试设置页的**手动降级开关**（强制走 HTTP
  轮询 `/feed?source=cenc_*`）；若轮询同样不通，就是该域名在当前网络不可达，不是插件能修的。
- `www.nmc.cn`（中国大陆气象预警：暴雨 / 地质灾害）不可达 → 与上面所有链路都不牵连；
  受影响的是这两类预警的播报与历史记录。它走的是普通 HTTPS 轮询，所以**没有可切换的降级通道**
  （不像 Wolfx 那样有 WS → 轮询的出口），只能等网络恢复。
- `api.weather.gov`（美国洪水 / 山洪 / 沿海洪水）不可达 → 与上面所有链路都不牵连；受影响的是
  美国境内关注点的洪水类预警。它是**浏览器直连**的（不经 Host），所以要看设置页「测试与诊断」
  里「源状态」的 NWS 行与诊断快照的 `overseas` 段，而不是 `/feed`。注意两类**不是故障**的常见状态：
  ① 关注点不在美国覆盖范围内 → 状态行写「关注点都不在美国源的覆盖范围内」（不发请求）；
  ② 上游对覆盖外的坐标回 `400` → 记成「被上游拒绝」并冷却 1 小时，**状态仍是 open**。
- `api.weather.gc.ca`（加拿大降雨 / 洪水 / 风暴潮）不可达 → 同样与其它链路不牵连，同样是
  浏览器直连。ECCC 还有两种情况容易让人以为它没工作：① **它只发气象预警与风暴潮，不管河川洪水**
  （那由省级机构发布）；② 它的 `status_en` 判不出"解除"，所以**永远不会补一条作废提醒**。
  另外当前季节若没有降雨类样本，`check-contracts` 的在线检查对它天然偏弱（记为 empty）。

### 3.2 当前版本**没有**的降级：不要建议

> 0.5.2 注：本节原先的第一条是「"把 WS 降级成 HTTP 轮询"开关尚未落地」。**它已在 0.5.0 落地**
> ——四条自动降级判据 + 设置页的强制开关都是现成能力（见 3.1 与文末入口表）。不要再把它
> 当成"没有的降级"介绍给用户。

- **代理支持**：插件不读 `HTTPS_PROXY`，也不支持配置代理。Node 的原生 `fetch`（undici）
  **不读**环境变量里的代理设置，所以**不要**建议"设个环境变量就好"。
  可靠的做法只有**系统级 / 路由器级透明代理（TUN 模式）**——那超出插件范围，由用户自行决定。

### 3.3 不可降级：如实告知，不要制造"能修"的错觉

- 上游停更（`stats.stale`）。
- 源改版导致的"数据格式异常"（蓝点）——用户处理不了，等插件更新；设置页有**重试**按钮，
  但那只在"源把字段改回来"时有意义。
  > 0.5.3 起蓝点的行为有两处变化，排查时要如实告诉用户：① **单条坏数据不再点亮蓝点**——线上
  > 是逐条 entry，上游一条格式不合法的数据不该让整个源变蓝，现在是"同一失败原因 10 分钟内累计 ≥5 条"
  > 或"连续 10 条全失败"才升级；② 蓝点**跨刷新存活**（它表达的"等插件更新"与刷新页面无关），
  > 同时**24 小时没有复现会自动清除**。所以"昨天看到的蓝点今天没了"既可能是插件更新修好了，
  > 也可能是自愈了——诊断快照里的 `dataHealth` 会写清是哪一种。
- 被限流 / 封 IP。
- 域名在当前网络不可达。

这四类都要明确说：**这不是你能修的**，并给出影响范围。

---

## 4. 症状：不是"连不上"，而是"提醒不响"

### 4.1 先看侧边栏状态点与设置页

> **设置页的导航（0.8.1 起）**：面板分五个选项卡——**地区 / 灾害 / 通知 / 履历 / 其他**，一次只渲染
> 一页，默认落在「地区」。排障面统一在 **「其他」→「测试与诊断」**；关注点（含全球 / 大陆 / 海外）
> 在「地区」；阈值在「灾害」；提示音与静默时段在「通知」；被压掉 / 未播报的记录在「履历」。
> 下面出现的路径都按这套写。

- 侧边栏底部一个圆点：**绿** = 全部正常 / **黄** = 连接中、重连中或链路降级 /
  **中灰** = 数据已过期 / **蓝** = 数据格式异常（等插件更新）/ **红** = 已停止或无法连接 /
  **灰空心** = 用户关掉了。悬停显示的是聚合状态与未达播报级别的气象警报；逐源明细在下面那处。
- **设置 → 灾害预警 → 其他 → 测试与诊断 → 源状态**：逐源的连接状态、增量条数、失败次数、增量缺口、最近拉取时间。
  （面板顶部还有一条**常驻状态条**：状态圆点 + 结论 + 已收到条数，切页不变。）

把这两处的**原文**复制给 AI，比截图更有用。

### 4.2 常见的"看起来没坏，其实不响"

| 症状 | 先查 | 说明 |
|---|---|---|
| 有地震但没提醒 | 设置 → 灾害 → 提醒阈值；设置 → 地区 → 关注地区 | 震度低于阈值 / 未关注该县都属"未达档位"：**既不播报、也不进历史**（0.9.4 起；此前会留下灰色条目）。诊断快照里仍能查到它们被处理过 |
| 全球地震没提醒 | 是否配置了「全球关注点」 | **未配置任何关注点时，全球源的消息直接丢弃**（既不入历史也不提醒）。这是有意的，避免每天几十条无关远震刷屏 |
| 页面开着但完全没声音 | 设置 → 通知 → 通知与声音：是否显示"提示音尚未解锁" | 浏览器要求先有一次页面交互才能出声。点一下页面任意位置即可 |
| 后台标签页不弹系统通知 | 通知权限 | 需要点"测试系统通知"授权；未授权时回退为页内提示 |
| 静默时段内没响 | 静默时段的起止时间 | 静默期间命中的预警仍记历史，只是不响铃；红色等级（EEW / 大海啸 / 震度6弱以上 / L4 以上气象）默认可穿透 |
| 气象警报只到 L3 没响 | 这是设计 | 播报边界固定为警戒レベル4 以上；L1〜L3 既不播报、也不进历史（0.9.4 起），L3 命中会在侧边栏悬停里提示一行 |

### 4.3 用测试按钮区分"链路坏"与"通知坏"

设置页有两个**不发任何网络请求**的本地测试按钮：

- **发送测试全球警报（轮换场景）**：构造 EMSC / USGS / NOAA 三种源格式的原文，走真实解析与匹配。
- **发送测试气象警报（轮换场景）**：构造 5 种气象电文。

按钮下方的提示会**如实**写出实际结果（已播报 / 未播报及原因）。据此二选一：

- 测试能播报 → 链路与通知都是好的，问题在"没有满足条件的数据"或具体某个源不可达。
- 测试不播报 → 通知 / 阈值 / 关注点配置有问题（提示里写了原因），与外网无关。

---

## 5. 收尾：给用户的结论模板

AI 完成上述检查后，按这个结构回答：

1. **分类**：属于哪一类（不可达 / 格式异常 / 停更 / 限流 / 本地问题 / 正常无数据）。
2. **证据**：贴出用到的命令与关键输出（`stats` 字段、curl 状态码）。
3. **影响范围**：哪几条链路还能用、哪几条不行。
4. **处置**：能降级的降级（并说明该降级在当前版本是否可用）；不能降级的**明确说这不是用户能修的**。
5. **不建议做的事**：不要建议关闭证书校验、不要建议高频重试、不要建议尚未实现的降级开关。

---

## 6. 症状：界面语言 / 配置导入

这两块都是 0.9.0 加进来的，排障时先分清「配置里的值」与「界面上的表现」。

### 6.1 界面语言

| 观察 | 判断 | 处置 |
|---|---|---|
| 改了语言**立刻**变 | 正常（设计如此，**不需要重启**） | — |
| 要刷新 / 重启才变 | 缺陷 | 报给开发者。0.9.0 有一类缺陷就是「某处文案在模块加载时就被固化」 |
| 一半跟着切、一半不切 | 缺陷 | 同上。0.9.1 起有渲染冒烟守着（4 语言 × 5 页签扫漏翻），再出现说明是新加的一处 |
| **下拉写着一种语言、整页却是另一种** | 缺陷（0.9.3 修过一例） | 报给开发者。0.9.3 修掉的是「Host 权威配置里的语言从不落到界面」：`bindSettingsScope` 的 `sync()` 只更新了配置与镜像，没有 `setLanguage`，于是在另一个浏览器 / 清过 localStorage / 手改过 `settings.yaml` 的路径上下拉与界面长期不一致，且不会自愈 |

检查点：诊断快照里的 `config.language` 是**配置里的值**；`localStorage` 的 `dsh.quakeAlert.v1`
里也有一个 `language`（本地镜像）。两者不一致时以 Host 的条目配置为准（它是权威，镜像只是备用）。
**0.9.3 起这一项与界面应当永远一致**（三条写入路径都会 `setLanguage`）；若 `config.language` 与
界面文案对不上，那是缺陷而不是"配置还没生效"。

**解析层的 `kindLabel` 与通知正文（`地震情报・各地震度` / `海啸警报` 这类）是既有的"不翻"项**
（0.9.0 登记、0.9.3 未扩大范围），所以英文 / 繁体界面下这几处会显示简体中文——见 CHANGELOG 的
`[0.9.3]` 有意不做。取数层的自由文本（`store.detail` 的"久无数据…正在重连"之类）同属该项。

**源文本不跟着界面语言走**是有意这样做的（正文、地名、气象厅的分类名），不是漏翻——用户看哪个国家的源，
就看那个国家的语言。所以英文界面下出现「英文模板 + 日文地名」不需要排查。

### 6.2 配置导出导入

导入失败时界面会给**具体原因**，对照处置：

| 提示 | 含义 | 处置 |
|---|---|---|
| 不是有效的 JSON | 文件损坏或被别的程序改写 | 重新导出。0.9.1 起**带 BOM 的文件可以直接导入**（记事本 / PowerShell 重存过的那种） |
| 这不是灾害预警插件的配置文件 | 选的不是本插件的导出文件 | 换文件 |
| 文件来自更新版本的插件 | 文件里的格式版本高于当前插件 | 升级插件，或让对方用当前版本重新导出 |
| **当前环境无法保存备份…导入已取消** | 浏览器存储不可用（隐私模式 / 配额满 / 被策略禁用） | **配置没有被改动**——这是有意的：没有备份就不做不可逆的整体替换。换一个允许存储的环境再导入 |
| 导入过程中出错了 | 兜底提示（附具体错误） | 把提示原文报给开发者 |

- **撤销**：「设置 → 其他 → 配置导出与导入 → 撤销上次导入」。**只保留最近一次**导入前的配置；
  连着导入两次之后，第一次导入前的配置就找不回来了。
- **导出的是配置本身**（关注点 / 阈值 / 语言 / 数据源 / 静默时段 / 通知开关），**不含**履历与源
  健康记录——这是有意这样做的，不是丢数据。

---

## 附：本文档引用的入口一览

| 入口 | 用途 | 状态 |
|---|---|---|
| `GET /dsh-quake-alert/feed?source=jma\|usgs\|noaa&since=tail&stats=1` | 读 Host 侧健康计数 | 0.4.1 可用 |
| `GET /dsh-quake-alert/feed?source=cenc_eew\|cenc_eqlist&since=tail&stats=1` | 读大陆源的 WS 健康计数（`connected` / `messages` / `reconnects` / `lastError` / `lastErrorAt` / `lastSchemaNote` / `dataTime` / `stale` / `ageSkipped`）。`stale` 由时钟推动，中继真停更时也会变 true；`lastError` 是「**当前**故障」，恢复即清零（见第 1 节） | 0.5.0 可用，1.0.1 加时间戳与结构观测 |
| `GET /dsh-quake-alert/stream?source=cenc_eew\|cenc_eqlist` | 大陆源的 SSE 推送（`event: sync` 第一条数据给出读取位置、缓冲状态与数据健康；此后每 15 秒一帧 `event: status`，兼作 keep-alive 并承载"中继停更"） | 0.5.0 可用 |
| `GET /dsh-quake-alert/feed?source=nmc_alarm&since=tail&stats=1` | 读大陆**气象**源（中央气象台 nmc.cn）的健康计数与条目载荷。载荷是 JSON：`alertid` / `title` / `issued` / `kind`（rainstorm / geology）/ `level`（red / orange / yellow / blue）/ `detail`（只有橙 / 红才有正文）。`stats.stale` 由**列表里最新一条的发布时间**推动（超 3 小时），`stats.errors` 增长说明列表请求失败或响应结构变了 | 0.5.2 可用 |
| `GET /dsh-quake-alert/areas` | 验证本地回环 webServer 是否活着（返回市区町村表 / 河川予報区域表 / 中国行政区划表 / 国家地区清单） | 可用 |
| `GET /dsh-quake-alert/areas?country=US` | 取某一国的城市包（0.8.0，人口 10 万以上的城镇，不含日本与中国）。未知国家码答 **404**——"这个国家没收录"与"国家码写错"据此分开 | 0.8.0 可用 |
| 设置页「其他 → 测试与诊断」里的「源状态」区块 | 逐源状态、增量、失败、缺口、最近拉取 | 0.4.1 可用（0.8.0 起该区块在「测试与诊断」下，0.8.1 起该页在「其他」选项卡里） |
| 侧边栏状态点悬停 | 聚合状态 + 未达播报级别的气象警报提示（逐源明细已并入上一行的「源状态」区块） | 0.4.1 可用，0.10.3 起不再逐源列明细 |
| `node scripts/check-wolfx-live.mjs` | 直接连 Wolfx 复验：建连 / query 指令 / 数据新旧，并按 DESIGN 11.5 的类别给出结论 | 0.5.0 可用，**仅仓库内**（`scripts/` 不入发行包） |
| `node scripts/check-cn-e2e.mjs` | Host↔Client 端到端复验：真实 Wolfx → 真实 HTTP SSE → Client 契约 | 0.5.0 可用，**仅仓库内**，需能连上 Wolfx |
| `node scripts/check-contracts.mjs` | **契约检查**：拉 10 个源的真实数据过一遍解析器，看上游是否改版（`schema` / `value` → 退出码 1，并打印契约里的 `required` 原文供对照）。网络不可达**不算失败**（只有"上游改版"值得处理）。`--offline` 用 `samples/` 的快照跑，不联网 | 0.5.3 可用，**仅仓库内**（CI 里也跑：手动触发 + PR） |
| 设置页「测试与诊断」里的大陆源行 | 显示**当前链路模式**（SSE 推送 / 已降级为轮询 / 已关闭）与收到、失败、降级次数 | 0.5.0 可用 |
| Client 侧只读诊断快照 | 设置页「其他 → 测试与诊断」一键生成并复制：聚合状态、逐源状态与数据健康、增量计数、大陆源链路模式、**被跨源优先源压掉的条数**、关注点摘要（含每个点的来源分支 `origin`，大陆点还有 `province` / `city`）、最近几条记录为什么没响铃。快照格式版本 **3**（0.8.2 起；2 = 0.8.0/0.8.1） | 0.5.0 可用，0.8.0 加 `authority` 段，0.8.2 加省市与提号 |
| WS → HTTP 轮询降级开关 | 中间设备重置长连接时的降级路径 | Host 侧**已可用**（`/feed?source=cenc_*`）；Client **已自动降级**（EventSource 不可用 / 连续拿不到第一条数据 / 连上不推流）；手动强制开关在设置页「大陆源链路」 | 0.5.0 可用 |
| 锁屏 / 休眠过夜后一直黄灯「链路降级」 | **1.0.1 起自愈**：恢复时主动换一条连接、不把冻结判成静默死亡，自动降级也会复探回 SSE。若仍停在黄灯，按顺序看三处——① 「源状态」里的大陆源链路模式（`已降级为轮询` = Client 那条；`SSE 推送` 但仍黄 = 看 next）；② 该源 `lastError` 与 `lastErrorAt`（**旧文案** = Host 侧未清零，**新鲜文案** = 真故障）；③ `connected === false` 或 `messages` 不再上涨 = 中继真的断了，按第 1 节查 Host | 1.0.1 可用 |
| 插件内代理支持 | 需要走代理的网络 | **0.5.x 补齐** |
| 设置页「测试与诊断」里的海外源行 | 海外源是 **Client 直连**（不经 Host），所以没有 `/feed` 路由可查。这一行显示：已查询轮数 · 请求次数 · **响应条目数** · 交给主链条数 · **过老只记历史** · **被上游拒绝** · 上游结果被分页上限截断的轮数 · 超上限跳过 · 失败次数 · 最近查询时间。「响应条目数」比真实预警条数大（NWS 按 5 个采样点各查一次，同一批会被重复计入）；「被上游拒绝」涨说明有关注点被上游拒了（例如多伦多不在美国 NWS 内，NWS 对覆盖外坐标回 400——那不是故障），**代码与文案都不替上游断言"这个点不在覆盖范围"** | 0.6.0 可用，0.6.1 订正字段与文案 |
| 诊断快照里的 `overseas` 段 | 与 `feed` / `streams` 并列的一张表，逐源给出 `requests` / `received` / `applied` / `rejected` / `ageSkipped` / `truncated` / `gated` / `throttledLast` / `throttledTotal` / `lastDataAt` / `lastError`。**`ageSkipped` 解释"我看到预警但没响"**（打开页面时已发布超 6 小时、或页面休眠超 30 分钟后恢复，只记历史）；**`gated` 是"进入过几次首轮 / 休眠恢复状态"**，不是"门槛激活的轮数"（0.6.1 订正；此前那份注释写的字段名 `uncovered` 在快照里根本不存在，实际是 `rejected`） | 0.6.0 可用，0.6.1 订正 |
| 诊断快照里的 `authority` 段 | **0.8.0 新增**：被跨源优先源压掉了多少条跨源副本（`suppressed`）、按**已播报的那个源**分组（`bySource`——它回答"是不是 USGS 总抢在日本源前面"）、以及最近一条的原因（`lastDetail`）。这些副本**连历史都不进**（同一场地震被多源各报一次会把那 30 条挤满），所以这一段是它们唯一的痕迹：优先源一旦判错（把两场不同地震并成一个 = 真漏报），先看这里。判据是「±2 分钟 + 50 km + 跨机构」——同一机构内部的"预警 → 速报"仍会进历史，不算抑制 | 0.8.0 可用 |
| `node scripts/check-overseas-sources.mjs` | 海外候选源的**调研复现**：四个源的可达性 / 灾种分布 / 体积 / CORS / 空间粒度，与 `DESIGN.md` 4.6 的表格逐条对应。网络不可达不算失败 | 0.6.0 可用，**仅仓库内** |
| `node scripts/capture-overseas-fixtures.mjs` | 重抓海外源的真实 fixture（`samples/nws/`、`samples/eccc/`），用于确认"结构是否变了"。注意 **ECCC 的灾种分布随季节变化** | 0.6.0 可用，**仅仓库内** |

> 这份文档要能照着执行，前提是有可用的诊断入口。0.4.1 落地了 Host 侧的 `stats=1` 与 UI 上的源状态；
> 0.5.0 追加了大陆源的 `stats=1`（含 WS 专属字段）、SSE 的 `sync` / `status` 帧，以及仓库内的
> `scripts/check-wolfx-live.mjs`。Client 侧的只读诊断快照（设置页「测试与诊断」，0.8.0 前叫「诊断」）与手动降级开关
> （`config.cnTransport`）均已落地（见 `DESIGN.md` 11.3 / 11.5）。
> 0.5.2 追加了大陆**气象**源（`source=nmc_alarm`）的 `stats=1`，以及仓库内的
> `scripts/capture-nmc-fixtures.mjs`（重抓真实样本，用于确认"结构是否变了"）。
> 0.5.3 追加了 `scripts/check-contracts.mjs`——它是排查"是不是上游改版了"的**第一入口**：
> 一次跑完 10 个源的真实数据过解析器，比逐个手敲命令再肉眼对字段快得多。诊断快照里的
> `dataHealth` 也扩成了三层（`data` / `fresh` / `counters`），失败次数与数据时间都在里面。
> 0.6.0 追加了**海外源**（美国 NWS / 加拿大 ECCC）：它们是**浏览器直连**的 REST 轮询，
> 所以排障要看的是设置页那一行与诊断快照的 `overseas` 段，而不是 `/feed`。两个已知边界
> 会让它们"看起来没工作"：**没有配对应国家的关注点时不产生任何请求**（状态行会说明去哪里配；
> 0.6.1 起"有点但都不在覆盖范围"与"一个都没配"是两句不同的文案），
> 以及**上游停更判不出来**（按点 / 框查询天然可能为空，能发现的只有请求失败与结构改版）。
> 0.6.1 追加了 `samples/nws/nws-event-chain.geojson` 与 `nws-cancel-chain.geojson`：
> 怀疑"同一场洪水重复响铃"或"取消没提醒"时，用它们对着 `nwsVtecKeyOf` 复算事件键最快。
>
> 0.8.0 追加了**跨源优先源**与**全球城市表**，两处都有新的排查口径：
> · 怀疑"同一场地震响两次"或反过来"某条明明该响却没响"时，先看诊断快照的 `authority` 段——
>   被压掉的副本**不在历史里**，这是它们唯一的痕迹；`config.watch.places[].origin` 会告诉你
>   每个关注点被算作了哪个分支（优先源判错往往就错在这里）。
> · 城市列表拉不起来时，直接打 `GET /dsh-quake-alert/areas?country=US`：**404 = 这个国家没被
>   收录**（表里只有人口 10 万以上的城镇，客户端会退化成手填坐标），不是故障；网络失败才是。
> · 设置页的排障面统一收进了「测试与诊断」：两个本地测试按钮（气象 / 全球）与逐源状态都在那里，
>   不再散落在各个灾种区块下面。
