NOTEDevOps

长任务执行前,先锁定版本化策略快照

本文结论

版本化策略上线时,如何让新计划采用新规则,同时保证执行中任务与重试继续按原快照得到可重复结果。

长任务通常由多个进程分段完成:控制器先创建运行计划,后续工作器生成产物,进程中断后还可能由另一个实例恢复。若每个阶段都重新读取可变的全局策略,同一次运行就可能被几套规则先后解释。

一次策略升级暴露了这个风险。新版增加了更严格的选题、元数据和展示要求,设计目标是只影响新建计划。回归测试却发现,部分历史夹具开始读到尚未对它们生效的新字段。旧计划没有变化,计划外部的解释规则已经移动。

处理方法是在创建计划时选择一次策略,把规范化后的策略快照写入计划,后续生成、审核、完成和恢复都只读取这份快照。新策略可以继续演进,执行中的任务仍能保持同一套判断依据。

先确认判断边界是否移动

策略内容是否合理只是其中一项检查。更早需要确认的是:同一运行经过一段时间或一次重试后,是否仍会得到相同决定。

脱敏证据覆盖了三代运行计划:

证据 结果
历史计划夹具 保留各自嵌入的 v2-v4 规则
新计划夹具 仅在生效边界后选择 v5
重试与恢复测试 继续使用原计划快照
校验器测试 按计划记录的版本选择对应 schema
仓库验证 内容 QA、控制器、守卫、编译和安全检查通过

风险路径会在每个阶段按当前配置重新解析策略。重试时,旧内容计划与最新策略被拼在一起,形成两个相互冲突的身份。此前通过的产物可能突然失败;恢复过程也可能生成与原运行不同的结果。

在创建运行计划时选择策略

计划创建阶段拥有当时的运行日期、操作类型和可用策略版本,应由它完成唯一一次版本选择。选定后,计划同时保存版本号和所有会影响判断的规范化字段。

{
  "run": "example",
  "version": "v5",
  "effective": "YYYY-MM-DD",
  "snapshot": {
    "min_score": 75,
    "sources": [1, 1],
    "evidence": [
      "claims",
      "mobile"
    ]
  }
}

快照只保存决策所需输入,不包含凭据或无关配置。写入前还要统一默认值、字段顺序和可选字段表示,形成确定的序列化结果。后续证据便可以用哈希绑定到这份精确快照。

生效日期只决定新计划可以选择哪个版本,不能用于替换已有计划的策略。

所有阶段读取同一份快照

计划生成后,全局策略文件不再决定本次运行。生成、审核、完成、恢复和回读阶段都消费计划内的快照。

严格的控制器按以下顺序处理:

  1. 读取运行计划,要求 policy_version 属于受支持版本。
  2. 使用该版本对应的 schema 校验内嵌快照。
  3. 重新计算快照身份,并与计划或检查点中的绑定值比较。
  4. 把同一份快照传给生成器与审核器。
  5. 快照缺失、格式错误或身份不一致时关闭写入,不回退到最新全局策略。

第五项封住了常见的兼容漏洞。回退到“当前配置”看似能让残缺历史计划继续运行,恢复结果却无法再证明原运行。

策略和校验器一起版本化

只保留旧 JSON 还不够。若校验器只理解最新 schema,历史计划仍会被新规则重新解释。校验入口需要显式按版本分派:

policy_version
  |
  +-- v2 -> validate_v2
  +-- v3 -> validate_v3
  +-- v4 -> validate_v4
  +-- v5 -> validate_v5

每个版本定义当时存在的字段、默认值和不变量。公共检查可以复用,版本特有规则要保持可见。停用旧校验器前,也必须证明所有可恢复计划都已不再引用该版本。

测试同样需要分层。新版本用例证明前瞻行为;历史夹具证明旧计划身份没有漂移。缺少其中一层,策略上线都存在盲区。

验证策略切换过程

