mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
132 lines
6.6 KiB
Markdown
132 lines
6.6 KiB
Markdown
# ADR-0149:原子 Plugin Package Resource Generation Identity
|
||
|
||
- 状态:Accepted(lock、activation intent、本地/Kubernetes pointer 与只读 source 已实现;
|
||
ADR-0150 已补纯领域语义 materializer 与本地/OCI byte source,ADR-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-core:generation 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。
|