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

272 lines
15 KiB
Markdown
Raw Permalink 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-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 后只能 INSERT`ql3_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-lifecycle`
`local-owner-cli/package-command``plugin-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
vertical`pg-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
零部分写,重放返回 `created`4 个 COMMIT 后 `SIGKILL` 均完整 durable 且重放
返回 `existing`。Edge 使用 `DELETE/FULL`Standalone 使用 `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`