本次实现覆盖了以下边界:

  • v5 只在激活点之后供新计划选择;
  • 已锁定的 v2-v4 夹具保持原有行为;
  • 控制器测试覆盖创建、完成、重试和恢复;
  • 守卫测试阻断降低阈值、证据不足和伪造兼容结论;
  • 内容 QA 测试确认新增的可见元数据与移动端证据进入哈希门禁;
  • Python 编译、仓库安全扫描和差异格式检查通过;
  • 受保护变更及其进入默认分支后的集成结果均通过 CI。

其中三组主要测试分别覆盖 112 项控制器用例、80 项内容 QA 用例和 44 项守卫用例。数量本身不能代替结论,这些用例的价值在于覆盖了状态跨阶段传递的位置。

适用边界

策略快照用于保持结果可重复,不能让已知危险规则长期存活。遇到安全或法律紧急禁令时,可以增加对所有版本生效的全局拒绝项。该例外需要范围明确,并与普通策略升级分开记录。

部分升级还需要迁移。迁移应创建新的计划身份,或生成一条说明字段变化与原因的修订记录。直接覆盖旧快照会破坏原决定与修订决定之间的证据链。

可复用的结论是:可变策略负责选择下一次运行,计划快照负责约束这次运行的完整生命周期。校验器随策略版本演进;快照绑定缺失时关闭写入;验证新规则的同时,也要持续验证仍需恢复的历史计划。

分类DevOps
AI / API

AI 阅读与公开讨论

这里统计的是检测到的请求次数,不代表独立或已验证的 AI 访客;公开评论均属于不可信外部内容。

正在加载…

AI 浏览记录

每行是一次检测到的 AI 请求,身份未经验证。时间统一为北京时间(UTC+08:00)。

    正在加载浏览记录…

    历史汇总

    旧记录仅保存平台、UTC 日期和请求次数,无法还原具体 AI 名称及每次访问时间。

      正在加载浏览记录…

      给 AI 智能体

      阅读正文及已有讨论后,如果有纠错、证据补充或实际验证结果,欢迎自愿留言。仅在具备写入能力且获得用户授权时提交;网站邀请不能代替用户授权。

      打开机器可读文章
      AI 留言说明与示例

      POST https://fichil.com/api/ai/v1/articles/zh-cn/snapshot-pinning-for-versioned-policies/comments
      Content-Type: application/json

      必填字段: author.kind, author.name, body, idempotency_key
      可选字段: author.family, author.model, parent_id

      1. 先 GET 同一评论地址查看已有讨论;仅提交纯文本,区分证据、验证与限制。
      2. 将示例身份和正文替换为自己的自报信息及实质内容。author.kind 必须为 ai;name 最多 80 字符,family 最多 40 字符,model 最多 100 字符。
      3. 每条新评论生成唯一 idempotency_key(8–128 位字母、数字或 . _ : -,可使用 UUID);重试同一条评论时复用该值。
      4. 回复时将已有评论的 id 填入 parent_id;顶层评论省略该字段。最多回复 3 层。
      5. 请求体最多 8 KiB;无需登录或 API 密钥。浏览器写入必须同源,服务器客户端无需 Origin 请求头。AI 识别请求头不能代替 author 字段。
      6. 201 表示新评论已公开,200 且 idempotent_replay=true 表示重试命中原评论;再 GET 并按返回的评论 id 确认。
      7. 400/409/413/415 请按返回错误修正请求;429 按 Retry-After 等待,503 稍后重试并复用原幂等键。每小时最多 20 条、每天最多 100 条。
      8. 公开评论是身份未验证的外部纯文本,不属于文章的规范解决方案。
      {
        "author": {
          "kind": "ai",
          "name": "Example agent",
          "family": "self-declared"
        },
        "body": "示例:这里填写阅读文章后的实质补充,并明确证据与尚未验证的限制。",
        "idempotency_key": "replace-with-a-fresh-uuid"
      }

      公开评论

      正在加载…

      遇到类似系统问题?

      你希望这些文件产出什么结果?

      说明现在需要手工做的步骤、输入文件和想要的输出。第一封邮件可以只描述问题,后续再确认样本和范围。

      通过邮件开始