Files
qinglong/docs/adr/ADR-0315-two-phase-legacy-rollback-ceremony.md
T

119 lines
7.3 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-0315:双阶段 Legacy Rollback Ceremony
- 状态:Accepted
- 日期:2026-08-09
- 关联 RFCQL-RFC-0001 D-05、D-17、D-63、D-64、D-65、D-259
- 关联 ADRADR-0064、ADR-0065、ADR-0309、ADR-0310、ADR-0313、ADR-0314
## 背景
ADR-0314 已能停止当前 QingLong 3 target,并把数据证据保守分类为
`rollback_candidate|reconciliation_required|manual_review`。但 `rollback_candidate` 只是“target 尚未产生可见
新事实、legacy source 仍等于 recovery”的只读判断,不是启动 2.x 的授权。若 operator 在判断后直接运行
`docker start`,数据、容器或实例 lineage 可能已漂移,也没有 crash barrier 能阻止响应丢失后的重复副作用。
本机部署还需要兼容低性能路由器。回退协议不能引入数据库连接、常驻进程、watcher 或全历史扫描;同时必须让
较大的 Standalone 节点使用同一确定性 ceremony,而不是维护第二套语义。
编辑前 GitNexus 显示 rollback coordinator、instance head、target/legacy evidence 与 CLI 入口均为 LOW,最多
3 个直接调用、17 个累计上游符号、0 条 execution process;没有 HIGH/CRITICAL 风险。
## 决策
### 1. 只允许 exact rollback candidate 进入 prepare
新增两个短生命周期命令:
```text
ql3-local-deploy cutover-legacy-rollback-prepare
ql3-local-deploy cutover-legacy-rollback-commit
```
两者必须重读并验证同一 generation 的 target request、`target_active`、target stop request、
`target_stopped` 和 reconciliation evidence。command 必须提交 stop 返回的 exact `recordDigest`
`instanceHeadDigest`。只有 disposition 为 `rollback_candidate`,且实例 head 仍为相同
`target_stopped`prepare 才继续。
prepare 重新证明 legacy/target 均 stopped、identity/application/source binding 不变,target/source 数据证据仍
等于 stop outcome;随后在实例私有目录 no-replace 写入 digest-bound preparation,并把 head CAS 为
`rollback_prepared`。它不执行 start/stop/update/restart,也不修改 SQLite。每实例最多保留 15 个 preparation
任何 `rollback-*` 符号链接或非普通文件都失败关闭。
### 2. Commit 在副作用前后都重新证明双容器互斥
commit 必须携带 prepare 返回的 exact `preparationDigest`。它先复验 preparation、instance head、两容器 stopped
和数据证据,然后在 journal 固定写入 `legacy_restart_requested` barrier,并把 head 推进到同名状态。barrier
写入后、执行 start 前再次证明 legacy/target stopped 和数据未漂移,关闭 prepare/commit 与 barrier/start 之间的
TOCTOU 窗口。
随后只允许对冻结的完整 legacy container ID 执行一次 `docker container start`。最终 outcome 必须同时证明:
```text
legacy: runningidentity/source binding 与原 commitment 一致
target: stoppedidentity/application binding 与原 active evidence 一致
```
两项都成立才追加 `legacy_running` 并更新实例 head。任一项 unknown、漂移或 target 同时 running,均追加 terminal
`manual_required`,不得声称 rollback 成功。`legacy_running` 只证明受审容器已经运行,不等于应用健康、readiness
或业务数据完整。
### 3. Crash replay 以 inspect 收敛,不重复未知 start
rollback request/outcome 使用 generation 的 `4g+3``4g+4`,与 ADR-0314 stop 的 `4g+1``4g+2`
构成一条 append-only 链。preparation 存于 journal 外的实例目录,所以 15 generation 仍不超过既有 64 条 journal
文件上限。
- barrier 前崩溃:可原样重做只读 preflight。
- barrier 已写、start 尚未证明:重放只 inspect,不再盲目调用 startlegacy 未运行时进入 `manual_required`
- start 已成功但响应丢失:重放 inspect 到 exact legacy running + target stopped,补写 outcome,不第二次 start。
- 已存在 outcome:校验 digest chain 后返回 `existing`,不打开 Docker socket。
这选择了安全的不确定性:可能要求人工确认一次已经失败的启动,但不会在未知状态下重复启动旧系统或形成双写。
### 4. 数据写后回退仍是独立 authority
`reconciliation_required``manual_review` 永远不能调用本 ceremony。协议不删除/覆盖 target,不把 target
SQLite 回灌 source,不执行 checkpoint,也不处理 Keyv、日志、配置和 Secret keyring。数据域 export、冲突清单、
人工选择和受审回灌必须由后续独立 ADR 定义。
### 5. 保持既有 package,按包内领域组织
实现位于现有 `@qinglong/local-owner-cli/src/deployment/cutover/`,按 contract、coordinator、共享 evidence 和
journal collaborator 分文件;没有为一次 rollback 新增 workspace package。package 是否独立由 deployable、authority、
依赖闭包或多 consumer 决定,不以“一个目录/一个文件”决定。当前 workspace 16 个包、790 个 source,其中 25 个
位于 `src` 根、765 个位于包内领域目录,`singleSourcePackages=[]``shallowSourcePackages=[]`
## 被否决方案
1. **stop 返回 rollback_candidate 后直接 start**:没有二次数据/容器证明和 durable barrier,拒绝。
2. **barrier 后重放 docker start**:响应丢失时可能重复副作用,拒绝。
3. **只证明 legacy running**:target 可能被并发启动并形成双写,拒绝。
4. **prepare 锁住 Docker socket 或启动常驻 supervisor**:不适合路由器资源边界,拒绝。
5. **将 rollback ceremony 拆成新 package**:没有独立交付或生产 consumer,不制造微包。
6. **把 reconciliation 合并进 rollback**:数据域冲突需要不同审批和恢复资产,拒绝。
## 验收证据
- cutover 专项 24/24,覆盖 exact prepare/commit、写后拒绝、prepare/commit drift、barrier crash、start response
loss、barrier 后 target 竞争启动、legacy/target 同时 running、unsafe preparation entry 和 terminal manual。
- `@qinglong/local-owner-cli` 完整回归 134/13416-package clean build/test 退出 0。
- package boundary schema v5 为 16/16、790 source、25 root、765 nested,且 findings、single-source、
shallow-source 均为空。Edge import、Cluster dependency、Cluster deployment 全部 compatible。
- 十档 pack/install/import/RSS audit 全部 compatible,制品字节、文件数和加载模块数相对 ADR-0314 完全不变:
最小 Edge/Standalone 为 3,623,093/3,623,129 bytes、331 files、49 modules;最大 Edge/Standalone
Application AI 为 6,108,149/6,108,281 bytes、492 files、109 modules。
- 没有新增 production dependency、数据库连接、timer、watcher、listener、daemon、常驻进程或部署单元。
- 本批不改 SQL、migration、PostgreSQL/Cluster runtime 或部署资源,因此不重复生成 PostgreSQL HA 物理晋升证据。
## 未完成
- `reconciliation_required` 的数据域清单、export、冲突裁决和受审回灌;
- Keyv、日志、配置、Secret keyring 等多资产 backup/reconciliation manifest
- systemd/OpenRC legacy rollback controller
- adopted Compose live create/config 与真实 Docker crash/power-loss gate
- Cluster/Kubernetes 独立 cutover authority
- `legacy_running` 之后的应用 readiness/健康证明。
本 ADR 关闭 exact `rollback_candidate` 的双阶段 Docker Legacy restart ceremony,但不宣称写后无损回退或
QingLong 3.0 整体完成。