Files
qinglong/docs/adr/ADR-0497-atomic-cluster-legacy-env-migration-application.md

7.3 KiB
Raw Permalink Blame History

ADR-0497Cluster Legacy Env 的原子 Task/Trigger 迁移与只追加回执

  • 状态:Accepted
  • 日期:2026-08-24
  • 决策:D-402
  • 关联:ADR-0104、ADR-0233、ADR-0259、ADR-0491、ADR-0495、ADR-0496

背景

ADR-0495 冻结了无敏感内容的 Cluster migration planADR-0496 又让 Task 与执行修订只需保存 一个固定版本的 environmentBundleRef。此前仍缺少真正提交计划的 authority:如果分别调用现有 Task 与 Trigger repository,会开启两个独立事务,出现 Task 已改而 Trigger、schedule 或 receipt 尚未提交的裂脑窗口;如果一次把 100,000 个 Task 和 500,000 个 Trigger 全部装入 JS 数组,又会 让 Automation Manager 在低内存节点上不可用。

迁移还必须处理合法的历史 pin:Trigger 可能仍固定 Task r1,而 Task 当前 head 已推进到 r2。 新 Trigger 应固定本次迁移产生的 Task r3,不能错误假设旧 Trigger pin 等于 Task current head。

决策

1. 使用可重放流与双摘要冻结输入

新增 profile-neutral application contract 与 qinglong/cluster-legacy-env-migration-application-receipt@v1。调用方提供可重放的 Task/Trigger mutation stream;每项包含 ordinal、实体身份、旧 revision/content digest 和独立 UUID mutation。 Task 与 Trigger 分别计算 source revision-set digest 和 mutation-set digest:前者必须匹配已发布 plan,后者必须匹配 application intent。

流必须从 ordinal 0 连续、按 Task/Trigger ID 严格递增,不允许重复、未知字段、非规范 UUID 或 超过 100,000/500,000 项。Task/Trigger ID 延续现有定义契约的 128-byte、无控制字符边界,不用 更窄的 ASCII 正则误伤合法历史数据。Trigger 可以为空,但 Task 至少一个。

2. 一个 Project-serialized SERIALIZABLE 事务完成全部写入

Automation Manager repository 先开启 SERIALIZABLE,取得 domain-separated Project advisory transaction lock,再验证 active Project、plan ID/digest 和 exact mutation replay。每批最多只保留 128 项,先完整处理 Task,再处理 Trigger;任何流摘要、head、ownership、spec 或 CAS 不一致都会 回滚 receipt 和全部 revision DML。

Task 阶段对每个 current head 执行 FOR UPDATE,拒绝 Plugin-owned、非 command@v1、已有 environmentBundleRef、revision/digest 漂移和数据库时间倒退;新 revision 保留 name、description、 command config、labels、enabled 与 created time,只追加 plan 中的固定 bundle ref。enabled Task 同时 生成新的 remote_worker Cluster execution revision,数据库只保存 ref,不保存 Env 名称或值。

Trigger 阶段复验 current Trigger revision、旧 Task pin 和 schedule revision,保留 spec 与 enabled 但绑定同 application 中该 Task 的新 revision/content digest。新 Trigger revision、head CAS 与 schedule reset 同事务提交;schedule 清空 due/claim 字段并递增 state/claim fence。旧 Trigger pin 可以是历史 revision,不要求等于 Task 迁移前 current head。

3. v70 提供三张只追加、无敏感内容的回执表

pg-0071-cluster-legacy-env-migration-applications 将 control contract 推进到 v70,新增:

  • cluster_legacy_env_migration_application_receiptsapplication/plan/mutation、四个 set digest、 固定 bundle ref、计数、数据库提交时间和精确 canonical receipt JSON
  • cluster_legacy_env_migration_application_tasks:每个 Task 的 before/after revision digest、mutation、 可选 execution digest 和 item digest
  • cluster_legacy_env_migration_application_triggers:每个 Trigger 的 before/after revision、旧 Task pin、 新 Task pin 和 item digest。

