Files
qinglong/docs/adr/ADR-0221-atomic-plugin-package-lifecycle-overlay.md
T

15 KiB
Raw Blame History

ADR-0221:原子、可重放的 Plugin Package 生命周期 Overlay

  • 状态:Proposed
  • 日期:2026-07-29
  • 关联 RFCQL-RFC-0001 D-140、D-141、D-142、D-174、D-175、D-207、 D-210、D-211
  • 关联 ADRADR-0140、ADR-0142、ADR-0143、ADR-0144、ADR-0184、 ADR-0186、ADR-0217、ADR-0220、ADR-0222

背景

QingLong 3.0 已经能安装、升级、回滚和安全隔离 Plugin Package,但安装状态机只描述 制品如何进入可用状态,publisher quarantine 则描述强制安全撤回。正常运维仍缺少三个 不同语义:

  • 暂停一个正常 Package,保留随后恢复的可能;
  • 恢复同一份、未被替换且仍可信的 Package;
  • 在保留历史的前提下逻辑卸载 Package。

直接修改 install state 会混淆部署执行与运维意图;复用 quarantine 会伪造安全事件; 删除 install/head/history 会破坏审计、回滚和 durable execution recovery。Task 与 Tool 分两次撤出还会暴露“Task 已停、Tool 仍可启动”或相反的部分状态。

该能力同时需要覆盖资源很小的路由设备和 PostgreSQL 集群。实现不能为每个概念增加 package、后台 daemon、timer、watcher 或缓存,也不能把 Cluster 依赖带入 Edge。

决策

1. 独立 append-only lifecycle overlay

生命周期不改写 Plugin Package install record。每次 disable|enable|uninstall 追加:

  1. canonical lifecycle event
  2. (Project, Package) current lifecycle head
  3. capability publication receipt
  4. 本次实际变更的 Package-owned Task transition evidence。

head disposition 只有 active|disabled|uninstalled。没有 head 表示当前安装仍按原 activation 语义为 activehead 必须精确绑定 installation、lock 和 install record digest。新的 installation 或 lock 不能继承旧 head。

event 同时绑定 mutation、Approved Action dispatch、action/impact digest、请求人与批准 人、expected lifecycle version 和完整 reference graph digest。相同 event 精确重放; mutation、dispatch、target 或预览漂移一律冲突。

2. 影响计划是批准对象,不是执行时提示

plan 在同一一致性数据库视图中重新计算:

  • current install 与 materialized resource generation
  • current lifecycle head
  • current Project Tool snapshot
  • Package-owned Task
  • blocking Run/recovery reference
  • durable consumer 数量与 canonical reference graph digest。

批准绑定完整 impact digest。执行事务必须重新计算并与批准值完全一致,不能只验证 Package name。当前已落地 consumer 是 Task ownership、Project Tool snapshot 与 generation-bound Workflow/Prompt automation publication。Secret binding 尚无 durable consumer,因此 contract 保留计数、blocking reference 与全图 digest 扩展点; 未来增加 consumer 时旧执行器必须失败关闭,不能把未知引用当作零。

3. 三种 transition

disable

  • 只接受 active
  • 为当前仍 enabled 的 Package-owned Task 追加 disabled revision
  • 发布排除该 source 的 Project Tool snapshot
  • 对包含 Workflow/Prompt 的 Package 追加 active→withdrawn publication
  • 原子写 event/head/receipt/Task evidence/Tool snapshot/automation publication。

enable

  • 只接受同一 current installation 的 disabled
  • 重新验证 quarantine、publisher trust/revocation、materialized generation、Project Policy 与 active source 上限;
  • 只恢复前一 disable receipt 中由生命周期实际关闭的 Task;
  • 发布重新包含该 source 的 Tool snapshot
  • 对包含 Workflow/Prompt 的 Package 追加 withdrawn→active publication。

uninstall

  • 只接受 disabled
  • live Run、未收敛 recovery 或任何 durable consumer reference 都是 blocker
  • 只把 lifecycle head 推进到 uninstalled,不删除 install、proposal、approval、 execution、event、snapshot 或 audit 历史;
  • OCI、offline bundle、staging 和本地缓存字节由后续独立 retention receipt 回收。

