Files
qinglong/docs/adr/ADR-0309-deployment-owned-legacy-silence-commitment.md
T

129 lines
7.1 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-0309:部署侧 Legacy Silence Commitment 与 adopted v3 启动门
- 状态:AcceptedDocker legacy-stop slice
- 日期:2026-08-09
- 关联 RFCQL-RFC-0001 D-05、D-06、D-17、D-63、D-64、D-65、D-85、D-87、D-259
- 关联 ADRADR-0064、ADR-0065、ADR-0066、ADR-0178、ADR-0194、ADR-0243、ADR-0308
> ADR-0310 已在同一 deployment product 中补齐 Docker target start/restart barrier、
> barrier 后 inspect-only 和 terminal `manual_required`。本 ADR 的 legacy-stop commitment
> 仍是每一代 target controller 的前驱事实。
## 背景
QingLong 3.0 已有旁路 SQLite adoption/activation、独立 Local Application、fresh deployment
与 systemd/OpenRC/Compose 描述符,但 adopted v1 仍可直接启动。source SQLite write fence 只能
证明一个数据库 writer 被阻塞,不能证明 2.x Scheduler、子进程、网络和其他外部副作用已经
静默。因此旧操作手册中的“先自行停止 2.x”不是可执行安全边界,D-64 也一直保持 Proposed。
ADR-0243 删除孤立的 `local-cutover` workspace package 是正确的:它没有 binary、部署消费者或
controller。现在 `@qinglong/local-owner-cli` 已经拥有真实 `ql3-local-deploy` 短生命周期产品、
私有 command-file 协议、Docker socket controller 和 durable file publication primitive,切换
能力应成为其 `src/deployment/cutover/` 内部 domain,而不是恢复第 20 个 package。
## 决策
### 1. 先关闭可证明的 Docker legacy-stop slice
新增 `local.deployment.cutover.legacy-stop`
`ql3-local-deploy cutover-legacy-stop`。命令精确绑定:
- deployment root、Docker executable 与 Unix socket
- cutover ID、Profile、target instance ID
- 私有 SQLite activation 文件及 reviewed activation digest
- activation 对应的 canonical legacy source path 与预期容器内 database path
- 完整 64-hex legacy container ID 和请求时间。
controller 不接受裸 PID、容器名、tag、调用方布尔值或自报“已停止”。它先验证 activation exact
shape/digest/Profile,再对精确 container ID 执行 `update --restart=no``stop --time 30`
`inspect`。只有 `Running=false``Restarting=false``Paused=false``Pid=0`、状态为
`exited|dead` 且 restart policy 为 `no`,并且唯一 bind mount 把 activation 的 source path
精确映射到预期容器内 database path 时,才承认 database writer 与该容器拥有的外部副作用
已静默。source path 必须先与 activation 的 `sourcePathDigest` 相等;container ID、
created/name/image、mount source/destination/RW 的低敏 identity digest 与 Docker endpoint digest
一并进入 commitment。
当前不接受 systemd/OpenRC legacy 或 Kubernetes/remote cluster evidence。无法证明的 controller
必须失败关闭,不能用人工 assertion 降级。
### 2. journal 有界、不可覆盖且可精确重放
每个 cutover 使用当前 UID 私有 `0700` 目录,最多保留 64 个 cutover
```text
service/cutovers/<cutoverId>/
├── 0001-legacy-stop-requested.json
└── 0002-legacy-stopped.json
```
记录为 `0600`、最大 64 KiB,由现有 stage→fsync→hard-link no-replace→directory fsync primitive
发布。第一条记录在任何 Docker mutation 前完成;第二条绑定第一条 digest。崩溃发生在 stop 前或
期间时,原命令可幂等重做 legacy 收敛;第二条已存在时只验证 exact identity/digest 并返回
`existing`,不重新打开 socket,也不重复 stop。目录漂移、跨 cutover 重放、activation/endpoint/
container 漂移或 retention 超限全部失败关闭。
### 3. adopted application v3 强制消费 commitment
新增 `qinglong/local-application-process@v3`,只允许 `storage.mode=adopted`,并要求:
```text
cutover.cutoverId
cutover.commitmentPath
cutover.expectedCommitmentDigest
```
Application 自己只读取并验证 commitment,不导入 Owner CLI、Docker 或 cutover controller,也不
停止 2.x。commitment 必须精确绑定 config 的 cutover ID、Profile、instance ID 与 activation
digest,并通过 payload digest、私有文件身份和 authority-path 去重。验证发生在 AI 选择、Plugin
Package source、signal subscription、SQLite/Secret/Recovery 和 lifecycle 之前。
fresh v2 不需要 commitment,行为不变。adopted v1 保留离线解析兼容,但生产进程会在取得任何
runtime authority 前返回稳定的 `QL3_LOCAL_APPLICATION_CUTOVER_COMMITMENT_INVALID`,因此不能
成为绕过门。
### 4. 资源与 Profile 边界
本能力不新增 workspace package、依赖、daemon、timer、watcher、listener、数据库连接或历史扫描。
Edge 与 Standalone 只在一次人工 cutover 命令中支付三个 Docker 调用和两个小文件;常驻 Application
只增加一次 16 KiB 上限私有 JSON 读取和 SHA-256。它适用于低配 Docker 路由器/NAS,也不会让本机
文件/Docker authority 进入 Cluster、Worker 或 PostgreSQL 产物。
### 5. D-64 仍未全部关闭
本 ADR 关闭的是“没有 legacy silence evidence 仍可启动 adopted target”的产品旁路,以及
`legacy_stop_requested -> legacy_stopped` 的可恢复前半段。以下仍是独立 Gate:
- `manual_required` 的只读诊断、人工 resolution 与新 cutover ceremony
- systemd/OpenRC legacy controller
- systemd/OpenRC target controller 与 target 显式 stop
- adopted Compose target 的受审 create/config 与真实 Docker live/crash Gate
- target 产生新 3.0 事实后的数据对账式回退;
- cluster/Kubernetes 独立 cutover authority。
因此 Application active 仍不等于完整 deployment cutover completed,任何路径仍不得自动重启 2.x。
## 否决方案
1. **恢复 `@qinglong/local-cutover` package**:没有新增独立 artifact/dependency/权限或版本责任,只会
重新制造已删除 importer;拒绝。
2. **只依赖 SQLite `BEGIN IMMEDIATE`**:不能证明 Scheduler、spawn、网络或外部系统静默;拒绝。
3. **接受 operator boolean/PID/container name**:不可稳定绑定 owner,也无法阻止重放和 PID/name
复用;拒绝。
4. **由 Application 调 Docker 或停止 legacy**:形成稳态 runtime 与部署 authority 循环,并让低配
设备常驻 socket 权限;拒绝。
5. **一次 shell 串联 stop 后直接 start**:崩溃后无法判断副作用是否发生;拒绝。
6. **把本机 marker 用于 cluster**:本机文件和 Docker endpoint 不是 Kubernetes/PostgreSQL 的租约、
fencing 或 quorum evidence;拒绝。
## 验收
1. 精确 Docker stopped evidence 发布 commitment,完全相同命令返回 `existing` 且不再调用 Docker。
2. running/restarting/PID/restart-policy/container identity 任一不满足时只保留 intent,不发布
commitment。
3. v3 commitment 的 shape、digest、cutover/Profile/instance/activation 任一漂移,Application 在
signal/storage 前失败。
4. adopted v1 不能启动,fresh v2 行为不变。
5. package/dependency/boundary/artifact 审计证明 package 数仍为 19Owner cutover 不进入 Application
或 Cluster production closure。
6. Edge/Standalone 常驻路径没有新增 timer、watcher、socket、数据库连接或历史扫描。