7.9 KiB
ADR-0190:Cluster Approved Publisher Trust Overlap Add 与 Safe Retire
- 状态:Accepted
- 日期:2026-07-28
- 关联:RFC D-175、D-177、D-178、D-179、D-180; ADR-0185、ADR-0187、ADR-0188、ADR-0189
背景
ADR-0188 只实现 compromised-key emergency revoke;ADR-0189 已保证挂载文件是 material、 durable head 才是 effective authority。正常 key rotation 仍缺少两种受批变更:
- 在不删除旧 key 的前提下让一个预分发的新 key 生效;
- 在所有当前安装均不再依赖旧 key 后安全退役旧 key。
若把 ConfigMap 更新直接当作生效,会绕过 Project Policy/Approval;若 retirement 只在 executor 启动时检查一次 head,并发 stage 仍可能在退役扫描后提交旧 signer provenance。
决策
1. 两种 action,不复用 emergency revoke
新增两个 action type:
plugin_package.publisher_key.overlap_addplugin_package.publisher_key.safe_retire
它们都要求 trust-authority Project 的 package.manage,固定创建
separation_of_duty Approval;请求人与确认人必须是不同的强认证 User。正常轮换不提供
break-glass;suspected/confirmed compromise 必须使用 D-178 emergency revoke。
客户端只提交:
actionRef、approval/audit identity;mode;publisher、keyId;- strong authenticated principal。
客户端不得提交 PEM、candidate snapshot、previous/current digest、generation、影响集或 retirement proof。
2. overlap-add 从只读 material 派生
管理进程启动时读取并严格验证 publisher material 文件。首次 observation 创建 base/head; 后续不同 snapshot 只以 digest-only candidate 写入 snapshot ledger,并返回当前 effective head,不能隐式推进 generation。
提案时,服务用进程内已审 material snapshot 与数据库 effective snapshot计算差异:
- 所有 effective key 必须逐字段保留;
- 必须只新增请求指定的一个
(publisher,keyId); - 新 key 在 proposal time 必须处于 lifetime;
- 禁止 removal、rewrite、批量 addition 或跨 publisher 附带变更。
proposal 绑定 candidate digest;executor 执行时再次从 durable snapshot ledger 解析并 重验同一 one-key addition。
3. safe-retire 从 durable effective set 派生
safe-retire 不要求 operator 先删除文件中的旧 PEM。服务从当前 effective snapshot 精确删除 请求 key,要求:
- 目标 key 当前存在;
- 同一 publisher 至少保留一个在 proposal time 当前有效的 successor;
- 除目标删除外没有任何 key 或 lifetime 变化。
候选 digest 由服务派生并写入 snapshot ledger。head 生效后,D-179 verifier 会排除旧 key; operator 可在确认所有 recovery Job 已观察新 generation 后再清理旧 PEM。
4. v39 schema contract / pg-0040 durable proposal 与 receipt
control-core v39 由 pg-0040-plugin-package-publisher-trust-transitions
新增:
plugin_package_publisher_trust_transition_proposals:不可变绑定 mode、Project/Policy fence、proposer、generation、目标 key、previous/candidate digest、action/preview digest;plugin_package_publisher_trust_transition_receipts:绑定 approved dispatch、 proposal、previous/current generation/digest、执行 User/fence、retirement proof summary 和数据库时间。
proposal/approval/dispatch/receipt 必须 exact replay。manager 只能插入 candidate/proposal/ approval/audit;只有 package-executor 可推进 head 和写 receipt。
5. retirement 与 stage 使用同一 signer fence
所有 (publisher,keyId) mutation 继续使用 ADR-0187/0188 的同一 domain-separated
advisory transaction lock。
safe-retire executor 在 SERIALIZABLE transaction 内:
- 锁 signer;
FOR UPDATE精确 authority head 并重验 generation/digest;- 扫描该 signer 对应的 current
staged|activating|activeinstall head; - 要求 matching count 为零;
- 写 zero-impact retirement proof receipt;
- 推进 effective head generation;
- 原子提交 head 与 receipt,二者共同构成唯一业务提交点。
通用 Approved Action dispatcher 在业务事务返回后,将 execution 投影为 succeeded。该投影
不参与 trust authority 判定,也不能单独推进 generation。若进程在业务 COMMIT 成功后、
execution 完成前崩溃,恢复执行必须以同一 dispatch、proposal、execution start time
重新调用 transition repository;repository 只允许返回完全相同的 receipt/head,
dispatcher 再收敛 execution。不得为了消除该投影窗口,把通用 dispatcher 的 execution
协议或其他 handler 的事务所有权耦合进 publisher trust repository。
provenance stage commit 在 signer lock 后必须按安装显式绑定的 trust authority 查询当前 effective snapshot,并要求 signer 仍存在。这样顺序只有两种:
- stage 先获锁并提交,retire 随后看到 impact > 0 而拒绝;
- retire 先获锁并提交,stage 随后看到 key 已不在 effective set 而拒绝。
不得仅依赖 recovery 启动时缓存的 registry。
6. authority ID 必须显式贯穿 stage
Package recovery 配置已经具有 QL3_PLUGIN_PACKAGE_TRUST_AUTHORITY_ID。D-180 将该 ID
传入 provenance repository/commit command;repository 不允许“任一 authority 包含该 key”
或隐式选择第一条 head。若未来需要一个集群多个 trust domain,必须在 Package proposal/
lock 中增加受批 authority binding,不能靠数据库枚举猜测。
7. 资源与包边界
复用现有:
- runtime-core trust/action contract;
- cluster-postgres snapshot/head、Approved Action、provenance repository 与 migration stream;
- cluster-admin management transport/process 和 caller-driven executor。
不新增 workspace package、生产依赖、listener、timer、Service 或常驻 controller。现有 executor 的最多 2 个连接、批预算和 opt-in CronJob 不变;低配节点可继续外部调用一次性 CLI。
不采用方案
ConfigMap rollout 自动推进 head
Kubernetes write authority 不能替代 Project Policy、双人 Approval 和 durable exact replay。
safe-retire 复用 emergency revoke
正常零影响 retirement 不应制造 compromise reason、revocation receipt、Run/Tool emergency deny 与 quarantine 语义。
retirement 只检查当前 catalog 或只读快照
catalog 不是 Cluster 当前 install-head authority,事务外只读也无法与并发 stage 串行化。
查询任意 authority 是否包含 signer
会在多 authority 时错误放宽 signer,属于 confused-deputy。
新拆 trust-transition package
没有新的 Profile、credential、制品或第三方依赖边界,违反 ADR-0185。
验收证据
- 纯 contract 覆盖 exact one-key add/remove、same-publisher successor、lifetime 和客户端 digest injection 拒绝;
- PostgreSQL mock 与真实 PG 覆盖 candidate observation、proposal exact replay、 manager/executor ACL、receipt/head atomicity,以及业务 COMMIT 后 execution 投影丢失的 exact replay;
- 并发真库门确定性覆盖
stage wins → retire blocked与retire wins → stage blocked; - management transport 只暴露 mode/publisher/key ID;
- executor 同时消费 revoke 与 transition,保持 caller-driven bounded work;
- 本机 arm64 PostgreSQL 18.4 真库集成覆盖 generation
1→2→3、2 条 receipt、 safe-retire exact replay,以及共享 advisory lock 下 stage/retire 双向单赢家; qinglong/postgresql-ha-contract@v1使用 physical streaming、remote_apply、 timeline1→2、旧主 fencing、promotion、pg_rewind与只读同步 rejoin,证明 old+new material 预分发、双人审批、2 proposals、2 receipts、2 succeeded executions 和 generation1→2→3在 WAL replay 与 promotion 后持续;- workspace 保持 22 包;cluster dependency、cluster deployment、CloudNativePG deployment 与 edge import audit 均无 finding。