Files
qinglong/docs/adr/ADR-0153-bounded-production-plugin-package-task-publication.md
T

9.0 KiB
Raw Blame History

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-sqlitecluster-postgres/package-executor;本机组合进入 local-applicationCluster 组合进入 cluster-admin 已有的一次性 recovery process。

该能力没有独立版本、部署或供应链生命周期,因此不得拆出只有少量文件的新 importer。 本切片不新增第三方依赖、schema、连接池、timer、watcher、socket 或常驻缓存; packages/ 仍为 21 个 importer。

2. 每次发布都以 active generation 为围栏

PluginPackageTaskPublicationCoordinator.publishActive(Project, Package) 固定执行:

  1. 读取当前 active resource generation;不存在则返回 absent
  2. generationDigest 查 immutable materialized revision
  3. revision 已存在时直接复用,禁止再次读取 staging/OCI
  4. revision 不存在时,以当前 generation、完整 lock 和受限 byte source 执行一次 materialization,再 create/exact-replay 发布 revision
  5. 调用 generation-bound Task reconciliation repository
  6. 再次读取 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-requiredsafeToAdmit=false

4. 本机 application 在 Secret 与执行栈之前失败关闭

edge/standalone 启动顺序固定为:

  1. storage ready
  2. 既有 Plugin Package install/activation recovery
  3. Plugin Package Task publication recovery
  4. Secret keyring、Run recovery、scheduler/execution lifecycle
  5. 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 中:

  1. 通过 package-executor schema/role readiness
  2. 完成 install/activation recovery
  3. 复用同一个 Kubernetes generation publisher
  4. 复用同一个 allowlisted、签名校验的 OCI authority,并只在 revision 缺失时创建 caller-owned resource byte reader
  5. 使用 executor-only materialized/reconciliation repository 完成 Task 发布;
  6. 关闭数据库和短生命周期 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。

被否决方案

  1. 为 coordinator/recovery 新建 package:没有独立生命周期,只会增加单文件包。
  2. 由 activation publisher 顺手物化并发布 Task:合并 Kubernetes/文件发布与业务 repository authority,且难以精确恢复 response loss。
  3. 每次启动都重读 staging/OCI:让耐久 revision 失去意义,并把重启可用性绑定到 Registry。
  4. 目录 watcher、数据库 LISTEN 或常驻 reconciliation loop:增加低配设备空载 内存、fd、timer 和不可预测 wakeup;当前 caller-driven startup/Job 足够收敛。
  5. 扫描全部历史 install:工作量随历史无限增长;pending 必须只来自当前 active generation 且缺 receipt 的集合。
  6. 冲突后继续 admission:会让 scheduler 看见与 active Package 不一致的 Task generation;冲突只能人工处理并失败关闭。

验证

  • runtime-core265 pass,覆盖首次物化/发布/reconciliation、revision exact replay 不读 source、末次 generation 切换、有界 final probe、manual/retry 分类;
  • local-sqlite86 pass,覆盖 pending→receipt→empty、两代原子发布、回滚与旁路拒绝;
  • local-application14 pass,证明 Task 发布 gate 位于 Secret 和执行 lifecycle 之前, 不安全结果释放 storage 且不开放 admission
  • cluster-admin98 pass、0 fail、1 条真实 Kubernetes 条件 skip,覆盖双恢复空队列、 package-executor readiness、进程配置和 authority subpath
  • PostgreSQL package146 pass、0 fail、1 条真库条件 skip
  • PostgreSQL 18.4 arm64 真库:34 pass、0 fail、1 条分角色环境 skip,两代 reconciliation、pending 查询与 manager/executor 精确 ACL 通过;
  • PostgreSQL 18.4 physical HA24 个具体 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 的证据外推到本协议。

后续

  1. 给本机与 Cluster 增加“真实 active Package 启动后 Task 可调度”的产品级 vertical test,并覆盖 generation 在 final probe 前切换;
  2. 把 reconciliation receipt 纳入 HA COMMIT-response-loss durable inspection
  3. 设计 immutable Tool handler generation snapshot
  4. 设计 Workflow/Prompt version repository 与 caller-driven activation
  5. 建立显式 Secret binding approval ceremony
  6. 在完整引用图和 retention policy 前继续禁止删除 Package ownership、receipt 和历史 revision。