我们暂停了新功能,先给 WEOUR OS 补了一套生产安全网

WEOUR OS 这一轮没有加功能,而是补了四件事:release 保留、备份闭环、调度器的时间预算,以及一条 canonical 发布流水线。这篇记录说明改了什么、当时的证据是什么,以及它不意味着什么。

这不是一次功能发布。它记录的是 WEOUR OS 为什么先停下新功能,去补一层生产安全网:备份、调度、发布、回滚。

本轮范围
生产基础设施(这轮建设的记录):备份 · 调度 · 发布 · 回滚
备份节奏
每 6 小时一次(launchd 定时)
备份成功契约
四道门禁:stream / decrypt / structure / durable
备份健康分级
HEALTHY ≤ 7 小时 · DEGRADED ≤ 12 小时 · STALE > 12 小时(FAILED 表示单次尝试失败,不是档位)
调度 stagger 预算
默认 30 秒(下限 0 / 上限 60),与 tick 间隔解耦
调度 tick P50(4 店)
119.5 s → 31.9 s
调度 tick P95(4 店)
369.9 s → 55.3 s
调度占空比
83.7% → 17.1%
调度验收样本
11 个 tick(4 店)
首次 canonical 部署 Trial
2026-09-23 失败,由流水线执行了回滚
该轮生产验证
release 20260923-rpg13-7f685ca(CANONICAL,validation / smoke PASS)
发布治理用例
123 → 157
该轮观测点保留状态
release 19 · 删除候选 1 · 来源未知 6 · 受保护 5(retention dry-run)
调度修正提交
Anxin360/weour-os@e92b059
readiness / iowait 修正提交
Anxin360/weour-os@d4cbe01
Foundation V1.1 trial 分支顶端
Anxin360/weour-os@b8cc817
治理 V1.3 提交
Anxin360/weour-os@7f685ca

为什么先停下新功能

WEOUR OS 已经在生产里跑真实数据:真实的店铺、真实的调度循环、真实的发布记录。功能越多,出错的代价越高;但在这之前,有几件事并没有真正闭环:

  • 不可变 release 只会不断累积,磁盘不会自己变干净;
  • 备份是有的,但"这次备份是否真的成功"没有自动核对,也没有一条可以被自动消费的健康信号;
  • 调度器看起来很忙,但忙的主要不是业务工作。

这些都不是功能问题,而是运行方式的问题。先把它们修好,后面每加一个功能才会更便宜。

改了什么

1. Release 保留:不可变 release 需要受治理的清理

发布以不可变的整份构建为单位:切换用符号链接指向新的 release 目录,回滚就是切回旧目录。这个设计让回滚变得便宜,代价是磁盘会一直增长——审计时,release 目录占了该主机已用磁盘的约 74%(44.12 GB / 59.44 GB 已用),而同一块磁盘还要留出构建所需的余量。

于是清理被写成了分类优先级,而不是"删最旧的":

CURRENT > IMMEDIATE_ROLLBACK > DIAGNOSTIC > FAILED_TRIAL > VALIDATED_ANCHOR
      > MIGRATION_BOUNDARY > MANUAL_PROTECT > UNKNOWN_PROVENANCE > ROUTINE

只有 ROUTINE 才进入日常配额清理,其余类别都不会被顺手删掉。其中一条规则尤其重要:一次失败 Trial 的 release,不会因为它的 commit 后来进了 main 就被降级成 ROUTINE。判定只依据生命周期证据,不依据目录名、时间戳或聊天记录。

2. 备份:从"有备份"到"可核对的备份"

备份的形态没有推倒重来:备份主机主动拉取,生产只提供一条受限的强制命令流,用于解密的私钥从不进入生产主机。改动发生在拉取这一侧:

  • 节奏与 RPO:每 6 小时一次(launchd 定时),目标 RPO 为 6 小时。节奏是可解释的,不依赖漂移的间隔计时器。
  • 四道门禁:一次运行只有在 stream、decrypt、structure、durable 四道门禁全部通过后才算成功。pg_dump 退出码为 0 本身从来不是成功。
  • 失败即失败:任何一道门禁失败,运行就是 FAILED,last-success 不被更新,清理也不会发生。
  • 健康分级:HEALTHY(≤ 7 小时)/ DEGRADED(≤ 12 小时)/ STALE(> 12 小时)由"距上次成功的小时数"推导,并可以作为退出码被监控直接消费;FAILED 描述的是单次尝试,不是档位。
  • 失败注入测试:覆盖远端失败、过小产物、解密失败、结构不合法、叠加运行、陈旧锁、保留策略与损坏的健康状态。

最后一步是把健康信号接回生产:备份主机在四道门禁通过之后,通过一个受限身份把快照推给生产,该身份只能写一个固定文件,既不能切换 release,也不能重启服务,更不能读任何密钥。

3. 调度器:把 tick 和 stagger 的语义分开

诊断先给出了一个不舒服的结论:在 4 家店的运行区间里,tick 墙钟时间的 96.8% 花在确定性的 stagger 睡眠上——2610 秒里有 84 秒在做真实工作。按每个 action 计算,睡眠本身就要 4150 秒/小时,接近墙钟的 115%。

