Files
qinglong/docs/adr/ADR-0310-docker-target-start-restart-barrier.md
T

155 lines
8.5 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-0310Docker Target 启动/重启屏障与人工终态
- 状态:Accepted
- 日期:2026-08-09
- 关联 RFCQL-RFC-0001 D-05、D-17、D-63、D-64、D-65、D-259
- 关联 ADRADR-0065、ADR-0243、ADR-0308、ADR-0309
## 背景
ADR-0309 已让 deployment owner 以真实 Docker controller 把 2.x owner 收敛为
`legacy_stopped`,并让 adopted Application v3 在取得 signal、SQLite、Secret、Plugin Package、AI
和 lifecycle authority 前消费该 commitment。但“旧实例已经停止”不等于“可以用一条无状态 shell
命令反复启动 3.0”:若 controller 在 Docker start 已产生副作用后崩溃,自动重试可能重复启动;若 target
曾 active 后旧实例被人工拉起,直接重启 target 又可能形成双 owner。
已删除的 ADR-0065 孵化包记录过正确状态语义,但它没有 production consumer。该语义必须进入现有
`ql3-local-deploy` 产品,而不是恢复第二十个 workspace package。低配路由器也不能为此安装 daemon、
watcher、数据库连接或进程管理 framework。
编辑前 GitNexus 显示部署 CLI `main` 为 LOW、1 个直接文件调用和 0 条产品执行流;新 supervisor 建立后,
重放入口为 LOWjournal path/read helper 为 MEDIUM、最多 6 个包内直接调用者且仍为 0 条产品执行流,
没有 HIGH/CRITICAL 风险。
## 决策
### 1. 继续使用现有 deployment product
新增两个显式命令,但不新增 package:
```text
ql3-local-deploy cutover-target-start
ql3-local-deploy cutover-target-restart
```
二者只存在于 `@qinglong/local-owner-cli/deployment/cutover`。Application 不导入 Owner CLI、Docker、
journal writer 或 controllerCluster/Worker/PostgreSQL 产物也不取得本机 Docker authority。
### 2. Target identity 在 start 前冻结
命令只接受完整 64 hex 的 legacy/target container ID 和不可变 `repo@sha256` target image。首次启动或重启
前,controller 必须同时证明:
- target 为 `created|exited|dead``Running=false``Restarting=false``Paused=false``Pid=0`
- restart policy 为 `no`root filesystem 只读、非 privileged 且启用 `no-new-privileges`
- `Cmd` 精确读取受审 Application v3 config
- 当前 UID 私有 config 仍绑定 cutover/Profile/instance/activation/legacy commitment
- 一个且仅一个读写 bind mapping 能分别解释 config、commitment、activation 和 legacy source 在 target
内的路径;
- container Created/Image/Name/ID、config digest 和 mount mapping 形成低敏 digest,随后不可漂移。
deployment controller 不把 `container name`、短 ID、PID、operator boolean 或单独的 Docker exit code当作身份。
### 3. 每代 start 只能跨越一次持久屏障
首次 generation 固定为 1
```text
0001 legacy_stop_requested
0002 legacy_stopped
0003 target_start_requested | manual_required
0004 target_active | manual_required
```
`0003` 先通过现有 hard-link no-replace primitive 原子发布,再调用一次
`docker container start <exact-id>`。若 controller 在屏障后崩溃,原命令重放只能 inspect,不能再次 start。
只有同一 target identity/binding 已 running,并且 Application 写出了新的、校验通过的 Linux startup receipt
才发布 `target_active`
start 响应失败或丢失也不直接等于失败:controller 在 Edge 30 秒/120 次、Standalone 60 秒/240 次的有界窗口
内只做 inspect。窗口结束仍不能证明 active 时发布 terminal `manual_required`,错误正文不入 journal,只保存
有限 reason、uncertain state 和 domain-separated error digest。
### 4. 每次 restart 都重新证明 Legacy 静默
generation 2..15 固定追加四条:
```text
legacy_recheck_requested
legacy_reverified | manual_required
target_restart_requested | manual_required
target_active | manual_required
```
Legacy inspect 必须重新得到与 `0002` 相同的完整 container identity digest 和 source bind digest,并再次证明
stopped + restart=no。只有 `legacy_reverified` 的 record digest 才能成为 restart request 的直接前驱;即使攻击者
重算后段 JSON 自身 digest,只要把它从该前驱链拆开仍会失败关闭。
restart 前 target 必须停止、identity/binding 不漂移,磁盘上的 startup receipt 必须等于上一代
`target_active`restart 后必须出现不同 receipt。这样 supported controller 路径不会把旧 active receipt
误认成新进程,也不会跳过 Legacy recheck。
### 5. `manual_required` 不可自动离开
本批支持四个有限 reason
```text
legacy_silence_unproved
target_preflight_unproved
target_start_result_unproved
target_restart_result_unproved
```
同一 generation 的 decision/outcome 使用固定中性文件名,因此 requested/active 与 manual 两个并发 writer
竞争同一个 no-replace inode,不会各自在不同文件中“双赢”。终态原命令只验证磁盘链并返回 `existing`,不重新
打开 Docker socket。任何路径都不自动启动 2.x。
### 6. 资源与源码结构
该能力是一条人工触发的一次性命令:没有常驻 timer、watcher、listener、daemon、数据库连接或目录历史扫描;
每代固定最多四个小 JSON,最多 15 代/60 条 target 记录,沿用 cutover catalog 的 64 项上限。轮询仅存在于
启动命令生命周期中。
实现没有形成新的顶层 package,也没有把所有逻辑塞进一个根文件。`cutover/` 内部分为 target command
contract、Docker/application evidence、journal primitive、record evidence 和 supervisorworkspace 根源码
仍只有公共/二进制入口。
## 被否决方案
1. **start 失败后直接重试**:失败可能发生在 Docker 已完成副作用之后,拒绝。
2. **只看容器 running**:无法证明正确 Application 已取得并报告 active,拒绝;必须同时消费新 startup receipt。
3. **复用上一代 Legacy commitment 直接 restart**:旧实例可能已被人工拉起,拒绝;每代重新 inspect。
4. **让 Docker `unless-stopped` 自动恢复 adopted target**:会绕过 restart generation 和 Legacy recheck,拒绝;
target 必须 `restart=no`
5. **为 supervisor 新建 workspace package**:没有新的 artifact/dependency/consumer 边界,拒绝。
6. **让 Application 调 Docker 或写 deployment journal**:会把常驻 runtime 提升为部署 owner,拒绝。
## 验收证据
- 新专项 6/6:正常首次启动与无 Docker 重放、屏障崩溃 inspect-only、未知 start 进入 manual、restart 前
Legacy reproof、Legacy 漂移禁止 restart、自洽但断链的 restart record 失败关闭。
- `@qinglong/local-owner-cli` 完整沙箱外回归 116/116;沙箱内唯一失败是既有 provider test 不能监听
`127.0.0.1` 的 EPERM,沙箱外原样通过。
- Application 43 项为 40 pass/3 条件 skippackage boundary 8/8、dependency 48/48、完整 19-package clean
build/test 与 Backend 1,1141,112 pass/2 条件 skip)通过。
- workspace 为 781 source/32 root/749 nestedOwner CLI 为 56/1/55package strict TypeScript/closure check
通过,workspace package 仍为 19,不新增 production dependency。
- 四个常驻 Application 制品仍 compatibleEdge/Standalone 为 4,744,898/4,745,042 bytes、432 files、
110 modulesAI 两档为 6,132,511/6,132,667 bytes、496 files、109 modules;最大实测 RSS delta
21,528,576 bytes,低于 24 MiB 门,Owner controller 未进入闭包。
- 最终 GitNexus 为 43,641 nodes/99,242 edges/1,711 clusters/272 flows;主入口和 evidence parser 为 LOW
journal read/path 为 MEDIUM、5 个包内直接调用者、0 process;`detect_changes` all/compare `develop` 仍为
12 files/31 symbols 与 14/34、low/0 process。QL3 孵化树未完整进入 Git baseline,因此 diff graph 只作
补充,不替代逐符号 impact、运行测试与 artifact 门。
- 本批未改 SQL、migration、PostgreSQL/Cluster runtime 或部署资源,不重复生成 PostgreSQL HA 物理晋升证据。
## 未完成
- systemd/OpenRC legacy 与 target controller
- `manual_required` 的只读诊断、双确认 resolution 与新 cutover ceremony(后续已由 ADR-0313 关闭);
- target 显式 stop 与最小写后分类(后续已由 ADR-0314 关闭;数据域 reconciliation/rollback 仍未完成);
- adopted Compose target 的受审 create/config 生成器及真实 Docker live/crash gate
- Cluster/Kubernetes 独立 cutover authority。
因此本 ADR 关闭 Docker target start/restart 的产品状态机与保守终态,不宣称 D-64 或 QingLong 3.0 整体完成。