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

133 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 内维护:
```text
service/cutover-instances/<instanceId>/head.json
```
目录固定为当前 UID 的 `0700`head 固定为 `0600`。记录绑定 Profile、instance、cutover、
activation、revision、generation、前一 head digest 和来源 journal/preparation digesthead
自身也有规范化 SHA-256 digest。
首次 legacy-stop 在接触 Docker 前以 no-replace 方式认领实例。后续只允许:
```text
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
新增命令:
```text
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 或错误正文,也不执行
`start``stop``restart``update`
### 4. Resolution 必须 prepare/commit 双阶段确认
新增命令:
```text
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/PostgreSQL**deployment 恢复在数据库可用前也必须成立,并会给路由器
增加第二 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 整体完成。