9.0 KiB
ADR-0153:有界的生产 Plugin Package Task 发布与启动恢复
- 状态:Accepted(共享协调器、本机 application gate、Cluster package-executor Job、 双方言 pending source 与真实 PostgreSQL/HA 门已实现;receipt 级 COMMIT-response-loss、Tool/Workflow/Prompt consumer 尚未实现)
- 日期:2026-07-26
- 关联:ADR-0138 至 ADR-0140、ADR-0149 至 ADR-0152、 QL-RFC-0001 D-144 至 D-147
背景
ADR-0152 已经保证一代 Package Task 在数据库内原子发布,但没有决定谁在生产入口中 连接 active generation、source bytes、immutable materialized revision 和 reconciliation repository。仅有 adapter 会留下四个产品缺口:
- 安装已 active、Task receipt 尚未提交时,应用可能提前开放 scheduler/executor;
- 每次重启若重新读取 staging/OCI,会把已经耐久化的 revision 又退化成外部可用性依赖;
- generation 在物化或提交期间切换时,旧 receipt 可能被误报为当前;
- 无界扫描、后台 watcher 或常驻缓存会把低性能路由设备的启动和空载预算变成未知量。
Cluster 还必须保持 management 与 execution authority 分离:公开管理 Pod 不能因为 Task 发布取得 Registry credential、Kubernetes write RBAC 或 package-executor 数据库 角色。
决策
1. 不新增 workspace package
共享发布与恢复协议放在既有
@qinglong/runtime-core/plugin-package-task-publication 显式 subpath。pending adapter
分别留在 local-sqlite 和 cluster-postgres/package-executor;本机组合进入
local-application,Cluster 组合进入 cluster-admin 已有的一次性 recovery process。
该能力没有独立版本、部署或供应链生命周期,因此不得拆出只有少量文件的新 importer。
本切片不新增第三方依赖、schema、连接池、timer、watcher、socket 或常驻缓存;
packages/ 仍为 21 个 importer。
2. 每次发布都以 active generation 为围栏
PluginPackageTaskPublicationCoordinator.publishActive(Project, Package) 固定执行:
- 读取当前 active resource generation;不存在则返回
absent; - 按
generationDigest查 immutable materialized revision; - revision 已存在时直接复用,禁止再次读取 staging/OCI;
- revision 不存在时,以当前 generation、完整 lock 和受限 byte source 执行一次 materialization,再 create/exact-replay 发布 revision;
- 调用 generation-bound Task reconciliation repository;
- 再次读取 active generation。
最终 generation 仍完全相同时才返回 current。已经切换或消失时返回
superseded,不得把旧 receipt 当作当前 admission 事实。损坏输入与确定性冲突进入
人工处理;外部存储、OCI、Kubernetes 或数据库不可用只进入有界重试。
3. pending source 只暴露缺 receipt 的当前 active Package
SQLite/PostgreSQL repository 实现相同的稳定 keyset page:
- 只选择当前 active install;
- 以 Project、Package、generation、lock 精确左连接 reconciliation receipt;
- 只返回缺少当前 receipt 的候选;
- 按
Project + Package唯一排序,读取limit + 1生成 continuation; - 单页最大 64,最多 64 页。
恢复默认使用 8 项 × 8 页并逐项处理;Cluster 已有 Job 配置当前默认使用 16 × 16,
但同样受 64 × 64 硬上限约束。每轮结束必须从游标起点额外 probe 1 项,捕获扫描期间
新出现或被 generation 切换重新暴露的 pending work。只要还有 pending、retry 或
manual-required,safeToAdmit=false。
4. 本机 application 在 Secret 与执行栈之前失败关闭
edge/standalone 启动顺序固定为:
- storage ready;
- 既有 Plugin Package install/activation recovery;
- Plugin Package Task publication recovery;
- Secret keyring、Run recovery、scheduler/execution lifecycle;
- application admission。
本机复用同一个 SQLite operation authority、同一个 activation publisher 和既有私有 staging byte source。相关 adapter 保持 lazy import:无 pending Package 时不打开 staging resource session,不引入扫描器或后台线程。默认最多观察 64 个候选,适合低配 路由设备;需要更大批次的 standalone 可显式提高预算,但不能越过硬上限。
5. Cluster 复用一次性 package-executor recovery Job
Cluster 不创建常驻 publisher,也不把能力装入 cluster-control 或公开 management
host。已有 package recovery Job 在同一个最多 1 连接的 package-executor Pool 中:
- 通过 package-executor schema/role readiness;
- 完成 install/activation recovery;
- 复用同一个 Kubernetes generation publisher;
- 复用同一个 allowlisted、签名校验的 OCI authority,并只在 revision 缺失时创建 caller-owned resource byte reader;
- 使用 executor-only materialized/reconciliation repository 完成 Task 发布;
- 关闭数据库和短生命周期 authority 后退出。
注入非 OCI stage authority 的测试或特殊部署必须同时显式注入 resource byte source, 不能隐式取得 ambient 文件、网络或 credential authority。完成事件同时输出安装恢复和 Task 发布恢复的低敏汇总。
6. 本 ADR 只开放 Task 发布闭环
这次接入不等于 Package 的所有资源都可执行:
- Tool 仍只有 Definition,没有 immutable global handler snapshot;
- Workflow/Prompt 仍只有 materialized definition,没有版本仓库和执行 consumer;
- Secret binding ceremony 仍未建立;
- receipt/history/ownership retention 仍禁止删除。
因此 production startup 只以 Task reconciliation receipt 作为 Task admission gate, 不能由 materialized revision 的存在推导其他资源已激活。
Profile 影响
- edge:默认 8 × 8、单 SQLite authority、逐项恢复、无后台线程;revision exact replay 不再读取 staging,空队列只执行有界数据库 probe。
- standalone:与 edge 使用同一实现,可显式提高恢复预算,不改变事务和资源上限。
- cluster:默认 caller-driven Job 16 × 16、单连接 Pool;多 Job 竞争由 generation fence、数据库锁和 exact replay 收敛,可按队列规模水平调度 Job 而不扩大单进程预算。
- worker:不导入 coordinator、OCI authority 或 reconciliation repository。
被否决方案
- 为 coordinator/recovery 新建 package:没有独立生命周期,只会增加单文件包。
- 由 activation publisher 顺手物化并发布 Task:合并 Kubernetes/文件发布与业务 repository authority,且难以精确恢复 response loss。
- 每次启动都重读 staging/OCI:让耐久 revision 失去意义,并把重启可用性绑定到 Registry。
- 目录 watcher、数据库 LISTEN 或常驻 reconciliation loop:增加低配设备空载 内存、fd、timer 和不可预测 wakeup;当前 caller-driven startup/Job 足够收敛。
- 扫描全部历史 install:工作量随历史无限增长;pending 必须只来自当前 active generation 且缺 receipt 的集合。
- 冲突后继续 admission:会让 scheduler 看见与 active Package 不一致的 Task generation;冲突只能人工处理并失败关闭。
验证
- runtime-core:265 pass,覆盖首次物化/发布/reconciliation、revision exact replay 不读 source、末次 generation 切换、有界 final probe、manual/retry 分类;
- local-sqlite:86 pass,覆盖 pending→receipt→empty、两代原子发布、回滚与旁路拒绝;
- local-application:14 pass,证明 Task 发布 gate 位于 Secret 和执行 lifecycle 之前, 不安全结果释放 storage 且不开放 admission;
- cluster-admin:98 pass、0 fail、1 条真实 Kubernetes 条件 skip,覆盖双恢复空队列、 package-executor readiness、进程配置和 authority subpath;
- PostgreSQL package:146 pass、0 fail、1 条真库条件 skip;
- PostgreSQL 18.4 arm64 真库:34 pass、0 fail、1 条分角色环境 skip,两代 reconciliation、pending 查询与 manager/executor 精确 ACL 通过;
- PostgreSQL 18.4 physical HA:24 个具体 gate 与总
passed全为 true,覆盖remote_apply、timeline 1→2、旧主先 fencing、pg_rewind只读重入及晋升前后 package authority readiness。
HA 门证明 schema、ACL 和组合依赖可跨 promotion 保持 ready,但尚未在 Task reconciliation receipt 的 COMMIT response-loss 窗口注入故障;不能把通用 transaction 或其他领域 receipt 的证据外推到本协议。
后续
- 给本机与 Cluster 增加“真实 active Package 启动后 Task 可调度”的产品级 vertical test,并覆盖 generation 在 final probe 前切换;
- 把 reconciliation receipt 纳入 HA COMMIT-response-loss durable inspection;
- 设计 immutable Tool handler generation snapshot;
- 设计 Workflow/Prompt version repository 与 caller-driven activation;
- 建立显式 Secret binding approval ceremony;
- 在完整引用图和 retention policy 前继续禁止删除 Package ownership、receipt 和历史 revision。