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

132 lines
6.6 KiB
Markdown
Raw 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-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`
```ts
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 digest**consumer 无法在不回读 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。