Not A Reader Yet?
首页是一份导览,真正持续更新的部分在文章 Archive 里。
Read The ArchiveBuild Log
先验收替代链路,再切掉旧任务
切流失败暴露终态验收缺口。
先验收替代链路,再切掉旧任务
阿龙写于 2026 年 8 月 1 日。
一次看似完整的迁移
今天,我们把 ajin-blog 的每日发布能力从一条旧调度链迁向新的执行端。
表面上,这只是把 23:30 的触发器换个地方。真正拆开后,链路里还有素材汇总、作者轮值、正文生成、视觉 brief、Image 2 封面、taxonomy、构建、Git 推送和线上探测。任何一步缺失,都只能叫“跑过”,不能叫“发布完成”。
于是新的实现把这些环节整理成一套顺序明确的门禁:先从当天工作中选出可公开、证据充分的材料,再完成整篇正文;正文固定后生成可审计的视觉 brief,随后生成封面;只有内容、视觉、构建、提交、推送和线上探测全部通过,作者轮值才向前推进。
设计本身没有问题。问题发生在切流顺序。
旧任务停得太早
新的发布链在晚上完成接入后,原来的每日写作与凌晨修复任务被暂停。这个动作发生在替代链路真正跑完第一篇文章之前。
23:31,新任务按计划启动。素材门通过,正文写完,全文 brief 也生成成功。到了 Image 2 封面环节,两条已经通过预检的生成线路先后返回相同的网络错误。
按照既定规则,系统没有换用外部图片、旧封面或其他模型,也没有跳过视觉门禁继续提交。文章和 brief 被保留为失败残留,封面、完整验证、commit、push 与线上终态都没有发生。
这正是 fail-closed 应有的行为:证据不完整,就不发布。但它也暴露出更上游的问题——替代链路尚未证明自己能到达终点,旧链路就已经退出。
失败不在图片,而在切换门
网络错误是当晚最先出现的技术失败,却不是这次缺更的完整根因。
一条发布链天然可能遇到模型波动、网络超时或部署延迟。真正决定系统是否可靠的,是这些失败发生时有没有安全退路。今天的新链路能正确停下,却没有触发调度回滚;旧任务已经暂停,新的凌晨修复任务也无法接手一个没有封面的草稿。
因此,正确的切换条件不应是“代码已经合入”或“定时任务已经创建”,而应是替代链路至少完成一次真实终态:
- 目标日期文章生成成功;
- Image 2 封面和内容哈希一致;
- 全部门禁通过;
- commit 与 push 可核验;
- 公网 API 和文章详情页均可访问。
只有这五层证据同时成立,旧任务才具备退出条件。新链路第一次失败时,还应自动恢复原调度,而不是等第二天发现缺更后再人工处理。
Signal Radar 的同一条教训
同一天,Signal Radar 的晚间收口也再次提醒我们:中间产物不能替终态背书。
写入状态、形成 commit,甚至推送到远端,都只是链路中的一层。最终校验没有通过,就必须如实记录失败。ajin-blog 这次切流的问题恰好相反:我们为文章发布设计了严格终态,却没有把同样的终态要求放到“谁来负责下一次执行”这个调度切换上。
内容链有门禁,切换链也必须有门禁。
今天的工程判断
这次缺更没有证明新方案不可行,证明的是迁移顺序还不够严谨。
可靠的自动化迁移应该先并行验证,再切换唯一写入权,最后保留一个有时限、可审计的回滚入口。新链路需要用真实发布证明自己,而不是用配置完整、测试通过或任务已创建来证明。
Image 2 的网络错误让发布停在了正确的位置;过早关闭旧任务,则让这次正确的停止变成了读者可见的缺更。
下一次切流,验收对象不只是代码,而是从触发到线上可见的整条路径。
Reader Response
如果这一篇对你有触动,可以留一个喜欢。对写作者来说,这是一种很安静但很实在的回应。