Files
qinglong/docs/adr/ADR-0139-kubernetes-plugin-package-active-pointer-cas.md
T

158 lines
7.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-0139Kubernetes Plugin Package Active Pointer CAS
- 状态:AcceptedConfigMap exact pointer、stage evidence verifier、
resourceVersion CAS、并发/读取窗口/响应丢失恢复与 subpath 门禁已实现;ADR-0140
已补 durable startup recovery、标准 OCI verifier、一次性 admin process、独立
image 与最小权限 Job/RBAC;真实集群专项门仍待完成)
- 日期:2026-07-24
- 关联 RFCQL-RFC-0001 D-08、D-09、D-132、D-135 至 D-137
- 关联 ADRADR-0087、ADR-0129、ADR-0134、ADR-0138
## 背景
ADR-0138 已冻结完整 activation intent、fresh publish 与 recovery inspect 的区别,并
实现本地 POSIX pointer。PostgreSQL Repository 也已能耐久保存 Cluster installation
和 PackageLock,但没有跨 admin 副本可见的 active pointer publisher。
Cluster 不能复用本机文件锁;也不能把 active generation 只写数据库后假定 Kubernetes
资源已发布。另一方面,为一个 ConfigMap adapter 新增 workspace package会继续加剧
packages 过细,而 `@qinglong/cluster-admin` 已拥有受审 Kubernetes client 依赖和
短生命周期 mutation authority。
## 决策
### 1. Publisher 留在既有 cluster-admin
新增显式
`@qinglong/cluster-admin/plugin-package-kubernetes-activation` subpath,不从 root、
administration 或 cluster-control production 入口导出。不新增 workspace package、
第三方依赖、timer、watcher、cache、sidecar 或数据库 connection。
runtime-core 新增共享 `normalizePluginPackageActivationIntent`,所有 Cluster pointer
在读写前都使用 exact schema、identifier、digest 和 generation 上限;publisher 不复制
第二套 activation intent 语义。
### 2. 一个目标对应一个有界 ConfigMap
目标 identity 固定绑定 cluster identity、namespace、Project 和 Package。资源名为
`ql3p-` 加 domain-separated SHA-256 的前 208 bit;完整 256-bit target digest仍写入
label 并在读取 canonical intent 后重算复验,因此名称截断不缩窄内容校验。
ConfigMap
- `apiVersion=v1``kind=ConfigMap`、mutable
- data 只有 `active.json`,上限 32 KiB
- pointer exact 保存 schema、cluster identity digest、activation intent 和 receipt
- labels exact 保存 managed-by、active schema 和完整 target digest
- annotation exact 保存 intent digest
- 禁止 binaryData、deletion timestamp、finalizer 和 owner reference。
资源名冲突或 metadata/data/pointer 漂移统一失败关闭。208-bit 名称只用于 Kubernetes
寻址,完整 identity、lock、stage 双证据和 intent digest 才是 authority。
### 3. stage evidence 由注入端口复验
`ClusterPluginPackageStageEvidenceVerifier.verify(intent)` 必须返回并精确匹配:
- lock digest
- stage reference
-领域 stage receipt digest
- adapter 外部 evidence digest
- content digest。
publisher 在每次 inspect 以及 publication 最终复核时调用 verifier。具体实现可以是
exact OCI digest、对象存储 receipt 或后续受审 Kubernetes 资源,但本 ADR 不允许
publisher 自行下载、缓存或猜测来源。verifier 不可用返回 unavailable,事实漂移返回
conflict。
### 4. create/replace 使用 resourceVersion CAS
首装只在 ConfigMap 不存在且 previous lock 为 null 时 create。升级先读取 current
pointer,必须 exact 等于 previous active lock,再用该对象的 resourceVersion replace。
第一次 observe 与取得写入 resourceVersion 之间存在第二次 GET;第二次返回的实际
pointer必须再次复验 previous lock。否则另一个副本在窗口中发布新 generation 后,
stale writer 会拿赢家的新 resourceVersion 将其覆盖。
Kubernetes 409 只在重新 inspect 得到完全相同 intent 时作为 exact replay;其他 winner
均为 conflict。两个并发候选最多一个成功。
### 5. 响应丢失只由恢复 inspect 裁决
create/replace 返回非 409 错误时,publisher 不在 fresh 路径猜测是否已经提交,而是
返回 unavailable,让数据库 record 保持 `activating`。ADR-0138 coordinator 在恢复时
只调用 inspect
- exact ConfigMap pointer → 提交 durable active receipt
- pointer absent → durable activation failure
- pointer 为其他 generation 或 tamper → durable fact conflict。
成功 API 响应后仍要重新 GET pointer 并再次复验 stage evidence;不能只信 response
body 或本地 request。
### 6. 产品入口继续分阶段开放
ADR-0140 已提供:
- 标准 OCI Distribution stage evidence verifierOCI manifest digest 与 bundle
layer digest 分离,签名通过 lock-annotated OCI referrer取得;
- `ql3-plugin-package-recover` production admin binary
- 独立 cluster-admin image
- 仅有 ConfigMap `get/create/update` 的 ServiceAccount、Role/RoleBinding 和
600 秒一次性 Job manifest。
仍不提供:
- object-store stage
- Approved Action durable consumer、Policy/Audit route
- operator repair
- ADR-0149 active generation source 之上的 Task/Workflow/Prompt/Tool/Trigger
语义 materializer
- ConfigMap GC、Package byte distribution、Runtime/UI Extension。
因此 adapter 可执行不等于 Cluster 已开放插件安装。
## 拒绝的方案
- 新增 `plugin-package-kubernetes` workspace package:拒绝;一个 adapter 文件没有
独立发布或运行生命周期,显式 subpath 足以隔离。
- 只在 PostgreSQL 切 active generation:拒绝;无法证明 Kubernetes 可见事实已发布。
- 使用普通 replace 或 merge patch:拒绝;缺少 exact previous resourceVersion fence。
- 只校验第一次 GET:拒绝;第二次 GET 后可覆盖窗口中的新赢家。
- 在 publisher 内实现 OCI client:拒绝;会合并来源、缓存、stage 与 pointer authority。
- 将 ConfigMap 设为 immutable 并每 generation 新建:拒绝;会把唯一当前 authority
退化为目录/label 扫描和 GC 问题。
## 影响
- Cluster 多副本获得单一、可 inspect、resourceVersion-fenced active pointer。
- edge/standalone 不依赖 cluster-admin;共享 intent normalizer 使六种本机制品各增加
2,532 bytes,但 files 与 loaded modules 不变,最大值仍低于硬门禁。
- packages importer 保持 21cluster-admin 继续复用现有
`@kubernetes/client-node`dependency audit 无新增 dependency。
- Kubernetes client 只进入独立短生命周期 admin image;常驻 cluster-control
production closure 不变。
- ConfigMap 只保存低敏摘要和 identity,不保存 bundle、Secret 或可执行内容。
## 验证
当前门禁覆盖:
1. canonical ConfigMap create、inspect 和 exact replay
2. exact previous lock replacement 与 stale writer conflict
3. create response loss 后 inspect 收敛且不 republish
4. 同一 resourceVersion 的两个候选 single winner
5. 两次 GET 之间插入新 winner 时 stale writer 不覆盖;
6. stage evidence drift 在任何 API mutation 前失败;
7. ConfigMap pointer canonical/tamper fail-closed
8. root 不导出、显式 subpath 可达;
9. runtime-core intent normalizer 与 generation 边界;
10. cluster-admin 全量 55 pass/1 条件 skipConfigMap response loss 已通过 durable
startup recovery 在下一轮 inspect 收敛且不再次 create
11. 标准 OCI allowlist、manifest/layer/referrer/signature 与重启重新取证;
12. 独立 admin image 的真实非 root、只读 rootfs CLI smoke
13. dependency/source 与 Kustomize/RBAC audit 仍为 21 importer、`findings=[]`
真实 Kubernetes API 的 ConfigMap 专项 CAS、RBAC 正负向和多 Pod response-loss fault
仍是产品开放前的独立 Gate。