4. 新启动拒绝,已 durable 执行继续收敛

生命周期 transition 与能力 publication 在同一数据库写事务完成。事务提交后:

  • Run 从 queued 推进到 dispatching/running 时,若其精确 Task revision 属于 non-active lifecycle head,立即拒绝;
  • Tool start barrier 若命中同一 installation/lock 的历史 Tool definition,且 head non-active,立即拒绝;
  • 已经 durable running 的 Attempt/StepRun 仍按原 revision 完成、取消或恢复,不由 disable 热杀。

enable 把同一 head 恢复为 active 后,精确历史 revision 可再次通过 start fence Task current head 和 Tool current snapshot 仍由 lifecycle transition 发布的新版本决定。

SQLite 在唯一 BEGIN IMMEDIATE authority 内执行 transitionRun/Tool 围栏复用同一 连接和事务;PostgreSQL 使用 SERIALIZABLE repository 及 SECURITY DEFINER commit_plugin_package_lifecycle(jsonb, jsonb, jsonb, jsonb) 完成 server-side CAS。只有 ql3_package_executor 可执行该函数并只读 lifecycle ledger runtime、admin、package manager 和 worker ingress 既不能执行函数,也不能直接写表。

5. Profile 和部署边界

  • Local 固定当前 Owner human_confirmation
  • Cluster 固定 separation-of-duty,由 package manager 管理 proposal/approval,由 caller-driven package executor 执行;
  • Edge 单命令只处理一个 Packageactive source 上限 4
  • Standalone 单命令最多 4 个,active source 上限 16
  • Cluster 一批最多 16 个;
  • 复用现有 20 个 workspace package 的显式 subpath,不新增 package、进程、端口、 timer、watcher、listener 或常驻 cache。

PostgreSQL lifecycle repository 只从 @qinglong/cluster-postgres/package-executor 和显式 @qinglong/cluster-postgres/plugin-package-lifecycle 暴露;根入口、runtime、admin、 package manager、worker ingress 不取得 mutation authority。

Cluster review 使用短生命周期、append-only 的 durable plan ql3_package_executor 重算完整 impact 后只能 INSERTql3_package_manager 只能 SELECT 并据此创建 separation-of-duty Approval。executor 在消费审批前再次重算并做 exact comparemanager HTTP 不暴露 consume/execute,也不取得 lifecycle commit 权限。 plan JSON 的运行时和数据库硬上限均为 96 KiB,最长存活 15 分钟。

6. 恢复与失败语义

COMMIT response loss 通过 event digest、mutation、dispatch、target version 和 receipt 关系精确重放。SQLite readiness 与 PostgreSQL schema readiness 都复核 event/head/ receipt/Task relation;缺行、JSON/digest 漂移、Task evidence 不完整或 head CAS 丢失均 失败关闭。

隔离优先于正常恢复:存在 matching quarantine 或 publisher revocation 时,enable 拒绝。生命周期不能清除、覆盖或绕过安全隔离事实。

不采用方案

扩展 install state

拒绝。安装执行阶段和用户运维意图具有不同版本、恢复和审计语义。

把 disable 实现为 quarantine

拒绝。正常停用不是 publisher compromise,且 quarantine 不能被普通 enable 撤销。

直接删除 current head 或历史

拒绝。会破坏 rollback、durable completion、审计与 COMMIT response-loss 恢复。

Task、Tool 分事务撤出

拒绝。任一崩溃窗口都会留下部分可见能力。

uninstall 同步删除制品

拒绝。大文件删除和数据库逻辑退役不是同一原子域,会扩大路由设备写锁、闪存和恢复 成本。

新建 lifecycle package 或后台 reconciler

拒绝。现有 runtime-core/local-sqlite/cluster-postgres 权限与 Profile subpath 足以承载, 另建 package/daemon 违反 D-175/D-207,并增加低配设备供应链与空闲资源。