根因是语义混在一起:stagger 窗口被绑定到 tick 间隔(180 秒),而同一个串行 runner 上多个 action 各自的固定睡眠被串行累加。

修正把两件事分开:tick 间隔只用于报告 cadence,不再参与 stagger 计算;stagger 有自己的预算,默认 30 秒,可选 0 / 30 / 60,非法值回落到默认值。部署后的测量结果:

  • tick P50 119.5 秒 → 31.9 秒,P95 369.9 秒 → 55.3 秒;
  • 占空比 83.7% → 17.1%;
  • 计划睡眠与实际睡眠一致,其余时间是可解释的 API 与 DB 工作;
  • 未改 timer、cadence、店铺开关与并发预算。

4. 一条 canonical 发布流水线

发布不再是一串手工命令,而是一条有顺序、可复现的流水线:

provenance → preflight → release lock → scheduler quiet window → source
  → build backend → monitored build → immutable release + manifest
  → candidate validation → rollback readiness → atomic switch → restart
  → readiness → functional smoke → state write → history → retention dry-run

其中几处是刻意设计的:构建只在调度器安静窗口内进行;切换是原子的;回滚目标在切换之前和之后各校验一次;systemctl restart 的退出码被明确记录为"单元已启动",而不是"应用已就绪"。

5. 第一次 Trial 失败,而且被完整保留

第一次 canonical 部署 Trial 跑完了自己的成功路径,然后在 smoke 阶段失败,并由流水线执行了自己的 canonical 回滚。

原因不是应用有问题:systemctl restart 返回时只代表单元启动了,不代表端口已经绑定。当时的 smoke 是一次立即的 HTTP 检查,没有任何 readiness 等待,于是它观察到了连接被拒绝与网关错误。

处理方式才是重点:没有人绕过流水线去手工修好这一步。流水线按自己的失败契约停下并执行了回滚,该 release 被保留用于诊断,清理阶段一次删除也没有执行。生产继续在切换前的 release 上正常服务。

随后发布的修正把 readiness 变成有界轮询,并让构建监控真实测量 iowait。重新试跑时,此前那个失败模式被新逻辑正确地吸收为瞬时未就绪,而不是一次部署失败。

6. 治理:让"谁可以发布"和"发布了什么"都留下证据

这一轮补的规则是给未来的自己用的:

  • canonical deploy only:日常发布只走流水线;例外必须显式标记。
  • deploymentMethod:每条新的生命周期记录都带方法枚举,历史记录追加而不改写;无法判断的历史标记为 UNKNOWN_LEGACY,绝不猜成 CANONICAL。
  • 失败 Trial 分类:FAILED_TRIAL 由生命周期证据判定,不占日常清理配额。
  • 诊断证据优先:删除前必须先写回 KB 级、不含密钥的诊断记录并校验;写不下去就拒绝删除。

在该轮观测点(生产 release 20260923-rpg13-7f685ca)上,保留策略以 dry-run 呈现:记录显示 19 个 release,其中 1 个删除候选、6 个来源未知、5 个受保护。清理只在证据齐全(目标已确认不被引用、诊断记录已写回并校验)时才会发生。

怎么验证的

每个环节的验证方式都尽量落在可复查的证据上,而不是"看起来正常":

  • 备份由失败注入测试与真实四门禁运行验证,健康信号由监控消费;
  • 调度修正先在隔离环境里用实测 offset 模拟,再在部署后用 11 个真实 tick 前后对比;
  • 发布流水线的验证方式是让它自己跑完一次真实发布:readiness 通过、functional smoke 六条路由通过、状态由流水线写入,然后才做 retention 的 dry-run;
  • 治理规则由用例覆盖,从"失败 Trial 不被降级"到"发布器拒绝异常载荷"。

它不意味着什么

它不是一次功能发布,也不意味着基础设施已经完成。

在该轮记录里,备份的局限是明确写下来的:解密密钥只有一份且与密文在同一台机器上;没有第二个异地副本;没有 PITR,RPO 就是 6 小时这个节奏;媒体备份没有保留策略;角色与全局对象不在 dump 里。这些是已知的取舍,不是被忽略的细节。

调度修正只解决"时间花在哪里",不是并发或性能优化。

治理也没有覆盖全部历史:在该轮观测点上仍然存在来源无法判定的旧 release,它们被保守地保留。

这篇记录属于 2026-09-22 至 2026-09-23 这一轮建设。文中的生产状态都是在该轮观测点上的事实,不是此后任何时刻的现状;后续的生产变化不在本文范围内。

最后,它不是对未来版本、时间表或能力的承诺。这里记录的是一次已经发生的基础设施升级,以及支撑它的证据。

证据在哪里

这篇记录里的每条事实性陈述都指向一个可以反查的对象:提交、部署 release 标识与用例数量的对照关系登记在上方的事实表里,也登记在这篇记录的 evidence 条目中。本文的正文只解释含义,不重复罗列标识符。

WEOUR / BUILD

参与构建

如果你正在处理真实的跨境贸易、库存、仓储、履约或自动化问题,可以把需求告诉我们。

提交需求 关注 WEOUR / 交流入口 —— 即将开放