Files
qinglong/docs/adr/ADR-0380-local-lost-run-retry-single-control-cadence.md
T

76 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0380Local lost Run retry 复用单一 execution-control cadence
- 状态:Accepted
- 日期:2026-08-12
- 关联 RFCQL-RFC-0001 D-292
- 前置决策:ADR-0021、ADR-0066、ADR-0067、ADR-0232、ADR-0366
## 上下文
Cluster 已消费 admission-time `RunRetryPolicy`,但 Edge/Standalone 的启动恢复只能把确定不再运行的本地 Attempt 与 Run 标记为 `lost`。此后没有生产 consumer 处理该状态:安全且显式启用的重试不会创建新 Attempt,禁用、unsafe 或耗尽的 Run 也不会终态关闭。
直接复制一套 Local retry policy、建立独立 lifecycle package,或为 retry 新增 timer/SQLite 连接都会形成不合理架构。纯 transition 本来就是 profile-neutral,只因最初由 Cluster 接入而使用 `Cluster*` 名称;本地运行时也已经拥有单 SQLite operation authority、`BEGIN IMMEDIATE` Run aggregate transaction 和唯一 execution-control timer。
## 决策
### 1. 通用契约留在现有 Runtime Core Run 域
不新增 workspace package,也不复制状态机。`runtime-core` 对既有纯 transition、page contract、coordinator 与错误发布 canonical `RunLostRetry*` 名称和 `run-lost-retry` subpath;早期 `ClusterRunLostRetry*` 名称继续兼容。新代码只能依赖 profile-neutral 名称。
该兼容层不创建第二份实现。后续在具备 call-graph-aware rename 工具和独立废弃周期后,才允许移动物理文件或删除旧名称;本切片不以文本替换破坏已有 Cluster consumer。
### 2. SQLite adapter 复用唯一 operation authority
`LocalSqliteRunLostRetryRepository` 位于 `ql3-local-sqlite/src/run`,不形成单文件微包。它:
- 只选择 runtime-owned、非 Workflow、无 cancellation intent、latest Attempt 为 `lost` 的 Run
- `lost` 优先,`retry_wait` 仅在 durable `next_attempt_at_ms` 到期后进入候选;
- 候选发现是一次有界 authority 操作,每个候选在同一既有 `LocalSqliteRunRepository.transaction()` 中重读并原子 CAS Run、RetryPolicy,插入新 Attempt 与 Events
- 已变化的候选返回 `raced`,响应丢失后的重放由当前 durable aggregate 自然收敛;
- 不拥有连接、cursor、timer、listener、watcher 或常驻内存队列。
没有 policy、未启用或 `maxAttempts<=1` 时以 `RUN_LOST_RETRY_DISABLED` 失败关闭;`safety=unknown``RUN_LOST_RETRY_UNSAFE` 失败关闭;耗尽以 `RUN_LOST_RETRY_EXHAUSTED` 失败关闭。只有 admission 时已证明 `idempotent|deduplicated` 的 policy 才能先进入 `retry_wait`,到期后创建一个全新、无执行 handle/lease 的 claimed Attempt。
### 3. Local application 复用现有控制周期
lost retry 注入 `LocalExecutionControlLifecycle`,每轮顺序固定为:
1. 消费内存中的 completion 通知;
2. 扫描 cancellation/deadline control
3. 执行一页 lost retry
4. 到慢周期时清理 completion receipt 与 Run log Artifact。
启动顺序仍为 startup recovery → 首次 execution-control cycle → scheduler。因而启动恢复刚写入的 `lost` 会在 Scheduler 首轮前进入安全 retry/终态。停机的 control drain 不再创建 retry work。
Edge 每轮最多 2 条,沿用 5 秒 control cadenceStandalone 每轮最多 16 条,沿用 1 秒 cadence。两者均复用已有 `unref` timer 和 SQLite authority,没有增加低配路由设备的空闲连接、常驻 sidecar 或独立唤醒源。
### 4. 明确不包含人工重试产品入口
本 ADR 只消费 admission 时冻结的自动 retry policy,不授予用户新的 mutation authority。`run.retry` Policy permission、人工 recovery API/CLI/UI、强认证、rate limit、审计与“对失败 Run 手工重跑”的产品语义仍需独立 ADR;不得把自动 lost retry 误报为人工恢复完成。
## 验收
- Runtime Core 验证通用名称与旧 Cluster 名称指向同一实现;
- SQLite 真库验证安全 lost → retry_wait → 新 Attempt 的原子闭环、禁用 policy 失败关闭、页上限、`hasMore` 与重放不重复;
- Local lifecycle 验证 control → lost retry → cleanup 顺序及重叠 `runOnce` 合并;
- Local application 真启动验证 startup recovery 后、Scheduler 前完成首次 lost retry
- 受影响 package build/test、完整 QL3 package/backend 回归、dependency/package/Edge import audit 必须通过后才允许阶段性提交;
- package 数、migration 数、表/索引数、timer 数和 SQLite connection 数不得增加。
### 验收证据(2026-08-12
- `pnpm run test:packages:ql3`18 个 QL3 package 全量清理、构建与测试通过,退出码 0;
- backend 回归:1165 项中 1163 通过、2 项按环境条件跳过、0 失败;
- package boundary audit18 个 package、1055 个 source file、1037 个 nested source file`singleSourcePackages=[]``shallowSourcePackages=[]`
- cluster dependency audit`findings=[]`Edge import audit121 个 imported moduleroot dependency 与 import 违规均为空;
- 本切片未新增 package、migration、表、索引、timer 或 SQLite connection。
## 被否决的替代方案
1. **新增 `ql3-local-retry` package**:只有一个 SQLite adapter,继续放大 package 碎片化,拒绝。
2. **复制 Cluster transition 为 Local 版本**:会让安全与终态规则漂移,拒绝。
3. **独立 retry timer**:增加路由设备唤醒、竞态和 shutdown 故障域,拒绝。
4. **启动时递归清空全部 lost backlog**:无法约束启动延迟与写放大,拒绝。
5. **`safety=unknown` 也自动重试**:可能复制不可逆外部副作用,拒绝。
6. **把人工 retry API 一并塞入本切片**:认证、Policy、审计与新 mutation 语义未冻结,拒绝。