Files
qinglong/docs/adr/ADR-0226-bounded-plugin-package-workflow-frontier.md

7.5 KiB
Raw Permalink Blame History

ADR-0226:有界 Plugin Package Workflow Frontier

  • 状态:Proposed
  • 日期:2026-07-30
  • 关联 RFCQL-RFC-0001 D-03、D-207、D-212
  • 关联 ADRADR-0156、ADR-0223、ADR-0224、ADR-0225

背景

ADR-0223 至 ADR-0225 已把 generation-bound Workflow DAG 原子展开为一个 runtime-owned Workflow Run、全部 StepRun、Event/mutation 与 admission receipt。 准入后的 Workflow Run 固定为 running,而 Task root StepRun 为 ready、其余 StepRun 为 pending

首次 PostgreSQL HA 重跑暴露出一个恢复边界错误:通用 Cluster Run recovery 把没有 RunAttempt 的 Workflow 聚合 Run 识别成孤儿执行。该判断对普通 Task Run 成立,但 Workflow Run 本身是编排聚合,不应直接进入普通 Task dispatcher,也不应伪造一个 RunAttempt 或终态绕过恢复。

同时,低配 Edge 不能为每个 dormant Workflow 建立 timer、watcher 或内存队列;Cluster 多副本也不能依赖进程内 frontier。

决策

1. 通用恢复器排除 Workflow 聚合

PostgreSQL 通用 recovery source 必须排除 trigger_type='plugin_package_workflow'。这不是忽略孤儿工作,而是明确 ownership:

  • 普通 Task Run/RunAttempt 继续由既有 Cluster recovery 处理;
  • Workflow 聚合 Run 只由专属 Workflow frontier/recovery 处理;
  • Workflow 的 Task Step 后续仍必须通过既有 RunAttempt/Executor fence 启动,不能把 聚合 Run 作为普通 Task 分发。

2. 纯 frontier 契约留在现有 runtime-core

新增显式 subpath @qinglong/runtime-core/plugin-package-workflow-frontier,不新增 workspace package、 第三方依赖、进程或连接。一次 resolution 只接受:

  • exact normalized immutable plan
  • exact runtime-owned plugin_package_workflow Run fence
  • 与 plan 一一对应的最多 128 个 durable StepRun
  • 单调的存储 observation time。

契约对完整 StepRun 集合、Run/Project/Workflow/publication identity、Task definition ref/digest 和 record digest 失败关闭。

3. 一次纯计算收敛依赖 frontier

resolution 使用确定性规则:

  1. pending Step 的所有 needs 都为 succeeded 时生成 pending → ready mutation
  2. 任一祖先为 failed|skipped|cancelled|timed_out 时,依赖失败可在同一 pass 传递, 下游生成 pending → skippedresult code 固定为 dependency_not_succeeded
  3. mutation 按 canonical Step key 顺序绑定连续 Run version/event sequence
  4. frontier event ID 不超过双方言共有的 36 字节,mutation ID 由 plan、StepRun identity/version 与目标状态做 domain-separated digest
  5. resolution 返回 projected ready StepRun ID,但不把读取结果解释为执行 claim。

当全部 required StepRun 终结时,纯契约给出固定的 Run terminal 建议: failed/skipped → failed,其次为 timed_outcancelled,全部成功才为 succeeded。terminal transition 同时绑定 expected Run version/event sequence、 固定 error code、finished time 和不超过 36 字节的确定性 RunEvent identitystorage adapter 必须把它与本轮 StepRun mutation 原子提交。

4. 双方言使用同一 port,不新增 package

