feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,128 @@
# ADR-0199Generation-Fenced 本机 Compose Rollout
- 状态:Accepted
- 日期:2026-07-29
- 关联 RFCQL-RFC-0001 D-184、D-187、D-188、D-189
- 关联 ADRADR-0194、ADR-0197、ADR-0198
## 背景
ADR-0198 能证明本机 exact digest、当前 SQLite 和 Compose 合并结果兼容,但
`docker compose up -d` 的成功只表示 daemon 接受了创建请求,不表示应用完成存储、
Secret、Scheduler、Executor 和 Plugin Package recovery 激活。Docker 与 SQLite 也
不存在可同时提交 selection、容器状态和部署回执的事务。
因此 rollout 必须显式处理四个窗口:preflight 后 generation 被并发切换、候选容器
启动后没有 active、rollback selection 已提交但容器尚未恢复,以及 receipt 已落盘
但调用方没有收到响应。
## 决策
### 1. 复用现有部署 CLI,不拆新 package
入口为:
```text
ql3-local-deploy compose-apply --command-file /absolute/private-command.json
```
command 固定包含 deployment root、canonical Docker executable、explicit canonical
Unix socket、root acknowledgement、expected generation、rollout UUID、开始时间和
一组预授权的 failure rollback mutation/time。CLI 不接受 shell command、Compose
额外参数、健康字符串、image、container ID 或 caller 声明的 compatibility。
Docker 调用复用一个部署包内部 runner:显式 `--host unix://...`、一次性 `0700`
空 config、清理 proxy/context 环境、bounded output/timeout。该实现仍属于已有
`local-owner-cli` 的部署内聚模块,不形成新 workspace package 或常驻进程。
### 2. Rollout lock 与 revision CAS 双向 fencing
每次 apply 先以 normalized command 的 exact bytes 获取
`service/.compose-rollout.lock`。普通 `compose-revision` 在该锁存在时失败关闭;
只有同一 apply 的失败恢复可携带 exact lock intent 推进 rollback generation。
apply 获取锁后再次检查 active selection 与 immutable revision。正常路径只接受
expected generation;恢复路径只接受 `expected+1`、previous 指回 attempted
generation、rollback target 等于 attempted previous,且 mutation 等于 command
预授权值。其他状态均不能猜测。
### 3. Active 是应用事件,不是容器 Running
成功 apply 固定使用两个正式 Compose 文件,并增加:
```text
--detach --force-recreate --no-build --pull never --remove-orphans
```
随后只观察新 service container,逐项复核:
- container ID 与 Running 状态;
- exact `Config.Image`
- generation/mutation labels
- read-only rootfs、network none、非 privileged
- 应用 stdout 中 schema v1、正确 Profile、AI excluded 的结构化 `active` 事件。
Edge 窗口固定 30 秒,Standalone 固定 60 秒;轮询次数同时有上限。没有
`active`、Docker 命令失败或 container identity 漂移都按候选失败处理,不能由
Compose restart policy 掩盖。
### 4. 失败是 generation-forward recovery
若 attempted generation 有 previous generation,控制器使用 command 中预授权的
mutation/time调用既有 rollback CAS,创建 `attempted+1` generationimage 来自私有
immutable历史 revision。随后对新 generation 重新执行完整 preflight、Compose
apply和 active 验证。
若初始 generation 没有 previous,控制器显式 stop,结果为 `failed_stopped`。旧
digest rollback 也无法 active 时,不写成功/回退 receipt、不删除 rollout lock
由 operator 保留现场并原样重放。
返回语义:
- `active`:候选 generation 正常;
- `rolled_back`:坏候选已隔离,新的 rollback generation active
- `failed_stopped`:没有可恢复历史,service 已停止。
后两者 CLI 输出结构化结果但退出码为 2,避免自动化把已恢复的失败候选误报为发布
成功。
### 5. Receipt 是确定性私有事实
`service/rollouts/<rolloutId>.json` 为当前 UID、`0600`、no-replace 的 canonical
receipt,绑定 normalized command digest、attempted/final generation、结果和
active event digest。recorded time 取 command 已授权的 rollback time,因此
stage/rename/fsync 任一窗口重放都生成相同 bytes。
receipt 已存在时先严格复核完整 shape、canonical bytes、command digest 与结果内部
一致性,然后直接返回,不再次操作 Docker。receipt 已发布但 lock 未清理时,同一
命令只清理 exact lock。rollback selection 已推进而 receipt 缺失时,同一命令只
继续 rollback apply,不重新启动失败候选。
## 发布门
`ql3-local-compose-rollout-live-contract.cjs` 对 release pushed local digest 的 Edge
和 Standalone 分别创建 fresh authority、运行真实 compose-apply、验证私有
receipt,再发送 SIGTERM,要求应用发布 `event=stopped`
`stopResult=stopped`,最后 `compose down`。任一 Profile 失败会阻断共享 image
release workflow。
## 验收证据
- `local-owner-cli` 40/40
- image release contract 20/20
- 22 importer dependency/source audit 和 Edge import audit 零 finding
- workspace 保持 22 packages,无新增生产依赖;
- arm64 Docker Compose v5.3.1 上,Edge 128 MiB/64 PID 与 Standalone
256 MiB/256 PID 对 localhost exact manifest `sha256:e696…9b70` 均返回
rollout active、durable receipt、graceful cleanup
- 清理后没有遗留带 QingLong deployment generation label 的容器。
本机 localhost digest 只是 live evidence,不替代 GHCR 双架构 manifest、签名和
attestation 的正式 release 记录。
## 未包含
- migration 前 backup、写后 SQLite capability 与旧 image 的数据回退判定;
- Docker daemon/主机断电、ENOSPC 和 stage/fsync 实机 fault matrix
- systemd/OpenRC install、enable、健康与 rollback
- 正式 GHCR release 成功记录和物理路由器长期资源证据。