feat(ql3): prepare service manager legacy rollback

This commit is contained in:
whyour
2026-08-20 16:34:30 +08:00
parent 0c503932cc
commit ad291e514d
11 changed files with 1058 additions and 13 deletions
+19
View File
@@ -11,6 +11,25 @@
最新增量证据(2026-08-20):
- D-379/ADR-0472(已接受;OpenRC live actor 待镜像基础设施恢复后补跑):为 systemd/OpenRC adopted cutover 增加 Owner-side
`service-legacy-rollback-prepare`。命令绑定 exact `target_stopped` record 与 instance head,重读 stop intent、Application v3、
activation 和 legacy-silence commitment,并复用 Docker 路径相同的 stable-fd SHA-256/inode/sidecar reconciliation。
只有 target 等于 activation、source 等于 recovery 且双方 SQLite sidecar clear 的 `rollback_candidate` 才 no-replace 发布
digest-bound preparation 并 CAS 到 `rollback_prepared`;target 写后或证据不确定返回结构化 `not-prepared`,保持
`target_stopped`,不错误进入 terminal `manual_required`。本切片位于现有 Local Owner 包,不新增 workspace package、
production dependency、数据库连接、daemon、watcher、timer 或 root mutationroot-only systemd/OpenRC legacy start、
inspect-only crash convergence、最终 `legacy_running` 消费与 2.x health proof 留给下一阶段。定向 prepare 门 `10/10`Local
Owner 全量 `175 total / 170 pass / 5 conditional skip / 0 fail`,完整 backend 为
`1,507 total / 1,505 pass / 2 conditional skip / 0 fail`18-package clean build/逐包测试单次退出 0。package boundary、
Service Bridge import、Edge import、Cluster dependency、Cluster/Worker deployment、Console 与 Console distribution 八项审计
全部 compatible/passedworkspace 仍为 18 packages、`singleSourcePackages=[]``shallowSourcePackages=[]`Local Owner 为
`108 source / 107 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 modules
Application+AI 为 `4,501,822 / 4,501,954` bytesMCP 为 `7,324,601 / 7,324,709` bytes。Docker live gate 已完成 systemd
root/non-root 两个 actor 的真实 stop、prepare 与 exact replayOpenRC 两个 actor 因 `node:24-alpine` registry mirror EOF 与
credential helper 挂起未执行,不能宣称四组合门已闭合,镜像基础设施恢复后须补跑。本切片不改变 PostgreSQL schema、ACL、
repository、role、Pool、连接或 failover 语义,因此不重跑且不重新占有 HA 证明;D-373/D-374 PostgreSQL 18.6 arm64 HA
`146/146`、timeline `1→2` 仅作为相邻既有基线。
- D-378/ADR-0471(已接受):把孵化重点从连续 Cluster Console 增量转回 3.0 首发兼容闭环,新增直接运行生产 Cron/Subscription
Express Router 与 Celebrate validator 的 2.x 核心执行 API 基线。Cron 覆盖 list/create/update/disable/enable/run/stop、单日志、日志列表和
单实例 stopSubscription 覆盖 list/create/update/disable/enable/run/stop/log。确定性 TypeDI spy 同时锁定服务参数与现行 JSON envelope,尤其
@@ -0,0 +1,98 @@
# ADR-0472Service Manager Legacy Rollback Preparation
- 状态:Accepted
- 日期:2026-08-20
- 关联 RFCQL-RFC-0001 D-64、D-274、D-275、D-379
- 关联 ADRADR-0314、ADR-0315、ADR-0362、ADR-0363
## 背景
QingLong 3.0 的 Docker adopted cutover 已有 `target_stopped → rollback_prepared → legacy_running` 双阶段回退,
但 systemd/OpenRC 双 authority 路径只做到 `target_stopped`。该记录证明 3.0 service 已停止、shutdown receipt 与
进程身份闭合,却没有重新计算 target/source SQLite 的哈希、稳定 inode 与 `-wal|-shm|-journal` sidecar,也没有一份
可由 root bridge 消费的持久回退授权。
直接复用 Docker rollback coordinator 不成立:Docker controller 与数据文件同属 Owner authority,而 systemd/OpenRC
需要把 Owner 私有状态判定与 root-only init mutation 分开。与此同时,低配路由器不能为回退增加数据库连接、常驻
supervisor、watcher 或第二套 SQLite 语义。
编辑前 GitNexus 对共享 reconciliation reader、service cutover consumer、cutover record parser 与 CLI main 的影响均为
LOW:最多 2 个直接调用者、6 个累计上游符号、0 条已识别 execution flow。
## 决策
### 1. 先关闭 Owner-side prepare,不在本阶段执行 root mutation
在现有 `@qinglong/local-owner-cli` 包内增加:
```text
ql3-local-deploy service-legacy-rollback-prepare --command-file <private.json>
```
命令必须提交 exact `cutoverId/profile/instanceId/generation/activationDigest``target_stopped` record digest 与
instance head digest。Owner 重新读取并绑定:
- 当前实例 head 必须仍是相同 generation 的 `target_stopped`
- stop record 必须来自 action=`stop`,含有效 shutdown receipt 且没有 manual reason
- 原 stop intent、Application v3 config、activation 与 legacy-silence commitment 的 digest/路径/lineage 必须一致;
- target/source SQLite 必须在同一稳定 fd 快照内完成 SHA-256inode/path/size/mtime 与 sidecar 状态必须闭合。
只有 `targetMatchesActivation=true``sourceMatchesRecovery=true` 且双方 sidecar clear 时,disposition 才是
`rollback_candidate`,命令才 no-replace 发布
`service-manager-gNN-rollback-prepared.json` 并把 instance head CAS 到 `rollback_prepared`。prepare 不执行
start/stop/restart/enable,不修改 SQLite,也不读取 Docker socket。
### 2. 非候选不污染实例终态
target 已产生写入或存在 target sidecar 时返回 `not-prepared/reconciliation_required`source 与 recovery 不一致或证据
无法稳定读取时返回 `not-prepared/manual_review`。两者都保持原 `target_stopped` head,不写 preparation,不进入
`manual_required`。这是为了保留显式重启 3.0 或进入后续数据 reconciliation ceremony 的能力;回退不可用不等于实例
已不可恢复。
### 3. Docker 与 service-manager 共用同一 reconciliation 实现
将既有 reader 抽成按 `profile/activation/source/target/expectedActivationDigest` 采集的内部共享接口,Docker 原入口继续
委托它,保持现有调用与返回 contract。这样两类部署使用相同哈希、stable-fd、sidecar 和 fail-closed 规则,不复制一套
容易漂移的数据判定。
### 4. 保持 package 粒度
实现继续位于 `local-owner-cli/src/deployment/service-manager`,没有新增 workspace package、production dependency、
binary 制品或常驻组件。模块边界按 Owner prepare 责任划分;只有将来 root bridge 需要独立 OS 分发/签名/依赖闭包时,
才重新评估是否拆包。
## 被否决方案
1. **stop 成功后直接启动 legacy**:没有数据重验证和 durable authorization,拒绝。
2. **非 rollback candidate 直接写 manual_required**:会错误封死 3.0 正向恢复,拒绝。
3. **复制 Docker reconciliation 代码**:两套哈希/sidecar 语义会漂移,拒绝。
4. **让 root bridge 自己读取 Owner SQLite 并决定能否回退**:混淆数据 authority 与 init mutation authority,拒绝。
5. **为 prepare 新建 workspace package**:没有独立交付和依赖边界,不制造微包。
## 验收证据
- 定向 service cutover/rollback preparation `10/10`,覆盖成功、exact replay、target 写后拒绝、Application config 漂移与
legacy-silence commitment 漂移拒绝。
- Local Owner 完整测试为 `175 total / 170 pass / 5 conditional skip / 0 fail`18-package clean build/逐包测试单次退出 0
backend 全量为 `1,507 total / 1,505 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 为 `108 source / 107 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。prepare 没有进入基础路由器闭包。
- Docker live gate 已依次完成 systemd root/non-root 两个 actor 的真实 stop、prepare 与 exact replay,随后在构建 OpenRC actor
前因 `node:24-alpine` registry mirror EOF/本机 credential helper 挂起而停止;因此本 ADR 不把 systemd/OpenRC 四组合门
记为完整通过,待镜像基础设施恢复后补跑 OpenRC root/non-root。测试容器和临时镜像均已清理。
- 本阶段不修改 SQL、migration、PostgreSQL repository/role/Pool 或 HA 语义,因此不重新生成 PostgreSQL HA 证据。
## 未完成
- root-only systemd/OpenRC legacy start barrier、响应丢失后的 inspect-only 收敛;
- Owner 对 legacy running、target stopped 与 preparation/data 未漂移的最终消费;
- `legacy_running` 后的 2.x readiness/health proof
- `reconciliation_required` 的 export、冲突裁决与受审回灌;
- 固定物理 Edge 的完整 prepare/commit/rollback 证据。
本 ADR 只关闭 service-manager 回退的 Owner prepare 阶段,不宣称 systemd/OpenRC legacy rollback controller 或
QingLong 3.0 升级/回退 Gate 已全部完成。
+1
View File
@@ -475,6 +475,7 @@
| [ADR-0469](./ADR-0469-optional-console-package-installation-observation.md) | 可选 Console Package Installation 只读观察 | Accepted |
| [ADR-0470](./ADR-0470-session-scoped-console-capability-discovery.md) | Console 会话级能力发现与服务端操作围栏 | Accepted |
| [ADR-0471](./ADR-0471-legacy-core-api-compatibility-baseline.md) | Legacy 核心执行 API 兼容基线 | Accepted |
| [ADR-0472](./ADR-0472-service-manager-legacy-rollback-preparation.md) | Service Manager Legacy Rollback Preparation | Accepted |
## 规则