Files
qinglong/docs/adr/ADR-0149-atomic-plugin-package-resource-generation-identity.md
T

6.6 KiB
Raw Blame History

ADR-0149:原子 Plugin Package Resource Generation Identity

  • 状态:Acceptedlock、activation intent、本地/Kubernetes pointer 与只读 source 已实现; ADR-0150 已补纯领域语义 materializer 与本地/OCI byte sourceADR-0151 已补 durable revision repository;执行发布仍未实现)
  • 日期:2026-07-25
  • 关联:ADR-0132 至 ADR-0143、QL-RFC-0001 D-130/D-132/D-137/D-140

背景

Plugin Package 已能从受审批 lock 经过 staging 原子切换为 active,但此前 active pointer 只说明哪个 package content/generation 已发布,没有给资源消费者一份可直接读取、 可恢复且与切换同时生效的资源集合。若后续 consumer 重新读取 Manifest、扫描 staging 目录或按文件逐项更新,会产生三类错误:

  • 重启恢复可能看到与审批时不同的目录或 Manifest;
  • upgrade 期间 Task、Workflow、Prompt、Tool 可能来自两代 package
  • 路由器需要 watcher/cache 才能维持视图,集群多副本又无法靠进程内锁形成一致事实。

另外,Manifest 路径曾允许 1024 字节,而 canonical bundle entry 只允许 255 字节, 导致一份 Manifest 可能在计划阶段通过、到归档阶段才失败。

决策

1. lock 保存受审批的资源引用快照

PluginPackageLock 升级为 qinglong/plugin-package-lock@v2。构造 lock 时从已经 规范化的 Manifest 生成最多 256 条 {kind, path}

  • kind 只允许 prompt | task | tool | workflow
  • path 必须位于对应的 prompts/ | tasks/ | tools/ | workflows/ 下;
  • path 全局唯一、按 kind/path 排序,UTF-8 最大 255 字节;
  • 快照同时进入 Approved Action payload 与 lock digest。

durable recovery 因而只需要既有 lock,不需要重新取得 Manifest、打开 bundle 或扫描 目录。旧 @v1 lock 缺少该事实,必须失败关闭;3.0 alpha 不做静默补值。

Manifest content path 上限同步前移为 255 字节,与 canonical bundle 一致,让错误在 提案和计划之前出现。

2. generation 是完整、受限且可验证的值

新增 runtime-core 显式 subpath @qinglong/runtime-core/plugin-package-resource-generation,不新增 workspace package。 qinglong/plugin-package-resource-generation@v1 固定绑定:

  • installation、Project、Package、lock digest
  • generation、exact previous active lock
  • content tree digest
  • lock 中的完整有序资源引用;
  • domain-separated generationDigest

它只包含低敏元数据,不包含资源字节、解析结果、handler、Secret 或执行 authority。 contentDigest 继续绑定 bundle 中的路径、字节和 entry digest;后续 materializer 仍必须通过 staging evidence 读取并验证实际字节,不能只信任路径。

3. activation 与 pointer 同时切换 generation

activation intent 升级为 qinglong/plugin-package-activation-intent@v2,完整携带 resource generation,且 intent digest 绑定 generationDigest。normalizer 必须逐项复验 generation 与 intent 的身份、lock、代数、previous lock 和 content digest。

本地 pointer 与 Kubernetes pointer 分别升级为:

  • qinglong/plugin-package-active-pointer@v2
  • qinglong/plugin-package-kubernetes-active-pointer@v2

因此 POSIX rename 或 ConfigMap resourceVersion CAS 赢得切换时,package active fact 和资源集合在同一个 pointer 中同时生效。旧 pointer 精确形状不兼容并失败关闭。

最多 256 条、每条最多 255 字节的路径会使原 32 KiB pointer 上限不充分。上限提升为 512 KiB,仍低于 Kubernetes ConfigMap 1 MiB 限制;normalizer 的条数和路径上限是首要 容量边界,publisher 不接受更宽对象。

4. consumer 只读 active generation,不扫描目录

新增 PluginPackageResourceGenerationSource

findActiveResourceGeneration(
  projectId: string,
  packageName: string,
): Promise<PluginPackageResourceGeneration | null>

本地 publisher 以一次私有 pointer 文件读取实现;Kubernetes publisher 以一次 ConfigMap GET 实现。读取不重新验证全部 blob、不创建 cache、timer、watcher、socket 或数据库连接。后续语义 materializer 必须以 source 返回的 generation 作为唯一输入 身份,并在解析受信 staging 字节后一次性发布自己的不可变 revision。

本 ADR 不宣称 Task/Workflow/Prompt/Tool schema、交叉引用、冲突策略、Trigger/Tool Registry 接入或执行装配已经完成。

Profile 影响

  • edge/standalone:只有显式安装或资源解析请求才读取一个小型 pointer;没有常驻 generation 进程,空资源集合仍是固定小对象。
  • cluster:所有副本读取同一个 ConfigMap CAS 赢家;无 informer/watch 依赖,consumer 可在自身请求边界决定是否缓存,但缓存不能成为权威事实。
  • 两种 Profile 共享同一纯领域 generation normalizer,不复制语义。

被否决方案

  1. 新增 resource-generation workspace package:没有独立部署或依赖生命周期, 会继续把 packages/ 拆成单文件包。
  2. 激活后扫描 staging 目录:目录不是审批事实,也不能保证跨代原子性。
  3. 每个资源独立写数据库/ConfigMap:会制造部分发布、回滚顺序和大量小对象。
  4. pointer 只保存 generation digestconsumer 无法在不回读 Manifest 的情况下 得到精确资源集合。
  5. 在 publisher 内解析 YAML/JSON 并注册资源:把字节发布 authority 与业务语义、 Tool/Task authority 合并,且增加路由器峰值资源和集群失败窗口。

验证

  • runtime-coregeneration canonicalization、两种构造路径、摘要/顺序/路径/重复/256 条上限、lock v2、intent v2 与恢复链路;
  • local-admin:无 pointer 返回 null、POSIX 原子发布后读取 exact generation、upgrade 返回新代、旧代/stage/pointer 漂移失败关闭;
  • cluster-admin:无 ConfigMap 返回 null、create/replace/并发/response-loss 后读取 CAS 赢家、metadata/data 漂移失败关闭;
  • package boundary:使用 runtime-core 新 subpath,不新增 workspace importer、第三方 依赖或常驻 authority。

后续

ADR-0150 已实现显式、受限的 semantic materializer:四类 JSON 文件格式、Package 内引用/权限校验、不可变 revision、POSIX staging reader 与 OCI streaming reader。 下一阶段要为 revision 增加 SQLite/PostgreSQL create/exact-replay repository,并以 active generation 为唯一选择事实接入 TaskDefinition/Tool Registry;仍不得重新发明 目录扫描或第二套 active pointer。