mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# ADR-0227:Generation-bound Workflow Task Attempt 准入
|
||||
|
||||
- 状态:Proposed
|
||||
- 日期:2026-07-30
|
||||
- 关联 RFC:QL-RFC-0001 D-03、D-207、D-212
|
||||
- 关联 ADR:ADR-0031、ADR-0105、ADR-0223、ADR-0226、ADR-0228
|
||||
|
||||
## 背景
|
||||
|
||||
ADR-0223—0226 已把 Plugin Package Workflow 原子展开并推进到 ready StepRun。
|
||||
现有 Local/Cluster dispatch 与 activation 则是 Run-centric:
|
||||
|
||||
- candidate 从 Run 读取 `taskId/taskRevision`;
|
||||
- Local activation 要求 Run 为 `queued` 且 Attempt 是该 Run 的 latest;
|
||||
- Cluster dispatch 要求 Run 为 `queued|dispatching`,同样排除非 latest Attempt;
|
||||
- activation 会把 Run 自身推进为 `dispatching|running`。
|
||||
|
||||
这些约束适用于一个 Run 表示一次 Task 执行的普通路径,却不能直接用于 Workflow
|
||||
aggregate。Workflow Run 的 `taskId` 是 Workflow resource ID,`taskRevision` 是
|
||||
automation publication digest,Run 已为 `running`,而一个 DAG 还可能有多个 ready
|
||||
StepRun。直接复用现有 candidate 会把 Workflow 当 Task 执行;只创建 RunAttempt 也
|
||||
无法给 executor 提供正确、generation-bound 的 TaskDefinition 和 execution revision。
|
||||
|
||||
materialized Workflow plan 当前故意绑定 Package source Task 和 source digest;真正可执行
|
||||
的 runtime Task identity/revision 要由同 generation 的 Task reconciliation 生成:
|
||||
`pkg:<package>:<resource-task>` 与
|
||||
`qltd:v1:<revision>:<task-definition-content-digest>`。它不能在执行时从 current head
|
||||
临时读取,否则长期运行的 Workflow 会混入后续 Package generation。
|
||||
|
||||
## 决策
|
||||
|
||||
### 1. 在 source plan 与 executor 之间增加原子 Attempt admission ledger
|
||||
|
||||
新增现有 runtime-core 的显式 subpath
|
||||
`plugin-package-workflow-task-attempt-admission`,不新增 workspace package。一次纯
|
||||
admission 必须同时接受并验证:
|
||||
|
||||
- exact immutable Workflow plan、running aggregate Run 与一个 exact `ready` Task
|
||||
StepRun;
|
||||
- 与 plan generation/project/package/lock/materialized revision 全匹配的 immutable
|
||||
Task reconciliation receipt;
|
||||
- 一个完整且可重新计算 digest 的 Local 或 Cluster immutable execution revision;
|
||||
- storage 分配的 Run-global Attempt number 和单调 database observation time。
|
||||
|
||||
契约从 reconciliation item 推导 runtime Task identity 和 `qltd:v1` revision,拒绝
|
||||
caller 单独声称 task/execution digest。Local revision 固定 `local_process`,Cluster
|
||||
revision 固定 `remote_worker`。
|
||||
|
||||
### 2. 一次 admission 只创建执行意图,不伪造已启动状态
|
||||
|
||||
纯结果包含:
|
||||
|
||||
- deterministic、双方言可写的最多 36 字节 Attempt ID 与 RunEvent ID;
|
||||
- 带 `stepRunId`、Run-global Attempt number、`claimed` 和 exact executor type 的
|
||||
RunAttempt;
|
||||
- `workflow.task_attempt_admitted` Event;
|
||||
- 保持 `running`、只推进一次 version/event sequence 的 aggregate Run;
|
||||
- 绑定 plan、ready StepRun version/digest、reconciliation receipt digest、runtime
|
||||
Task identity/revision、TaskDefinition digest、execution digest、Attempt/Event/Run
|
||||
fence 的 immutable receipt。
|
||||
|
||||
StepRun 在 admission 时仍为 `ready`,因为 `claimed` 只表示 durable dispatch intent,
|
||||
不等于进程/Worker 已启动。StepRun 的 `attemptCount` 继续只在进入 `running` 时增加。
|
||||
后续 StepRun-aware activation 必须原子推进 Attempt 与 StepRun;不能在本 ADR 中提前
|
||||
写 `running`。
|
||||
|
||||
### 3. 双方言 adapter 使用短事务与 current-ready-epoch 唯一性
|
||||
|
||||
双方言实现必须:
|
||||
|
||||
- 以 `(run, stepRun, stepRunVersion)` 作为一个 ready epoch 的 exact replay 边界;
|
||||
- 在 Run lock 下分配 Run-global Attempt number,插入 Attempt、Event、admission
|
||||
receipt 并对 aggregate Run 做一次 CAS;
|
||||
- candidate page 最多 64 条,只列 `ready` 且当前 epoch 尚无 admission winner 的
|
||||
StepRun;
|
||||
- SQLite 复用单 operation authority 和 `BEGIN IMMEDIATE`;Cluster 使用
|
||||
caller-driven keyset 与短 `SERIALIZABLE` transaction;
|
||||
- COMMIT response loss 只读取 durable receipt,不生成第二个 Attempt;
|
||||
- 不新增 timer、watcher、listener、per-Workflow coordinator、连接或缓存。
|
||||
|
||||
### 4. execution activation 必须新增 StepRun-aware 路径
|
||||
|
||||
现有普通 Task Run dispatcher/activation 保持不变。Workflow Attempt candidate 后续从
|
||||
admission ledger 读取 Task identity/revision/execution digest,并按 StepRun 而不是
|
||||
“latest Attempt of Run”围栏。Local/Cluster activation 都必须保留 aggregate Run 为
|
||||
`running`,原子推进 Attempt 和对应 StepRun,允许多个独立 ready Step 并行,同时仍由
|
||||
Run version/event sequence 作为 aggregate serialization boundary。
|
||||
|
||||
Cluster 路径复用现有 `cluster-postgres` repository、`run_attempts` 和
|
||||
`run_dispatch_leases`,不新增 Workflow executor package 或第二套 lease 表。
|
||||
dispatcher 通过 immutable Task Attempt admission ledger 投影 Task
|
||||
project/revision/execution identity,并按 StepRun 检查 newer Attempt;普通 Task Run
|
||||
继续使用原 Run-centric 条件。lease claim、starting、running、start failure 和
|
||||
completion 都在同一个 repository 内按 ledger 是否存在分支,普通 Run 行为不变。
|
||||
|
||||
Workflow Task activation/completion 的 aggregate 规则固定为:
|
||||
|
||||
- 父 Run 始终保持 `running`,只推进 version/event sequence;
|
||||
- Attempt 保存 Worker/Lease/callback/Artifact terminal evidence;
|
||||
- StepRun 通过 canonical mutation/event 推进,`attemptCount` 只在进入 `running`
|
||||
时增加;
|
||||
- `starting` 后 Worker 直接完成的崩溃窗口必须原子补写
|
||||
`ready → running → terminal`,不能跳过 StepRun start barrier;
|
||||
- terminal completion exact replay 绑定 immutable Attempt/Event,即使父 Run 后续因
|
||||
其他步骤取消或终结也不能改写历史结果。
|
||||
|
||||
### 5. Attempt deadline 是 Task 级停止意图,不是父 Run 取消
|
||||
|
||||
Workflow Task 的 `deadline_at_ms` 到期时,Remote Worker lease control 必须返回
|
||||
`stop_requested(timeout)`,并以 StepRun/Attempt 绑定的
|
||||
`workflow.task_timeout_requested` Event 留下 durable audit,但不得写父 Run 的
|
||||
`cancel_requested_at_ms`。完成或 start-failure 回执以数据库观察时间复验 deadline,
|
||||
将该 Attempt/StepRun 收敛为 `timed_out`;父 Run 由 frontier 汇总所有 StepRun 后决定
|
||||
最终状态。
|
||||
|
||||
用户、Policy、shutdown 等明确的整 Run cancel intent 仍保存在父 Run,并可投影到所有
|
||||
在途 Worker。Task deadline 与 Run cancellation 必须保持两个 authority,避免一步超时
|
||||
取消整个 DAG。
|
||||
|
||||
### 6. Workflow Task recovery 必须拥有独立的 StepRun 语义
|
||||
|
||||
现有通用 lost transition 是单 Run/单 Attempt 模型,会把 active Attempt 与父 Run
|
||||
一起标记为 `lost`,因此 Workflow aggregate Run 仍按 trigger type 排除。Task Attempt
|
||||
则可以复用同一个有界 recovery source、`run_recovery_controls` claim 与 evidence
|
||||
provider,但 resolution 必须通过 immutable admission ledger 识别
|
||||
`workflow_task` scope,禁止执行 `mark_attempt_and_run_lost`。
|
||||
|
||||
pristine、尚未获得 lease 的 claimed Workflow Task Attempt 是正常 dispatcher backlog,
|
||||
不得因为 `lease_expires_at_ms IS NULL` 被当成 lost。获得过 lease 且过期后:
|
||||
|
||||
- `claimed` 尚未跨过 start barrier,没有外部副作用;旧 Attempt 写为 `lost`,exact
|
||||
`ready` StepRun 以 `ready→ready` 增加 version/digest,保持 attempt count、ready time
|
||||
与父 Run `running`,从而产生一个可再次 admission 的新 epoch;
|
||||
- `starting` 或 `running` 已存在外部副作用不确定性,只有可信 `not_running` evidence
|
||||
才能恢复;Attempt 写为 `lost`,StepRun 收敛为 `failed`。running 路径必须保留
|
||||
`running→lost→failed` Event/mutation 链。v1 没有 Step 级幂等/去重策略,因此跨过
|
||||
start barrier 后禁止静默自动重试。
|
||||
|
||||
PostgreSQL resolution 在 live claim fence 下先取得与 activation/completion 相同的
|
||||
Attempt advisory authority,再复验 Run、Attempt、admission receipt 与 canonical
|
||||
StepRun snapshot,并在一个事务内 CAS 父 Run counters、Attempt、StepRun,追加
|
||||
Attempt Event、StepRun Event 与 mutation。父 Run 状态不变,最终状态继续由 frontier
|
||||
汇总。实现复用现有 runtime-core/cluster-postgres 与 claim 表,不新增 workspace
|
||||
package、lease/recovery 表或常驻 timer。
|
||||
|
||||
## 不采用方案
|
||||
|
||||
### 把 Workflow aggregate 改回 queued 并复用普通 dispatcher
|
||||
|
||||
拒绝。它会把 Workflow ID/publication digest 当成 TaskDefinition identity,并且一次
|
||||
latest Attempt 模型无法表达 DAG 并行 Step。
|
||||
|
||||
### 为每个 Step 伪造 child Run
|
||||
|
||||
拒绝。现有 StepRun 与 RunAttempt 的 same-Run 外键会被破坏;额外 child Run 还会复制
|
||||
Workflow/Step 状态、取消、重试和终态语义,制造双聚合。
|
||||
|
||||
### 在 dispatch 时读取 current TaskDefinition head
|
||||
|
||||
拒绝。current head 可能已属于后续 Package generation,导致一次 immutable Workflow
|
||||
执行混合不同 generation。
|
||||
|
||||
### 只保存 taskId/taskRevision,不验证完整 execution revision
|
||||
|
||||
拒绝。caller 可伪造 execution digest 或 executor type;必须使用既有 Local/Cluster
|
||||
normalizer 重新计算完整 immutable revision。
|
||||
|
||||
### 新建 workflow-executor package
|
||||
|
||||
拒绝。当前能力没有独立部署、重依赖或权限域,复用 runtime-core 与双方言 storage
|
||||
subpath 更符合 D-207。
|
||||
|
||||
## 当前验证
|
||||
|
||||
1. GitNexus 对 admission、dispatch、lease、activation、completion、lease control
|
||||
和 recovery source 的影响均为 LOW;普通 Task Run 分支保留原行为;
|
||||
2. SQLite v42 已完成 immutable ledger、readiness 与 `BEGIN IMMEDIATE` adapter;
|
||||
PostgreSQL `pg-0046`/control-core v45 已完成 ledger、runtime-only snapshot、
|
||||
六角色 readiness 与短 `SERIALIZABLE` adapter;
|
||||
3. Cluster 已覆盖 bounded candidate、per-Step newer Attempt、父 Run 不变的 lease、
|
||||
starting/running/start-failure/completion、`starting` crash window、Task deadline
|
||||
和父 Run 后续取消后的 exact replay,并已实现 admission-bound 专用 recovery;
|
||||
4. ADR-0228 已补齐双方言整 Workflow cancellation convergence:取消后冻结
|
||||
frontier/admission,尊重 start barrier,最后终结父聚合;
|
||||
5. runtime-core 419/419、local-sqlite 185/185;cluster-postgres 237 项为
|
||||
236 pass、1 条件 skip、0 fail;
|
||||
6. 全新 PostgreSQL 18.4 六角色实例已完成 Task admission→recovery→requeue→
|
||||
second admission→whole cancellation;
|
||||
7. PostgreSQL 18.4 arm64 physical streaming HA 在
|
||||
`QL3_HA_SKIP_IMAGE_PULL=true` 下通过:`remote_apply`、timeline 1→2、旧主先
|
||||
fencing、`pg_rewind` 只读同步重入、两个 fresh control replica 与总
|
||||
`gates.passed=true`;
|
||||
8. workspace 仍为 20 个 package;本切片没有新增 package、第三方依赖、migration、
|
||||
timer、watcher、listener、缓存或第二套 lease/cancellation 表。
|
||||
|
||||
## 尚未关闭
|
||||
|
||||
1. Local StepRun-aware activation、completion、deadline 与真实断电门;
|
||||
2. application/control lifecycle 装配、并行度/背压、产品入口和真实低配资源门;
|
||||
3. 在途 Worker stop_requested→completion/recovery→父 Workflow terminal 的产品
|
||||
crash-window 门;
|
||||
4. 在 PostgreSQL HA fixture 中增加 Workflow Task admission→dispatch→activation→
|
||||
completion/timeout→promotion replay 的独立 domain gate。
|
||||
Reference in New Issue
Block a user