文件
hyb-oa/.trellis/tasks/08-23-wecom-public-health-alert-diagnostics-and-debounce/design.md
T

6.7 KiB
原始文件 Blame 文件历史

企微公网健康告警防抖与诊断增强设计

1. Architecture and Boundaries

本任务保持现有“ARM systemd timer → 公网探测器 → 脱敏 summary → 共用企微 helper”的边界,不引入守护进程、数据库或外部状态服务。

  • 探测器负责网络阶段分类、连续样本计数、恢复确认和抖动周期判定。
  • 告警 helper 继续负责 summary 校验、消息文案、Webhook 安全读取和发送侧去重。
  • 安装脚本继续固定 5 分钟 timer、受控路径、root 所有权和默认 disabled。
  • 本地合同测试通过 stub、受控 socket/DNS 失败和测试 capture 验证,不连接真实机器人。

2. Failure Classification Contract

分类优先级固定为:

  1. socket.gaierror 或等价解析失败 → dns_resolution_failed;
  2. TCP 建连拒绝、重置、不可达及其他建连类错误 → tcp_connection_failed;
  3. TLS 证书或握手校验失败 → tls_validation_failed;
  4. socket/请求超时 → request_timeout;
  5. 非 2xx HTTP → http_unavailable;
  6. 响应超过上限 → response_too_large;
  7. JSON、编码或 {data: {ok: true}} 合同不满足 → response_contract_invalid。

异常类型按从具体到通用的顺序匹配,避免 OSError 提前吞掉 DNS、TLS 或超时。对无法进一步安全细分的连接阶段异常归入 tcp_connection_failed,但不把异常文本写入状态、summary 或日志。

3. State Model

3.1 State schema

探测状态升级为显式版本,字段只包含:

  • phase:healthy、failure_pending、alerted、recovery_pending、recovery_watch、flapping;
  • activeCategory / lastRecoveredCategory:稳定类别 token 或 null;
  • consecutiveFailures / consecutiveSuccesses / flapTransitions:有界非负整数;
  • recoveredAt / healthySince:必要时存在的非负 Unix 秒时间戳。

旧版 schema 只含连续失败数和最后类别。部署时采用保守兼容迁移:有效旧状态映射为等价的 healthy 或 failure_pending,不凭旧状态补发消息;非法或未知 schema 继续 fail closed。告警 helper 自身状态 schema 保持向后兼容。

3.2 Normal incident flow

healthy --失败1--> failure_pending --失败2--> alerted(发送公网健康告警)
alerted --成功1--> recovery_pending --成功2--> recovery_watch(发送公网健康恢复)
recovery_pending --失败--> alerted(不发送恢复;持续事件仍去重)

失败阈值统计持续不可用样本,告警使用达到阈值时的最新稳定类别。已告警期间相同类别不重复发送;类别发生变化时沿用现有最多一次更新通知语义。

3.3 Recovery watch and flapping flow

recovery_watch
  --60分钟内同类失败达到2次--> flapping(发送一次链路反复抖动提醒)
  --不同类别失败达到2次-----> alerted(作为新事件发送普通告警)
  --观察窗到期且健康--------> healthy(静默)

flapping
  --任意失败/短时成功-------> flapping(只更新脱敏计数)
  --连续健康满30分钟--------> healthy(静默退出)

“连续健康满 30 分钟”以第一条连续成功样本的时间为起点,期间任何失败都会清空 healthySince。状态转换实现为可直接传入当前时间的纯函数,使合同测试可使用合成时间,不依赖等待真实 30/60 分钟,也不增加生产测试参数。

4. Notification Contract

应用健康事件使用专用标题:

  • FAIL_APP_HEALTH:OA 公网健康告警;
  • PASS_APP_HEALTH_READY:OA 公网健康恢复;
  • FAIL_APP_HEALTH_FLAPPING:OA 公网健康链路反复抖动。

正文只保留状态、稳定类别和操作提示。helper 根据应用健康状态选择专用文案,其他备份/dead-man/提醒状态继续使用原有通用标题。抖动事件采用独立、白名单内的稳定状态 token,防止与普通失败去重键互相覆盖。

状态转换返回“通知候选”而不是自行发送:处于普通故障、已确认恢复或抖动故障时,可在后续同类样本上重复交给 helper 评估,由 helper 的现有状态完成去重;如果上次发送失败且 helper 状态未落盘,下一次样本可以重试。抖动期的健康样本和静默退出不产生恢复候选,由此保证抖动退出不产生额外恢复消息。

5. Data Flow and Failure Handling

  1. 校验 URL、状态目录、状态文件及阈值。
  2. 读取并严格校验/迁移探测状态。
  3. 执行一次 /ready 探测并生成成功或稳定失败类别。
  4. 用当前时间和样本驱动纯状态转换,得到新状态及可选通知候选。
  5. 原子写入 0600 探测状态;有通知候选时才写脱敏 summary 并调用 helper,由 helper 去重或重试。
  6. 探测失败仍返回非零,健康样本返回零;通知发送或本地状态失败继续 fail closed。

状态先落盘再通知,保持当前“状态不可安全保存则不发送误导消息”的边界。若通知失败,命令返回失败并保留可诊断的稳定错误;后续同类样本再次提交候选,helper 仅在自身发送状态尚未落盘时重试,合同测试必须证明成功发送后不会重复。

6. Compatibility and Trade-offs

  • 2 次恢复确认会把恢复消息延后约 5 分钟,这是减少短时成功噪声的明确代价。
  • 同类抖动期不逐次通知,会降低每次瞬断的可见性;本地计数和最终稳定退出状态保留诊断能力。
  • 不同类别复发仍作为新事件处理,消息可能多于“全部静默”,但避免把新根因错误吞掉。
  • 不修改 /ready 合同、timer 周期、Webhook 文件、summary 隐私边界或非应用健康告警语义。

7. Rollout and Rollback

  • 本地阶段仅提交代码、合同测试和必要文档,不触碰 ARM/DNS/真实企微。
  • rollout 获批后先以 install 模式部署 disabled unit,核对文件权限、unit 内容、旧状态迁移和只读探测,再单独批准 enable。
  • 启用后先观察分类计数和消息频率;DNS 调整只以实际 dns_resolution_failed 证据为依据。
  • 回滚时停用 timer,恢复上一版探测器/helper/unit;保留并备份脱敏状态。若旧版不能读取新 schema,则在明确目标文件后将新状态移出状态目录,由旧版建立健康基线,避免伪造恢复通知。

8. Security and Privacy

  • 禁止写入 URL、域名、IP、文件路径、异常文本、响应 payload、Webhook 或 Secret。
  • capture 仅在既有显式测试模式下可用,测试输出权限保持 0600。
  • 所有状态枚举和类别经过白名单/token 校验;计数与时间戳必须有类型和范围检查。
  • 生产、企业微信和 DNS 写入继续受独立批准门禁控制。