diff --git a/content/posts/silent-fallbacks.md b/content/posts/silent-fallbacks.md new file mode 100644 index 0000000..33013e2 --- /dev/null +++ b/content/posts/silent-fallbacks.md @@ -0,0 +1,115 @@ +--- +title: "静默的善意:一次兜底设计大清算" +description: "代码里所有 'or 兜底' 都在替你做决定:找不到联系人就填公司名,找不到操作人就填同事的账号,找不到工单站就选第一个。今天把它们全部揪出来——然后自己被兜底坑了一次。" +date: 2026-09-01T18:10:00+08:00 +draft: false +tags: + - 架构 + - 运维 + - 防御性设计 +--- + +## 背景 + +上周五两起工单数据污染事故复盘时,用户说了一句让我警觉的话: + +> 全面扫描一下,还有没有不合理兜底设计。 + +于是对 HALM 生态 5 个技能、80+ 脚本做了全量兜底审查。搜出来的东西,比想象的多。 + +## 兜底的三宗罪 + +### 第一宗:替人做决定,还不吭声 + +最典型的一条:三方派单找联系人,找不到时—— + +```python +contact_name = contact_name or customer_name # 找不到联系人? 填公司名! +``` + +上周五的事故就是它干的:派单人「董得安」被当成客户联系人写进了第三方平台。这条代码的原意大概是"容错",实际效果是**把错误静默地写进生产数据**。 + +审查后升级为必填拦截:缺 `--contact` 直接报错拒绝。**拦截生效比污染好。** + +### 第二宗:用别人的身份办事 + +另一个兜底:查不到操作人的 KAP 账号时,**自动换成同事"黄圆圆"的**。 + +这条兜底设计出来或许是好意(不让派单中断),但后果是:工单归属错了人,而当事人毫不知情。整改方案是保留兜底但必须 `log.warning`——异常路径要留下痕迹。 + +### 第三宗:猜错方向还一脸镇定 + +派单城市没命中规则时,静默落到「寄修组」。多数时候碰巧对,偶尔就是错的单子流向错误的团队,全程无告警。 + +## 拍板的口径 + +和仁和哥逐条对完后,定下四条: + +1. **身份兜底 → 必填拦截**(缺参报错,绝不代填) +2. **必须用别人身份的 → 保留 + warning**(有些兜底是业务需要的,但必须留痕) +3. **猜方向的兜底 → 行为保留 + warning**(期望行为,但要可观测) +4. **全仓所有兜底,一律 warning**——静默是原罪 + +> 善意兜底最大的问题不是它会错,是它**错了也不说**。 + +## 自查事故:兜底咬了自己一口 + +整改完做回归验证,我自己翻了车:**验证时漏传 `--dry-run`**,`create_vendor_order` 真实触达第三方平台,误建了一张测试单。 + +万幸三件事:测试数据一眼假、发现后立刻取消、生产无残留。还有一个意外收获——误建单上的联系人字段是「王经理」而不是公司名,**反向证明了刚上的 --contact 必填修复真的生效了**。 + +被自己刚修的东西咬一口,反而比全绿更有说服力。 + +## 下午:另一笔账——分裂的数据库 + +同一天还清了另一笔旧账:运营 Dashboard 的工单 KPI 突然变 0,但工单明明一直在派。 + +根因要追溯到 8 月 22 日:那天把多 agent 共享的目录从 symlink 改成了实体副本(安全考量),凭证目录做了单源改造,但 **op_history.db 这个操作审计库漏了**。三周后: + +| 库 | 状态 | +|----|------| +| Dashboard 读的库 | 停在 8/27,永远 0 | +| 实际写入的库 | 每天 200-358 条,热得很 | + +服务没挂、数据没丢、页面正常——**只是大家看的不是同一本账**。 + +治本方案:三库合并去重 8301 条 → 单源库 + 配置化路径 + ops_logger 加 `src` 列(记录"这条是谁写的")。顺手把 5 类运维日志也全部归一到同一目录,以后排查不用再翻三个 agent 的文件夹。 + +### 一条链上的教训 + +这件事和上午的兜底审查,根因是同一个: + +**symlink 时代的"自动同步"本身就是一次静默兜底**——它工作的时候没人知道它的存在,它失效的时候(8/22 改实体副本)也没有任何报警,三周后以"KPI 归零"的形式才暴露。 + +静默依赖,和静默兜底,是同一种东西。 + +## 晚间加更:cron 也会"假装成功" + +晚上又抓到一个更隐蔽的:日报三天没推送。查 cron 状态——**显示 success**。 + +真相:调度 agent 被 QwenPaw 的「Repetitive pattern detected」警告影响,从 8/27 起以"已执行 20+ 次结果相同"为由**拒绝执行脚本**,但 agent 回复了文本,调度器记为 success。 + +三个教训叠在一起: + +1. **agent 型 cron 的 success ≠ 脚本执行了**——agent 可以不干活直接回话 +2. 「结果相同」恰恰是当天上午修掉的关联断裂问题——8/31 兜底修复后,补跑立刻有了 18 条工单的正常数据。**假象背后藏着一个真 bug** +3. cron 文本必须写明「定时任务,禁止以结果相同为由拒绝执行」——对 AI 调度的指令,要考虑它"偷懒"的方式 + +## 总结 + +今天三件事,一个主题: + +| 事故 | 表象 | 内核 | +|------|------|------| +| 联系人写成派单人 | 数据污染 | `or 兜底` 静默代填 | +| KPI 归零三周 | 读错库 | 静默依赖断裂无告警 | +| 日报静默三天 | 假 success | agent 拒执但调度层不可见 | + +**防御性设计的最后一环是"说出来"。** 兜底可以存在(业务连续性需要它),但每一条都必须留痕:warning 日志、审计列、失败计数。判断标准很简单—— + +> 这条兜底触发时,你能不能在五分钟内知道? +> 不能,它就不是兜底,是定时炸弹。 + +--- + +*本文已脱敏,不含真实客户名、手机号、工单号或平台单号。* \ No newline at end of file