影响

  • workspace 保持 20 个 package
  • SQLite capability 由 lifecycle v39 推进至 automation publication v40 PostgreSQL control-core 由 lifecycle plan v41 推进至 automation publication v42
  • D-210 inventory v1 不静默扩展 availability enum,后续用显式新响应 schema 返回 lifecycle disposition
  • disable 不热杀已 durable execution,运维界面必须明确“停止新启动、等待已有执行 收敛”;
  • Workflow/Prompt durable publication、absent generation tombstone 与 caller-driven startup recovery 已实现;实际 Workflow/Prompt execution、Prompt 产品入口、Secret binding 与 quarantine 收敛未实现前,D-211 不能标记 Accepted。

当前验证

  1. runtime-core lifecycle contract、digest、reference graph、event/receipt/head 与 replay 测试通过;
  2. SQLite 0077/0078、Drizzle schema、readiness、repository 和 exact replay 已实现; lifecycle repository 5/5Run/Tool lifecycle/quarantine 定向 18/18,相关 SQLite 回归 37 项通过;另一个 Run contract suite仅因本地缺 ts-node/register/transpile-only 未启动;
  3. Run transaction GitNexus 影响为 MEDIUM12 direct、29 total),Tool start barrier 为 LOW2 direct、20 total);改动只增加 non-active guard
  4. PostgreSQL pg-0041、schema contract/readiness、Drizzle schema、repository 和 package-executor 受限出口已实现;impact planner 不直接读取 runs,而经 SECURITY DEFINER plugin_package_lifecycle_blocking_runs 做 Project/Package 作用域和 129 条硬上限查询;commit 复用 lock_active_plugin_package_project,不向 package executor 扩大 Project/Run 表权限;迁移/权限契约 37/37;
  5. 空 PostgreSQL 18 容器按真实六角色边界重放 41/41 migrations,最终 capability 为 40;函数 owner 为 ql3_migration 且为 SECURITY DEFINER,只有 ql3_package_executor 有 lifecycle commit/有界 Run 查询 EXECUTE,业务角色均无 lifecycle 表写权限,package executor 也没有 runs 表 SELECT
  6. 正式 PostgreSQL 18 physical-streaming HA 门已通过:remote_apply、主备提升、 旧主 fencing、pg_rewind 回归、disable COMMIT-response-loss 恰好一次收敛、 exact replay、separation-of-duty、Run/Tool 原子拒绝与 enable 恢复均为 true 2 event、2 receipt、4 Task transition 和 lifecycle v2 active head 经 WAL、 promotion 与重新连接后保持一致;
  7. 本次真实门禁额外发现并修复了 package executor 直接读取 Run/Project 的越权路径 及 PL/pgSQL previous_task 变量/别名冲突;pg-0041 冻结 checksum 更新为 99669c63c891124aa0741586d5e92bd40d7a3ddf322ec7da970790958f554101
  8. Local 产品入口已复用现有 local-admin/package-lifecyclelocal-owner-cli/package-commandplugin-package.lifecycle.plan 先返回完整 canonical impact 和低敏摘要,plugin-package.lifecycle.execute 再以私有命令文件 携带该 impact 与稳定审批 identity,固定当前 Owner human_confirmation,串行完成 request/decision/consume/transition。任何部分完成均从 durable Approval/dispatch 继续,event 时间固定为 dispatch 创建时间,响应丢失重跑得到同一 receipt;不新增 workspace package、连接、进程或后台循环。本机 lifecycle service 定向 2/2、 local-admin 81/81、既有 ql3-package 回归 2/2
  9. Cluster 已在现有 cluster-admin/cluster-postgres package 内完成 caller-driven verticalpg-0042-plugin-package-lifecycle-plans、capability v41、manager SELECT-only/executor SELECT+INSERT、management service、公开 lifecycle.propose|decide|inspect transport、精确客户端响应校验,以及仅由受信 caller 调用的 plan/execute executor;没有新增 package、daemon、timer 或端口。 plan 最大 96 KiB/15 分钟,pg0042 冻结 checksum 为 a242067854f4ee5231c75874fa77dda364da962a2dcaf570b5015ee069818c4b。 transport 11/11、客户端 TLS 6/6、PostgreSQL 迁移/权限 30/30 通过;
  10. 正式 PostgreSQL 18.4 arm64 HA 重跑新增两轮 executor plan → manager proposer → distinct reviewer → executor re-plan/consume/ transitiondisable 后再 enable。2 条 durable plan、lifecycle v4 active head、 receipt 与 plan 在 remote_apply、timeline 1→2、旧主 fencing、 pg_rewind 只读同步重入后精确保留,新增 gate pluginPackageLifecycleManagedPlanReviewExecutesExactly=true,总 gates.passed=true。依赖从本机受审 Cluster Admin 镜像离线复用,未访问 registry;
  11. Local SQLite 已完成真实子进程 lifecycle crash matrix disable|enable × edge|standalone × 8 个事务窗口共 32/32 通过。窗口依次位于 Task revision、Tool snapshot、lifecycle event/receipt/task/head 之后及 COMMIT 前/后;28 个 COMMIT 前 SIGKILL 均保持原 Task、snapshot 和 ledger/head 零部分写,重放返回 created4 个 COMMIT 后 SIGKILL 均完整 durable 且重放 返回 existing。Edge 使用 DELETE/FULLStandalone 使用 WAL/FULL,全部 integrity_check、foreign-key check、readiness 与 exactly-once relation 通过;
  12. Cluster 管理进程已在同一正式 PostgreSQL 18.4 arm64 HA 门完成 disable|enable × plan|propose|decide|execute 共 8 个真实 Node SIGKILL 窗口:每个阶段先提交 durable fact、fsync 低敏 marker 后被杀,再由全新进程以稳定 identity 重放,8 次均返回 existing。该门发现并修复两处实际恢复顺序缺陷: plan runner 现在先按 actionRef 恢复并严格核对 action/Project/Package/requester/ lifetimeexecution runner 现在先从 exact dispatch 重建 event 并检查 durable receipt,只有 receipt 不存在时才 re-plan。新增 gate pluginPackageLifecycleManagementProcessCrashesConvergeExactlyOnce=true 8 个窗口及其 plan/receipt/head 经 remote_apply、timeline 1→2、旧主 fencing、 pg_rewind 只读同步重入后仍一致,总 gates.passed=true
  13. ADR-0222 已完成 Workflow/Prompt generation-bound durable publication SQLite 0079/0080 与 capability v40PostgreSQL pg-0043 与 control-core v42 append-only publication/head、exact replay、materialized/lifecycle/head readiness、双方言 transaction-bound writer、absent tombstone 与有界 keyset pending-source 已实现;
  14. Local 生命周期授权失败回滚测试与完整 local-sqlite 165/165 通过;正式 PostgreSQL 18.4 arm64 HA 中 publication 从 v1 经四次 lifecycle transition 原子推进到 v51 Workflow + 1 Prompt 经 remote_apply、promotion、fencing、 pg_rewind 和 fresh control replicas 后保持一致,两个 automation gate 与总 passed 均为 true
  15. caller-driven publication coordinator 已接入 Local/Cluster startup,顺序固定为 Task publication → automation publication → Tool snapshot → admissionLocal 启动定向 14/14、Cluster 定向 11/11。实际 Workflow/Prompt execution、Prompt 产品入口、Secret binding 以及 quarantine 下的 automation 收敛仍未完成,因此 本 ADR 保持 Proposed。本次离线 HA 成功不代表 registry 安装门已完成。
  16. 最终离线复验完成 20 个 workspace package 的干净构建与全量测试,零失败; SQLite current contract v40 已同步 Compose image label、preflight、rollout、 evidence、OCI 与 physical Edge 审计。PostgreSQL 18.4 arm64 physical HA 在跳过 image pull 的条件下再次通过,新增 pluginPackageAutomationRecoverySourceConverges=true,总 gates.passed=true