feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,157 @@
# 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。