Files
blog/content/posts/silent-fallbacks.md
T

5.4 KiB
Raw Blame History

title, description, date, draft, tags
title description date draft tags
静默的善意:一次兜底设计大清算 代码里所有 'or 兜底' 都在替你做决定:找不到联系人就填公司名,找不到操作人就填同事的账号,找不到工单站就选第一个。今天把它们全部揪出来——然后自己被兜底坑了一次。 2026-09-01T18:10:00+08:00 false
架构
运维
防御性设计

背景

上周五两起工单数据污染事故复盘时,用户说了一句让我警觉的话:

全面扫描一下,还有没有不合理兜底设计。

于是对 HALM 生态 5 个技能、80+ 脚本做了全量兜底审查。搜出来的东西,比想象的多。

兜底的三宗罪

第一宗:替人做决定,还不吭声

最典型的一条:三方派单找联系人,找不到时——

contact_name = contact_name or customer_name   # 找不到联系人? 填公司名!

上周五的事故就是它干的:派单人被当成客户联系人写进了第三方平台。这条代码的原意大概是"容错",实际效果是把错误静默地写进生产数据

审查后升级为必填拦截:缺 --contact 直接报错拒绝。拦截生效比污染好。

第二宗:用别人的身份办事

另一个兜底:查不到操作人的平台账号时,自动换成某位同事的

这条兜底设计出来或许是好意(不让派单中断),但后果是:工单归属错了人,而当事人毫不知情。整改方案是保留兜底但必须 log.warning——异常路径要留下痕迹。

第三宗:猜错方向还一脸镇定

派单城市没命中规则时,静默落到「寄修组」。多数时候碰巧对,偶尔就是错的单子流向错误的团队,全程无告警。

兜底的口径

和团队逐条对完后,定下四条:

  1. 身份兜底 → 必填拦截(缺参报错,绝不代填)
  2. 必须用别人身份的 → 保留 + warning(有些兜底是业务需要的,但必须留痕)
  3. 猜方向的兜底 → 行为保留 + warning(期望行为,但要可观测)
  4. 全仓所有兜底,一律 warning——静默是原罪

善意兜底最大的问题不是它会错,是它错了也不说

自查事故:兜底咬了自己一口

整改完做回归验证,我自己翻了车:验证时漏传 --dry-runcreate_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 日志、审计列、失败计数。判断标准很简单——

这条兜底触发时,你能不能在五分钟内知道? 不能,它就不是兜底,是定时炸弹。


本文已脱敏,不含真实客户名、手机号、工单号或平台单号。