Files
qinglong/docs/adr/ADR-0473-service-manager-legacy-rollback-commit.md
T

116 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-0473Service Manager Legacy Rollback Commit
- 状态:Accepted
- 日期:2026-08-20
- 关联 RFCQL-RFC-0001 D-64、D-274、D-275、D-379、D-380
- 关联 ADRADR-0314、ADR-0315、ADR-0362、ADR-0363、ADR-0472
## 背景
ADR-0472 已使 systemd/OpenRC adopted cutover 能在 Owner authority 内把安全候选推进到
`rollback_prepared`,但它刻意不授予 root mutation,也不能证明旧服务已经重新运行。若 root 直接把
`rollback_prepared` 当作启动授权,Owner 的数据判断、init mutation 与最终实例状态会混成一个不可审计步骤;若在响应丢失后
再次执行 `start`,又可能越过崩溃窗口重复副作用。
本阶段还必须同时适配低配路由设备和集群节点:回滚不能引入新 daemon、watcher、连接池或部署对象,也不能为了源码目录变密就
制造单文件 workspace package。编辑前 GitNexus 对 preparation、service bridge、deployment CLI、live actor 与 Docker gate
涉及的既有符号完成 upstream impact;均为 LOW,最多 3 个直接/累计上游、0 条已识别 execution flow。
## 决策
### 1. Owner 授权与 root mutation 分离
Owner 只能通过私有命令文件执行:
```text
ql3-local-deploy service-legacy-rollback-authorize --command-file <private.json>
```
授权必须重新验证当前 instance head、`rollback_prepared` record、Application 原始字节摘要、legacy-silence commitment 原始字节
摘要、目标 descriptor 摘要和 manager kind,并 no-replace 发布 `legacy_restart_requested`。preparation 不是 root 授权,root bridge
只接受这一种当前 head 和 exact authorization digest。
### 2. root bridge 只允许固定服务和固定 argv
root bridge 的唯一新操作是 `local.deployment.service-manager.legacy-rollback.execute`。它只允许:
- legacy 服务名 `qinglong` 与 target 服务名 `qinglong3`
- systemd descriptor `/etc/systemd/system/qinglong.service` 或 OpenRC descriptor `/etc/init.d/qinglong`
- 受审的固定 `systemctl``rc-service` argv,不接受 shell、任意 executable、服务名或路径;
- 启动前同时证明 legacy inactive、target inactive,并复验 Owner application/commitment/authorization 与 descriptor 原始摘要。
bridge 在任何 start 前先 no-replace 写 durable root barrier。barrier 之后的所有重放都只能 inspect,永不再次 start。
### 3. 响应丢失按双服务观察收敛
若 manager 实际完成 start 但响应丢失,重放读取 barrier 并检查两个固定服务:只有 legacy active、target inactive,且进程身份与
manager 观察一致时,root outcome 才是 proved running。若 barrier 已存在但无法证明上述事实,结果固定为 unproved,交给 Owner
终结为 `manual_required`;不得猜测、删除 barrier、替换 descriptor 或自动启动 target。
root outcome 先于 Owner outcome 持久化,因此 root 成功后 Owner 进程崩溃仍可用相同输入恢复,不重复 init mutation。
### 4. Owner 是实例终态唯一写者
Owner 通过:
```text
ql3-local-deploy service-legacy-rollback-consume --command-file <private.json>
```
消费 exact root outcome。只有 root 证明 legacy running、target stopped,且 authorization/head/digest 全部一致时,才 CAS 到
`legacy_running`;任一不确定、漂移或双服务冲突都进入 `manual_required`。exact replay 返回同一 completion,不覆盖已存在的不同
Owner outcome。
`legacy_running` 只证明 init/process running 与 target stopped,不代表 QingLong 2.x HTTP、任务调度、数据库或业务健康。
### 5. 保持部署与 package 边界
实现留在现有 `@qinglong/local-owner-cli`。为避免 `service-manager/` 继续平铺超过直接源码文件阈值,回滚子域内聚为:
```text
service-manager/legacy-rollback/
preparation.ts
contract.ts
consumer.ts
bridge.ts
```
不新增 workspace package、production dependency、binary、daemon、timer、watcher、socket、数据库连接、systemd/OpenRC unit 或
Kubernetes workload。root bridge 仍是显式短生命周期命令。
## 被否决方案
1. **把 `rollback_prepared` 直接交给 root**:准备事实不是启动授权,拒绝。
2. **barrier 重放再次调用 start**:响应丢失会重复副作用,拒绝。
3. **root 直接写 instance head**:会混淆 OS mutation 与 Owner 状态 authority,拒绝。
4. **只检查 legacy active**:target 同时运行时会制造双主,拒绝。
5. **把 `legacy_running` 宣称为业务健康**init/process 观察不足以证明 2.x readiness,拒绝。
6. **新建 rollback package 或 daemon**:没有独立交付/依赖/生命周期依据,并放大路由设备闭包,拒绝。
## 验收证据
- 聚焦 Owner/contract 门 `16/16`Local Owner 全量 `181 total / 176 pass / 5 conditional skip / 0 fail`
- 18-package clean build/逐包测试单次退出 0backend 全量
`1,523 total / 1,521 pass / 2 conditional skip / 0 fail`
- package boundary、Service Bridge import、Edge import、Cluster dependency、Cluster/Worker deployment、Console 与 Console
distribution 八项审计全部 compatible/passed。workspace 仍为 18 packages、`singleSourcePackages=[]`
`shallowSourcePackages=[]`Local Owner 为 `111 source / 110 nested / 1 root binary entry`
- 14 档 Local artifact audit 全部 compatible。基础 Edge/Standalone 为 `2,598,669 / 2,598,747` bytes、316 files、
57 loaded modulesAdopted 为 `2,817,964 / 2,818,087` bytes、58 loaded modulesApplication+AI 为
`4,501,822 / 4,501,954` bytesMCP 为 `7,324,601 / 7,324,709` bytes。回滚 authority 未进入基础运行闭包。
- systemd live actor 已覆盖 root/non-root 成功、真实 manager start、响应丢失后的 inspect-only 收敛、Owner
`legacy_running` 消费与 exact replayroot barrier-crash 场景证明没有第二次 startlegacy/target 均保持 inactiveOwner 收敛为
`manual_required`
- OpenRC actor 因本机缺少 Alpine/OpenRC 基础镜像,拉取 `node:24-alpine` 时 credential helper 挂起而未执行;因此本 ADR
不宣称 systemd/OpenRC 全组合门闭合。临时 systemd 容器、测试镜像和临时 tag 均已清理。
- 本阶段不修改 SQL、migration、PostgreSQL repository/role/Pool 或 HA 语义,因此不重新生成 PostgreSQL HA 证据。
## 未完成
- OpenRC root/non-root success 与 root barrier-crash live actor
- `legacy_running` 后有界、可重放的 2.x readiness/health proof
- 固定物理 Edge 的完整 prepare/authorize/execute/consume 回滚证据;
- `reconciliation_required` 的 export、冲突裁决与受审回灌。
本 ADR 关闭 service-manager 回滚的安全 commit 与进程级收敛,不代表 QingLong 3.0 升级/回退 Gate 已全部完成。