表中没有 Env name/value、bundle carrier、plaintext/ciphertext、key ID、Task/Trigger spec、命令或 provider path。application/plan/mutation/receipt 唯一,ordinal 与 revision 关系有 named constraints 子表通过 (application_id, project_id) 复合外键隔离 ProjectTrigger item 又通过复合外键固定到 同 application 的 Task item。三表均从 PUBLIC 撤销,只向 ql3_automation_manager 授予 SELECT, INSERT,不授予 UPDATE/DELETE/TRUNCATE。

4. Exact replay 不重新消费输入流

相同 application mutation 先读取 durable receipt,精确比较 intent,然后聚合验证 Task heads、 Task/execution revision、Trigger heads/revision 和 schedule 仍与逐项 receipt 一致。验证成功直接返回 existing,不调用 Task/Trigger stream factoryintent drift、receipt 缺项、ordinal gap 或 current head 已继续推进均失败关闭。序列化、死锁和 lock timeout 使用新 stream factory 最多重试三次。

5. 不为该能力新增微包或 Edge 常驻成本

纯契约位于既有 runtime-core 的显式 subpathPostgreSQL authority 位于既有 cluster-postgres 显式 subpath;不进入 runtime/admin/root entrypoint,不新增 workspace package、 生产依赖、daemon、controller、timer、watcher 或全集缓存。低内存 Cluster 节点只承担固定 128 项 batchEdge/Standalone 默认 import graph 不加载 PostgreSQL authority。

被拒绝的替代方案

分别调用 Task 与 Trigger repository

拒绝。两个事务无法保证 Task、Trigger、schedule 和 receipt 原子,response loss 也无法证明哪一半 已经提交。

先收集全部候选再写数据库

拒绝。100,000/500,000 上限会把路由级设备或小型管理节点变成内存压力点;可重放有序流和固定 batch 已经能在事务回滚后重新计算摘要。

要求 Trigger 旧 Task pin 等于 Task current head

拒绝。不可变 Trigger 合法固定历史 Task revision;只需复验该旧 pin,并把新 Trigger 显式重定向到 本次 application 产生的新 Task revision。

把 Task/Trigger spec 或 Env 名称写进 receipt

拒绝。revision 本身已经保存规范 specreceipt 只需要 identity、digest、count 和 fence,复制内容会 扩大 PostgreSQL backup、HA replica 和审计泄漏面。

当前验证与后续门禁

runtime-core 完整测试 591/591cluster-postgres package 测试 361 total / 358 pass / 3 conditional skip / 0 failv70 migration/schema/readiness 定向门 74/74CI 同构八角色 PostgreSQL integration 57/57。Automation Manager 对 Project 与 immutable plan 只需普通 SERIALIZABLE SELECTTask/Trigger 可变 head 与 schedule 继续持有 FOR UPDATE。真实 PostgreSQL 18.6 arm64 HA 多次通过 146 gatestimeline 1→2;最终报告 SHA-256 为 a7c8a05e08c748d677475a09ce2998b741ed9326e27678613e93d431bc3769aa

真实数据库用例证明:带空格的合法 Task/Trigger ID 可迁移;Trigger 固定 Task r1、Task current r2 时会原子生成 Task r3 与 Trigger r2 并让新 Trigger 固定 r3execution plan 只出现 bundle ref schedule fence 递增且 claim/due 清空;exact replay 不消费 streamAutomation Manager UPDATE 以及 runtime/admin SELECT 均以 42501 被拒绝。

本 ADR 关闭 D-402 的 Cluster Task/Trigger application/receipt 边界,但 ADR-0491 仍保持 Proposed。 后续仍必须完成 direct Vault/KMS/HSM custody、migration Job 的短期身份与装配、在 PostgreSQL promotion 之后对既有 application receipt 执行 exact replay,以及固定低性能 Edge 设备的真实 空间、写放大、断电与恢复证据。