19 KiB
ADR-0124:可恢复的 Worker Credential 交付确认与重叠轮换
- 状态:Proposed(共享 contract、协调器、PostgreSQL v1/v2/v3/v4 delivery ledger、capability v15 discard tombstone、有界恢复、PostgreSQL 16/18 四角色、PostgreSQL 18 physical promotion 下 v1/v2/v3/v4 COMMIT-response-loss、受控复制链分区与旧主 rewind/rejoin、POSIX adapter 与 Kubernetes resourceVersion CAS adapter/单 API server RBAC 证据已实现;生产 operator/proxy TLS、基础设施 STONITH、真实 Pod/raw-wire 分区与管理产品入口尚未完成)
- 日期:2026-07-23
- 关联 RFC:QL-RFC-0001 D-23、D-59、D-60、D-121、D-122
- 关联 ADR:ADR-0050、ADR-0058、ADR-0060、ADR-0061、ADR-0122、ADR-0123
背景
Cluster Admin 已能在 PostgreSQL 中原子写入 Worker credential、mutation 与 security audit,并且只在新建
mutation 时返回一次 ql3w token。精确重放返回 token: null,数据库也只保存 HMAC digest。这个边界避免
服务端长期保存 bearer secret,但还不能直接成为生产部署 ceremony:
- 数据库提交成功后、token 发布到 Worker Secret 前崩溃,会留下无法从数据库恢复的有效 credential;
- 对同一 credential ID 执行
rotate会立即令旧 version 失效,发布失败会同时失去旧、新两个可用 token; - “Secret 已写入部署系统”不等于 Worker 已实际使用新 credential 完成认证;
- 401/403 后由 Worker 自助签发会绕过管理 Policy、强认证、audit 与 version fence。
因此,生产轮换必须把一次性 secret 的暂存、数据库事实、部署发布、Worker 观察确认和旧 credential 撤销组成 可恢复状态机。现有一次性签发 service 继续作为短生命周期低层 authority,但不得直接暴露为远程产品入口。
决策
1. 产品轮换使用新 credential ID 和重叠有效期
生产 issue/recovery 每次创建新的 credential ID;不得用同 ID 的新 version 原地替换正在工作的 token。 旧 credential 在以下两个事实都耐久成立前保持 active:
- deployment adapter 已确认 exact mutation/token generation 原子发布;
- Cluster ingress 已用新 credential ID/version 认证同一 Worker,并在 Session heartbeat/transition 事务中写入 credential observation acknowledgement。
确认后才能以独立 mutation 撤销旧 credential。确认超时只告警和停止收敛,不得提前撤销旧 credential, 也不得让 Worker 自动创建新 Session。新 credential 可在受限 overlap 窗口内与旧 credential 并存;同一 Worker 可以持有多个 credential ID,但每个 ID 仍只有一个当前 version。
2. Secret 必须先耐久 stage,再提交 credential
@qinglong/cluster-admin 定义 delivery port,不新增 package。生产 coordinator 的顺序固定为:
- 生成 token,并以 mutation ID、credential ID、Worker ID、token digest 和目标 generation 调用
stage;adapter 必须 no-replace、私有/加密、有界并可在进程重启后按 mutation ID 恢复; - 在 PostgreSQL 中原子 append credential、mutation 与 allowed audit;
- 调用 adapter 将 exact staged generation 原子发布到部署目标;
- 写入 delivery-published acknowledgement;
- 等待同一 Worker Session 事务写入 authenticated observation acknowledgement;
- 两类 acknowledgement 都成立后,提交旧 credential revoke mutation。
token 只能在 stage/publish adapter 的受限 secret boundary 内出现;命令 JSON、HTTP response、Session wire、 日志、metric、audit 与 PostgreSQL 行都不得包含 token。adapter 必须复制需要保留的 bytes,调用方在 stage 返回后立即清零自己的 Buffer。
3. PostgreSQL ledger 只保存低敏恢复事实
新增 append-only delivery ledger,至少绑定:
- mutation ID、new/previous credential ID、new credential version、Worker ID;
- staged token digest、deployment target digest 和 generation;
staged、credential_committed、published、observed、previous_revoked各阶段的时间与 fence;- 发布 acknowledgement、认证 observation 的主体/Session/version 摘要;
- recovery attempt 的低基数结果和 security audit event ID。
ledger 不保存 token、私钥、证书、Authorization header、Session lease token 或 deployment provider credential。阶段推进使用 expected-state/version CAS;同 mutation 的 exact replay 返回已有低敏状态,语义漂移 或同 generation 不同 digest 一律 fail closed。
4. Recovery 是显式、无 timer、可分页的短生命周期操作
恢复器由受审管理 CLI/API、部署 controller reconcile 或运维命令显式驱动,不在 Worker/Cluster control 常驻进程新增 timer。每次只处理有界页,并按事实执行:
- 只有 staged、数据库无 mutation:先提交数据库 cleanup authorization,再清除 staged secret;
- credential 已提交但未发布:从 exact staged record 重试发布;
- 已发布但未 observed:保留新旧 credential,等待 Session cadence 或发出告警;
- 已 observed 但旧 credential active:提交独立、可重放的 revoke;
- digest、target、Worker、Session 或 version 漂移:进入人工审查,不自动覆盖 Secret 或撤销旧 credential。
数据库结果不确定时不得删除 staged secret;部署发布结果不确定时必须 inspect exact generation 后再裁决, 不得盲目重写。所有清理都要求 ledger 终态和最小保留期。
4.1 全局 orphan 清理采用数据库 tombstone,不以单次 absence 作为删除证明
部署目录的有界扫描只能发现候选 stage,不能直接授予删除权限。resolveDelivered(deliveryId) = null
与一个尚未提交完成的 issuer 可以同时成立;扫描器若据此删除,会让随后成功的 credential commit 永久失去
对应 token。因此 capability v15 新增 append-only cleanup ledger,并遵守同一 deliveryId 上的
单赢家协议:
- cleanup coordinator 读取并规范化低敏 stage intent;
- PostgreSQL 事务获取
ql3-worker-credential-delivery:<deliveryId>advisory transaction lock; - 在同一事务内重新检查 mutation、delivery 与既有 cleanup 记录;只有前两者均不存在时,才插入绑定
delivery ID、Worker、credential、token digest、target digest、generation 与数据库时间的 v1
discard_authorizedtombstone; commitDelivered在插入 credential/mutation/delivery 之前获取同一把 delivery lock,并在 tombstone 已存在时 fail closed;由此数据库 commit 与 orphan cleanup 只能有一个赢家;- coordinator 只有拿到 exact committed authorization 后,才调用 adapter 的 exact
discard;删除成功或 已证明文件不存在后,再以 expected-version CAS 追加 v2discarded; - authorization 响应丢失时从 ledger 精确恢复,discard 响应丢失时用 adapter
inspect重放;任何 intent 语义漂移、数据库不可用、锁结果不确定或未知目录项都进入人工审查。
tombstone 一旦写入不得撤销或复用该 delivery ID。全局恢复分别分页读取 deployment stage inventory 与未完成 cleanup ledger,不引入常驻 timer。POSIX adapter 仍只承担受控单写者文件语义;Kubernetes 多 Pod 由独立 resourceVersion CAS adapter 提供部署侧并发协议,并必须再通过真实 API server/RBAC 故障矩阵才能成为生产证明。
5. 认证观察复用现有 Session,不增加 Worker authority
Cluster authenticator 已产生 credentialId、credentialVersion 和 authenticationId。Session heartbeat 或
transition 在验证 Worker/Session/version fence 后,将这一低敏 principal identity 与 delivery ledger 关联;
不增加新的 Worker 写路由,也不接受 Worker 自报 credential ID/version。新 token 被部署后,现有
WorkerProductionCredentialProvider 在下一次请求读取它,coordinator 在同一 Session 上恢复 heartbeat。
401/403 只暂停 Pull 并触发外部运维信号;Worker 不能调用 issue/recover/revoke。409 继续按 Session fencing 处理,不能被 credential recovery 掩盖。
被否决的替代方案
- 数据库提交后再直接写 Secret:保留不可恢复的 commit-to-publish 崩溃窗口。
- 同 credential ID 原地 rotate:交付确认前旧 token 已失效,容易把 Worker 永久锁出。
- 数据库保存可解密 token:扩大 cluster 数据库泄露面,并混合 credential authority 与 delivery authority。
- 只以 Secret provider 的写成功作为完成:无法证明 Worker 实际读取并通过新 credential 认证。
- Worker 收到 401 后自动签发:绕过管理权限、强认证、审计、审批与撤销策略。
- 为每个 Worker 增加续期 timer/watcher:放大路由设备常驻成本,并与 D-121 单 cadence 冲突。
实现 Gate
- 定义 exact delivery port、低敏 ledger contract、错误分类和 crash matrix;实现进程内 fake adapter 测试。
- 增加 PostgreSQL migration/repository,验证双连接 CAS、事务重试、commit response loss 和有界 recovery page。
- 在 Session heartbeat/transition 同事务写 authenticated observation,验证 credential/Worker/Session/version 漂移。
- 至少实现一个真实部署 adapter,并证明 no-replace、inspect-before-retry、原子发布、权限与容量上限。
- 覆盖 stage 前后、DB commit 前后、publish 前后、observation 前后和 revoke 前后的进程终止矩阵。
- 验证低配 Edge 不新增 timer/socket/数据库,Cluster Worker 只复用现有 Session cadence 与 mTLS Agent。
- 完成 Gate 前保持
Proposed,不得把低层一次性 token 返回接口暴露为常驻远程管理 API。
当前实现证据
@qinglong/runtime-core/worker-credential-delivery已定义 exactDeliveryIntent、append-onlyDeliveryRecord、credential commit/publication command、错误分类与 repository port。intent 不携带credential_committed假事实;record 强制 v1 committed → v2 published → v3 observed → v4 previous-revoked 的连续证据和时间单调性;discard contract 另以 exact stage intent 绑定 v1discard_authorized→ v2discarded,不把单次文件 absence 当作删除权限。@qinglong/cluster-admin/worker-credential-delivery已实现无 timer 的 stage-before-commit coordinator: 复用既有强 Principal、HMAC、credential mutation/audit service,禁止构造一次性返回 token;token Buffer 在 adapter 返回后清零。覆盖正常发布、publish failure、publication ledger 响应丢失、孤儿 stage 清理、 raw mutation/missing stage/语义漂移 fail-closed 与同 ID rotation 拒绝。pg-0015-worker-credential-delivery-ledger将control-core推进到 capability v14,新增一张 append-onlyworker_credential_deliveries表;admin 与 worker-ingress 仅获 select/insert,runtime 零权限。Drizzle、 schema contract、冻结 checksum、readiness 和四角色权限保持一致。PostgresWorkerCredentialAdministrationRepository.commitDelivered()在同一 SERIALIZABLE transaction 中 写 audit、credential、mutation 与 delivery v1;markPublished()只 append v2。读取会验证完整 v1→vN 连续历史、不可变字段和累计证据,覆盖 COMMIT response loss、publication replay、gap 与历史改写。- Worker Ingress pipeline 只把 authenticator 产生的 Worker/credential identity 交给
AuthenticatedWorkerSessionRepository,不接受 Worker body 自报 delivery 或 credential。PostgreSQL heartbeat/transition 在更新 Session 的同一 transaction 中读取并验证 append-only credential delivery 历史:v2 published 才 append v3 observed;v1 未发布会回滚 Session 更新,v3/v4 精确重放不重复写,非 delivery legacy credential 保持兼容。按 credential lookup 的 partial unique/indexed path 已纳入同一 reviewed migration。 listRecoveryPage()使用 PostgreSQL statement observation、delivery ID keyset cursor、64 条硬上限, 只返回 v1 committed、v2 published 与携带 previous credential 的 v3 observed;v4 和无需撤销的 v3 不再 占用恢复页。WorkerCredentialDeliveryRecoveryService是显式、无 timer 的短生命周期协调器:v1 复核 exact stage 后发布并 append v2,v2 等待认证观察,v3 用 delivery ID 派生的 domain-separated mutation 原子提交旧 credential v2 revoke、audit、mutation 与 delivery v4。所有时间取自恢复页的 PostgreSQL observation,不使用管理节点本地时钟;响应丢失通过确定性 mutation 与终态移出恢复页收敛。- PostgreSQL 16 临时真库已用独立最小权限
ql3_admin与ql3_worker_ingressPool 贯通 v1 commit、v2 publication、Session heartbeat 同事务 v3 observation、恢复页与 v4 revoke,并在 v4 的 COMMIT 已成功但响应 丢失后精确收敛;21 项 integration 中 20 pass,只有未配置独立 runtime URL 的既有测试 skip。该测试同时 揭示并修复了 worker-ingress 对 append-only delivery ledger 使用FOR UPDATE、与既定SELECT+INSERT权限冲突的问题;现由既有 Worker Session 行锁串行化 observation,不扩大 ingress 权限。 @qinglong/cluster-admin/worker-credential-file-delivery在既有 package 内提供首个真实 POSIX adapter,未新增 package、依赖、timer、socket 或数据库。adapter 绑定 stage/target 目录 owner、mode、device 与 inode;stage 采用单一 bounded 私有文件、O_NOFOLLOW、fsync 与 hard-link no-replace,target 使用 previous credential ID fence、durable operation lock、0600临时文件、rename 原子替换、目录 fsync 与发布后重读。publication digest 绑定 delivery、credential、generation、target 与 token digest,响应丢失可由目标文件精确重放。stage root 最多 128 项,keyset page 最多 64 项且只返回低敏 intent;不确定临时文件、未知条目、权限/目录身份漂移、 非预期目标代际与锁竞争均 fail closed。该 adapter 已与 stage-before-commit issuer 贯通,Worker token 文件 与现有请求时惰性 credential provider 格式兼容。pg-0016-worker-credential-stage-discard-ledger将control-core推进到 capability v15,新增无 delivery FK 的 append-onlyworker_credential_stage_discards:admin 只有SELECT+INSERT,runtime 与 worker-ingress 零权限。authorization 与 credential commit 对同一 delivery ID 复用 advisory transaction lock;前者在 同一 SERIALIZABLE transaction 内重查 mutation/delivery 并追加数据库计时 tombstone,后者在写入前重查 tombstone,因此只能有一个赢家。短生命周期 cleanup service 分别有界分页扫描 deployment inventory 与 未完成授权,只有 exact authorization 才 discard,随后追加 v2;响应丢失通过 ledger/inspect 收敛,语义 漂移 fail closed。PostgreSQL 16 两个独立 admin Pool 的真实竞态已证明 commit/discard 恰有一个赢家,并 验证赢家重放和永久 fence。@qinglong/cluster-admin/worker-credential-kubernetes-delivery在既有 package 内实现多写者部署 adapter; ADR-0239 又把 target digest v2 扩展为绑定 cluster identity、namespace、Secret name/data key 与 exact Recreate Deployment。每个 delivery 使用 deterministic name 的 immutable Secret;目标 Secret create/replace 必须回传 GET 的 opaqueresourceVersion,随后 Deployment 也以独立 GETresourceVersion推进 PodTemplate。 409 只在 重读结果与 delivery/generation/token digest 完全一致时视为响应丢失重放,否则 conflict;discard 使用 UID + resourceVersion delete precondition,并在 404 后重读 absence。inventory 单次最多读取 129 条、超过 128 或出现 Kubernetes continue token 即 fail closed,再转换为领域 delivery ID keyset/64 条页;无 watcher、 timer、cache 或新 package。fake API 双 rotation 竞态先证明同一 resourceVersion 只有一个赢家;随后固定rancher/k3s:v1.34.3-k3s1(arm64,digestsha256:71abd3a56f57884c62732e0e0d87606052cb5f8555b7db7e8e33c04570b8175c) 的真实 API server 使用 10 分钟专用 ServiceAccount 重跑通过。RBAC 仅允许 Secret get/list/create/update/delete, 明确拒绝 watch/patch 与 ConfigMap get;官方 client 实际完成双 rotation 单赢家与带 UID/resourceVersion 的 orphan delete。HA control-plane failover、list compaction 与多 namespace 隔离仍待验证。client 精确固定 1.4.0,其历史依赖通过根 override 收敛到form-data@4.0.6与js-yaml@4.3.0,QL3 production vulnerability audit 为零 high/critical。- 当前验证:runtime-core 161/161、Cluster Admin 默认 37 tests 中 36 pass/1 real-API skip,且上述 real-API integration 显式启用后 1/1 pass;ADR-0239 后 fake adapter 定向 8/8,并在真实 K3s CoreV1/AppsV1 API 完成双 Secret/Deployment CAS 与 Recreate/PVC Gate。Cluster PostgreSQL 105 tests 中 104 pass/1 external skip、Cluster Control 131 tests 中 129 pass/2 external skip;全量 23-package build/test、cluster dependency、 Edge import 与六种 Profile artifact audit 均通过。当前 workspace 为 21 importer;最大常驻 application 为 2502983 bytes、413 files、 72 loaded modules、12435456 bytes 单次 RSS delta,仍低于 4 MiB/512 files/16 MiB 门禁。本机 arm64 PostgreSQL 18 四角色 integration 23/23、cluster-control 6/6 已通过,并修正 CI 中随 v15 漂移的 admin/worker-ingress 最小 GRANT;前者补充 active query 连接失效的精确 availability 分类,后者以真实 idle backend terminate 验证 active admission 摘流;真实多 Pod credential/Session replacement 并发矩阵、stale lock/temp 显式修复、Kubernetes HA control-plane/failover、管理产品入口和固定物理设备证据仍是 Gate;不能把单 k3s API server 的 resourceVersion 竞态或 PostgreSQL 单赢家结论扩大为生产多控制面发布证明。
pnpm test:postgres-ha:ql3已在postgres:18(18.4、arm64)两个独立数据卷和物理 replication slot 上完成 credential delivery 的 v1 credential commit、v2 publication ledger、v3 authenticated Session observation 与 v4 previous credential revoke 四个独立 COMMIT-response-loss 窗口。每次都在 driver 确认COMMIT后终止该 transaction backend,使调用方看到 unavailable/ECONNRESET,并在 promotion 前从 standby 依次复验[1]、[1,2]、[1,2,3]、[1,2,3,4]。timeline 1→2 promotion 后仍只有 4 条连续 ledger、3 条 credential history、3 条 mutation/audit;stage、publish 与 entropy 各只发生 1 次,旧 credential 精确为active → revoked,恢复页为空,PostgreSQL 相关领域行不含ql3wtoken。 该故障注入位于 PostgresClient 边界,不代表 raw-wire packet-loss、网络分区或生产 Secret provider 的响应丢失证明。