Files
qinglong/docs/adr/ADR-0313-instance-lineage-and-two-phase-cutover-manual-resolution.md

6.7 KiB
Raw Permalink Blame History

ADR-0313:实例 Cutover Lineage 与双阶段人工恢复

  • 状态:Accepted
  • 日期:2026-08-09
  • 关联 RFCQL-RFC-0001 D-05、D-17、D-63、D-64、D-65、D-259
  • 关联 ADRADR-0065、ADR-0309、ADR-0310

背景

ADR-0310 已让未知的 Docker target start/restart 结果进入不可自动离开的 manual_required,但该终态只存在于 service/cutovers/<cutoverId>/。若没有实例级当前 lineage,调用方可以换一个 cutoverId 创建新 journal,从而把“不可重试”降级为“换 ID 重试”。同时,operator 还没有只读诊断和受审的新 ceremony 授权入口。

这个缺口必须在短生命周期 deployment authority 内关闭。低配路由器不能为恢复流程安装 数据库、daemon、timer 或 watcherApplication 也不能因此得到 Docker 或 journal 写权限。

编辑前 GitNexus 显示 legacy-stop 与 target 主入口均为 LOW、各 1 个直接命令文件包装器、 0 条已识别执行流程;统一终态 helper 为 MEDIUM、5 个包内直接调用者、0 条执行流程;CLI main 为 LOW。没有 HIGH/CRITICAL 风险。

决策

1. 每个实例只有一个 durable lineage head

ql3-local-deploy 在现有 deployment root 内维护:

service/cutover-instances/<instanceId>/head.json

目录固定为当前 UID 的 0700head 固定为 0600。记录绑定 Profile、instance、cutover、 activation、revision、generation、前一 head digest 和来源 journal/preparation digesthead 自身也有规范化 SHA-256 digest。

首次 legacy-stop 在接触 Docker 前以 no-replace 方式认领实例。后续只允许:

legacy_stop_requested -> legacy_stopped
legacy_stopped|target_active -> target_active|manual_required
manual_required -> resolution_authorized
resolution_authorized -> 新 cutover 的 legacy_stop_requested

head 的替换使用已有 replaceExactFile(expected,next) compare-and-swap。不同 cutover ID 在 旧 head 未经人工 resolution 时必须失败,且失败发生在新 journal 创建和 Docker authority 打开之前。target start/restart 同样必须先证明自己仍是实例 head;旧 ceremony 在 resolution 后不能继续运行。

2. Journal 是不可变事实,head 是可比较索引

manual_required journal 不删除、不覆盖,也不改写成成功。target journal 先落盘、head 后 同步;若在二者之间崩溃,原命令重放从不可变 journal 恢复并补齐 head,不会重复 start。 head 指向 terminal record digest,因此 operator 不能用另一个同名或自造记录替换来源事实。

3. 诊断只能 inspect

新增命令:

ql3-local-deploy cutover-manual-diagnose

命令先验证 exact manual head 与其不可变 journal record,再对完整 legacy/target container ID 各执行一次 docker container inspect。输出只有 stopped|running|unknown、domain digest、 cutover ID 和 head digest,不返回 Docker 原文、路径、镜像名、PID 或错误正文,也不执行 startstoprestartupdate

4. Resolution 必须 prepare/commit 双阶段确认

新增命令:

ql3-local-deploy cutover-manual-resolution-prepare
ql3-local-deploy cutover-manual-resolution-commit

prepare 只有在 legacy 与 target 都被严格证明为 stopped 且 restart policy 为 no 时,才在旧 journal 中 no-replace 发布一个绑定 current/next cutover、current/next activation、manual head、 两个完整 container ID 和两份 inspection digest 的 preparation。running、ambiguous、inspect 失败或身份漂移都保持 manual_required

commit 必须显式提交 preparation digest,重新 inspect 两个容器,并要求 observation digest 与 prepare 完全一致。随后才以 old head digest 为 expected value,把实例 head CAS 到新 cutover 的 resolution_authorized。commit 不启动或停止任何容器;CAS 后崩溃可由同一命令在不访问 Docker 的情况下返回 existing。只有新 cutover 的 legacy-stop 可以消费该授权并开始全新的 stop/commitment/target ceremony。

5. 保守支持边界

本版只授权“双容器均已由 operator 在 controller 外明确停止”的新 ceremony。若 target 仍在 运行,诊断会报告 running,prepare 拒绝;本命令不会猜测应采用当前 target、停止 target 或 恢复 legacy。target stop、写后数据对账与 rollback 是独立能力。

6. 资源与源码边界

实现继续位于现有 @qinglong/local-owner-cli/deployment/cutover,按 instance lineage、manual command contract 和 manual coordinator 三个内部模块组织。没有新增 workspace package、生产 依赖、数据库连接、SQL/migration、后台进程或常驻资源。实例 catalog 最多 64 项,单 cutover journal 仍最多 64 个小型私有文件;每次诊断/prepare/commit 固定两次 inspect。

被否决方案

  1. 只在新命令中检查旧 cutover ID:调用方仍能直接走 legacy-stop,不能关闭旁路。
  2. 删除旧 journal 后重试:销毁未知副作用的唯一审计事实,拒绝。
  3. prepare 后不重新 inspect 就 commit:人工确认窗口内容器可能漂移,拒绝。
  4. resolution 自动 stop/start 容器:把人工裁决重新变成未知外部副作用,拒绝。
  5. 把 head 放入 SQLite/PostgreSQLdeployment 恢复在数据库可用前也必须成立,并会给路由器 增加第二 authority,拒绝。
  6. 新建 cutover package:没有独立 artifact、依赖闭包或 consumer 边界,拒绝。

验收证据

  • cutover 专项 9/9:原 6 项 start/restart 门继续通过;新增换 ID 绕过失败、inspect-only diagnose + prepare/CAS commit + exact replay/new ceremony,以及 prepare 后容器漂移拒绝。
  • @qinglong/local-owner-cli 完整沙箱外 119/119;沙箱内唯一失败仍是既有 provider test 监听 127.0.0.1 被 EPERM,沙箱外原样通过。
  • package 数保持 16;新增源码全部位于既有 deployment/cutover/ 领域目录,没有根级平铺文件。
  • 本批不改 SQL、migration、PostgreSQL/Cluster runtime 或部署资源,因此不重新生成 PostgreSQL HA 物理晋升证据。

未完成

  • target 显式 stop 与最小写后分类(后续已由 ADR-0314 关闭);仍 running target 的 adoption/resolution、数据域对账与 rollback 仍未完成;
  • systemd/OpenRC manual controller
  • adopted Compose live create/config 与真实 crash gate
  • Cluster/Kubernetes 独立 cutover authority
  • 强认证 operator ceremony、审计 UI/API 和外部审批集成。

本 ADR 关闭 Docker manual_required 的实例级绕过、只读诊断和最小双阶段新 ceremony 授权,但不宣称所有 controller 或 QingLong 3.0 整体完成。