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

7.1 KiB
Raw Blame History

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-stopql3-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=nostop --time 30inspect。只有 Running=falseRestarting=falsePaused=falsePid=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:

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,并要求:

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、数据库连接或历史扫描。