同一显式 subpath 公开最多 64 条的 keyset candidate page 和 listCandidates/advance repository port

  • SQLite adapter 位于既有 @qinglong/local-sqlite,复用单 LocalSqliteOperationAuthorityBEGIN IMMEDIATEcandidate SQL 只返回可推进 pending dependency 或全部 StepRun 已终结但聚合 Run 未终结的 Workflow,不为 root ready 或等待中的 Workflow 制造空写;
  • PostgreSQL adapter 位于既有 @qinglong/cluster-postgres,由 caller 驱动有界 keyset pageadvance 使用短 SERIALIZABLE transaction,锁定聚合 Run 和完整 StepRun 集合,使用数据库 transaction clock,并对 serialization/deadlock 做有界 fresh-client retry
  • 两者都先验证 immutable plan、Run identity 和所有 StepRun canonical JSON/mirror 再按 StepRun version/digest/status CAS;一轮只更新一次聚合 Runversion/event sequence 按 mutation 数加 terminal event 数整体推进;
  • StepRun、RunEvent、StepRunMutation 和 terminal Run/Event 同事务提交,任一 drift、 fence 或写入冲突整体回滚;已终态 aggregate 重放只读收敛为 settled

没有新增 migration、workspace package、第三方依赖、连接、timer、watcher 或后台 scanner。ready Task 的执行 admission 仍必须在后续原子绑定 StepRun 与 RunAttempt/Executor fencefrontier 的 ready projection 本身不是执行 claim。

5. admission replay 只冻结准入证据,不冻结当前执行状态

双方言 admission exact replay 继续严格验证 immutable plan、receipt、初始 RunEvent、 初始 StepRun mutation 与 admission step ledger,但不再要求当前 Run/StepRun 永远停在 准入初态。当前 aggregate 只允许合法运行/终态、identity 不变、canonical record 完整且 version/event sequence 单调;初始 Event 查询固定截止 admission final sequence。 因此合法 frontier/Executor 推进后仍可 inspect/exact replay,而历史准入证据缺失或漂移 仍失败关闭。

不采用方案

把 Workflow 聚合 Run 交给普通 dispatcher

拒绝。聚合 Run 的 taskId 是 Workflow IDrevision 是 publication digest,不是一个 普通 Task execution revision;直接分发会错误解释定义并把一个 DAG 当成一次 Task。

为恢复器伪造 RunAttempt 或把聚合 Run提前置为终态

拒绝。伪造 Attempt 会制造不存在的执行事实;提前终结会隐藏尚未执行的 ready/pending StepRun。

每 Workflow 常驻 coordinator

拒绝。它给低配路由器增加空闲内存、唤醒与写放大,并让 Cluster 正确性依赖进程内 ownership。

新建 workflow package

拒绝。纯契约没有独立部署、重依赖或权限域,另拆会违反 D-207 的 package 粒度标准。

当前验证

  1. 通用 PostgreSQL recovery SQL 影响评估为 LOW,新增 Workflow trigger exclusion
  2. runtime-core 405/405,包含 frontier 纯契约 6/6 与后续 Attempt admission 4/4
  3. local-sqlite 180/180;真实 SQLite frontier 覆盖 dependency ready、失败向下游 skipped、成功/失败 aggregate terminal、bounded page、drift rollback、settled 与 admission-after-progress exact replay
  4. cluster-postgres 223 pass、1 条无测试 URL 条件 skipmock 契约覆盖 SERIALIZABLE、Run/StepRun row lock、单 aggregate CAS、terminal 原子写、 40001 fresh-client retry、settled 与显式 subpath
  5. PostgreSQL 18.4 arm64 physical HA 在禁止 image pull 下完整通过,包含 remote_apply、timeline 1→2、旧主 fencing、promotion、pg_rewind 和两 fresh control replicas;新增 pluginPackageWorkflowFrontierTerminalizesAtomicallypluginPackageWorkflowFrontierExactlyReplayspluginPackageWorkflowFrontierSurvivesPromotion 三项 gate,总 passed=true
  6. HA durable facts 为 1 个 Workflow admission、5 个 Workflow RunEvent、3 个 StepRunMutationpromotion 后 frontier 返回同一 succeeded/settled winner admission 仍 exact replay
  7. workspace 仍为 20 个 package,未新增生产依赖或常驻资源。

尚未关闭

  1. ready Task StepRun 到 RunAttempt/Executor 的原子执行 admission
  2. Workflow 取消、超时和 Task post-start recovery
  3. frontier caller 与 Local application/Cluster control 生命周期装配;
  4. Edge/Standalone 真实断电、资源门和产品 API/CLI/UI。