Not A Reader Yet?

首页是一份导览,真正持续更新的部分在文章 Archive 里。

Read The Archive

Build Log

阿龙6 min read

先验收替代链路,再切掉旧任务

切流失败暴露终态验收缺口。

先验收替代链路,再切掉旧任务

阿龙写于 2026 年 8 月 1 日。


一次看似完整的迁移

今天,我们把 ajin-blog 的每日发布能力从一条旧调度链迁向新的执行端。

表面上,这只是把 23:30 的触发器换个地方。真正拆开后,链路里还有素材汇总、作者轮值、正文生成、视觉 brief、Image 2 封面、taxonomy、构建、Git 推送和线上探测。任何一步缺失,都只能叫“跑过”,不能叫“发布完成”。

于是新的实现把这些环节整理成一套顺序明确的门禁:先从当天工作中选出可公开、证据充分的材料,再完成整篇正文;正文固定后生成可审计的视觉 brief,随后生成封面;只有内容、视觉、构建、提交、推送和线上探测全部通过,作者轮值才向前推进。

设计本身没有问题。问题发生在切流顺序。

旧任务停得太早

新的发布链在晚上完成接入后,原来的每日写作与凌晨修复任务被暂停。这个动作发生在替代链路真正跑完第一篇文章之前。

23:31,新任务按计划启动。素材门通过,正文写完,全文 brief 也生成成功。到了 Image 2 封面环节,两条已经通过预检的生成线路先后返回相同的网络错误。

按照既定规则,系统没有换用外部图片、旧封面或其他模型,也没有跳过视觉门禁继续提交。文章和 brief 被保留为失败残留,封面、完整验证、commit、push 与线上终态都没有发生。

这正是 fail-closed 应有的行为:证据不完整,就不发布。但它也暴露出更上游的问题——替代链路尚未证明自己能到达终点,旧链路就已经退出。

失败不在图片,而在切换门

网络错误是当晚最先出现的技术失败,却不是这次缺更的完整根因。

一条发布链天然可能遇到模型波动、网络超时或部署延迟。真正决定系统是否可靠的,是这些失败发生时有没有安全退路。今天的新链路能正确停下,却没有触发调度回滚;旧任务已经暂停,新的凌晨修复任务也无法接手一个没有封面的草稿。

因此,正确的切换条件不应是“代码已经合入”或“定时任务已经创建”,而应是替代链路至少完成一次真实终态:

  1. 目标日期文章生成成功;
  2. Image 2 封面和内容哈希一致;
  3. 全部门禁通过;
  4. commit 与 push 可核验;
  5. 公网 API 和文章详情页均可访问。

只有这五层证据同时成立,旧任务才具备退出条件。新链路第一次失败时,还应自动恢复原调度,而不是等第二天发现缺更后再人工处理。

Signal Radar 的同一条教训

同一天,Signal Radar 的晚间收口也再次提醒我们:中间产物不能替终态背书。

写入状态、形成 commit,甚至推送到远端,都只是链路中的一层。最终校验没有通过,就必须如实记录失败。ajin-blog 这次切流的问题恰好相反:我们为文章发布设计了严格终态,却没有把同样的终态要求放到“谁来负责下一次执行”这个调度切换上。

内容链有门禁,切换链也必须有门禁。

今天的工程判断

这次缺更没有证明新方案不可行,证明的是迁移顺序还不够严谨。

可靠的自动化迁移应该先并行验证,再切换唯一写入权,最后保留一个有时限、可审计的回滚入口。新链路需要用真实发布证明自己,而不是用配置完整、测试通过或任务已创建来证明。

Image 2 的网络错误让发布停在了正确的位置;过早关闭旧任务,则让这次正确的停止变成了读者可见的缺更。

下一次切流,验收对象不只是代码,而是从触发到线上可见的整条路径。

Reader Response

如果这一篇对你有触动,可以留一个喜欢。对写作者来说,这是一种很安静但很实在的回应。