Files
qinglong/docs/adr/ADR-0398-pre-activation-plugin-package-candidate-qualification.md
T

106 lines
9.2 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-0398Plugin Package 激活前候选资格校验与自动保留旧版本
- 状态:Proposed
- 日期:2026-08-14
- 关联 RFCQL-RFC-0001 D-306B2
- 关联 ADRADR-0153、ADR-0394、ADR-0396
## 问题
现有 Local 与 Cluster 启动恢复先发布 active pointer、把安装记录推进为
`active`,随后才读取 staged bytes、物化 Package Task/Workflow/Prompt/Tool 资源。
因此一个摘要正确但资源语义无效的升级可能先替换健康版本,再在任务发布阶段失败,
迫使整个启动门失败。安装状态机虽然保留 `previousActiveLockDigest`,却没有在指针切换前
使用该事实形成真正的失败隔离。
“先切换、失败后再把指针写回去”也不安全:回写会与并发发布竞争,Kubernetes
ConfigMap 和数据库 head 之间会出现第二次分布式提交窗口,并且历史 generation 可能被
静默重新激活。自动恢复应避免制造需要补偿的外部事实,而不是依赖补偿事务。
## 决策
1. 已存在旧 active 的 `upgrade|reinstall|rollback``staged → activating` 之前必须依次通过所有前置条件。Secret binding/transition
receipt 先完成;随后从 staged install、immutable lock 和 content-addressed bytes
构建目标 resource generation,读取有硬上限的 Manifest/资源并执行完整语义物化。
generation 1 没有可回退的旧指针,并且 Secret-aware 首次安装仍需 ADR-0395 的
post-activation B1 binding ceremony,因此不进入本 ADR 的候选物化门。
2. 候选 materialized revision 以既有 `generationDigest` repository key 在激活前发布。
相同 revision exact replay 返回 existing;不同事实冲突。该 revision 尚不构成 active
Task reconciliation、Automation publication 与 Tool snapshot 仍只消费 active generation。
3. 确定性的候选语义错误或 durable revision 冲突把当前安装从 `staged` 原子推进为
`failed(reason=activation_fact_conflict)`。状态机必须保留
`activeLockDigest=previousActiveLockDigest`,并且 activation publisher 调用次数为零。
4. OCI、文件、SQLite/PostgreSQL 或 reader close 的瞬时不可用不写失败事实,安装保持
`staged` 并由既有有界 recovery 重试。不得在不可区分时把可用性故障伪装成坏包。
5. active pointer 发布成功后,既有 Task publication recovery 复用预先持久化的 revision
只执行 generation-fenced reconciliation;它继续承担响应丢失与并发 superseded 检查。
6. Local 与 Cluster 必须使用同一个 runtime-core prerequisite sequence 和候选物化实现。
Local 复用单 SQLite authority 与本地 staging readerCluster 复用 caller-driven recovery
Job、单 PostgreSQL Pool、OCI reader 与 Kubernetes CAS publisher。
7. 本决策不增加 workspace package、migration、表、第三方依赖、daemon、timer、watcher、
listener、连接池或常驻 cache。Edge/Standalone 只在已有启动恢复遇到 staged install 时
按需读取候选字节;没有待恢复安装时只创建少量短生命周期对象,不增加后台 cadence。
## 接受条件
- 共享测试证明有效候选在激活前发布且 exact replay 不重复写;语义无效候选不发布 revision。
- 升级恢复测试证明 rejected 候选进入 failed、旧 active lock 保留且 publisher 未调用。
- Local 与 Cluster 组合测试证明恢复顺序一致,既有 active publication/reconciliation 不回归。
- 完整 18-package、backend、package/dependency/deployment/edge/import 审计通过。
- 真实 PostgreSQL/Kubernetes 门证明失败升级没有移动 active ConfigMap/headphysical HA 门通过。
- 固定物理低配设备必须运行真实 SQLite 失败升级 workload,证明旧 active 保留、候选
revision 零写入、publisher 零调用、数据库完整性及耗时/RSS/数据盘增长上限;容器
cgroup 结果只能作为前置 stress,不能用开发机或虚拟化观测替代。
## 影响与替代方案
- 失败候选可能留下一个不可达、不可变的 materialized revision。它按 generation 有界,保留
失败取证事实;物理清理由独立 retention/GC receipt 决定,不在失败路径同步删除。
- 不把 materialization 塞入 Kubernetes publisher。Publisher 只拥有 pointer CAS 与投影
evidence;让它读取 OCI/PostgreSQL 会聚合执行和发布 authority。
- 不新增 `rolling_back` 状态。指针从未移动时,健康旧版本本来就仍是 active;新增补偿状态
只会扩大恢复矩阵并让低配设备承担无收益的持久化协议。
## 当前验证
- Runtime Core 定向 21/21 通过,覆盖前置条件顺序、候选预物化、exact replay、无效语义拒绝、generation 1 B1 兼容和升级失败保留
`activeLockDigest`;拒绝路径的 activation publisher 调用次数为零。
- PostgreSQL/OCI/Kubernetes 现场门已升级为 `qinglong/plugin-package-recovery-e2e-live-contract@v2`:先用真实 signed OCI package
激活 generation 1,再创建包含合法 Task 与循环 Workflow 的 generation 2;第一次 recovery 必须因 transition receipt 缺失而以
`ClusterPluginPackageRecoveryRequiredError` 失败并留下 `staged`,提交 content-free transition receipt 后,第二次 recovery 必须把升级写为
`failed(activation_fact_conflict)`,且 generation 2 materialized revision 数量仍为 0。现场门逐字比较 active ConfigMap 的 UID、
`resourceVersion` 与完整 `active.json`,因此不能用“错误切换后再补偿回来”冒充旧版本未移动;OCI v1 六个路径各读取一次,v2 六个路径
各读取两次,全部要求 HTTPS、exact Basic authentication、200 且无 redirect。最终 runtime rollout 仍只绑定最后一个成功 recovery Job
recovery ServiceAccount 继续只有 ConfigMap `get|create|update`,runtime 角色仍不能读取安装 authority。
- v2 现场门现在强制接收 canonical absolute `--report`,以 `0600` 临时文件、`fsync` 与 no-replace hard link 原子发布
owner-private 报告;目标已存在、父目录为 symlink、缺少显式 opt-in 或缺少 40-hex `QL3_SOURCE_REVISION` 时,均在访问
Docker/Kind 前失败关闭。admin/control 镜像的 OCI revision label 必须与报告源码 revision 相同;报告只保存 active JSON 的
SHA-256,不保存原始 pointer、Registry credential、数据库 DSN、kubeconfig、证书或 Secret material。独立离线审计对 envelope、
镜像 provenance、六段单调 ordering、数据库精确计数、OCI 18 次认证请求、ConfigMap-only RBAC、双节点 runtime 绑定、全部
11 个 gate 与三项 limitation 做 exact-shape 校验,并以 `O_NOFOLLOW`/inode/mode/size 复验私有报告。CI 在独立 job 内先审计,
再上传固定 14 天的 source-bound evidence artifact;这一证据链只属于验收,不新增产品 package、依赖或运行时常驻开销。
- 18-package clean build/test 在允许 loopback TLS 的环境退出 0backend 1205 项为
1203 pass/2 条件 skip/0 fail。新增/更新的 recovery E2E producer/离线审计契约 14/14Runtime Core 定向 21/21。package boundary 保持 18 个 package 且
`singleSourcePackages=[]``shallowSourcePackages=[]`cluster dependency、cluster deployment
与 edge import 审计均无 finding。
- PostgreSQL `18.4` arm64 physical HA 通过 125 项门,timeline `1→2`,报告 SHA-256
`8560469694c67776e5e4c70977f8bde8d4f5635f8e7d1c293ef449dc6da59f72`,临时 Docker
资源已清理。本机已成功构建现场门所需 admin/control 镜像,但固定 `kindest/node:v1.32.8` 不在本地缓存,受限网络拉取数分钟无进度;
门在创建任何 Kind 节点前被中止,并确认没有遗留集群或容器。因此 v2 门、私有报告与离线审计代码已完成,但仍不能计为真实 Kubernetes
现场通过;远端 CI 成功记录与固定物理低配设备证据仍待完成,本 ADR 保持 Proposed。
- 固定低配资格门的产品工作负载已落地:`evidence:plugin-package-recovery-edge`
使用 fresh production migration SQLite、正式 install/materialized repositories、正式
recovery coordinator 与正式语义物化门,实测无效 generation 2 从 staged 进入 failed
旧 active generation 保留、publisher 0 次、候选 materialized revision 0 行、数据库
`integrity_check=ok`。该工作负载已接入 128/256 MiB Linux 资源门和统一 physical Edge
recorder;统一聚合器会 exact-shape 拒绝缺失、改名或失败报告,并把
`plugin_package_failed_upgrade_retains_active_generation` 列为独立 collected/remaining
evidence。当前本机 arm64 执行仅是开发验证,尚未产生固定型号、无虚拟化设备上的
owner-private 总报告,也未证明受控断电,因此不能把本 ADR 转为 Accepted。
本轮真实 workload 在 Node `v24.18.0` arm64 开发机观测为 14.390 ms、RSS delta
3,014,656 bytes、SQLite logical/allocated growth 各 8,192 bytes,均低于
10,000 ms、96 MiB、4 MiB 的候选上限;这些数值只证明脚本与门禁可运行,不是
固定设备支持结论。对应 backend 新增路径、物理聚合和 Linux release 契约 34/34
package boundary、cluster dependency、cluster deployment、Edge import 与 service
bridge import 审计均通过。