5.6 KiB
ADR-0380:Local lost Run retry 复用单一 execution-control cadence
- 状态:Accepted
- 日期:2026-08-12
- 关联 RFC:QL-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仅在 durablenext_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,每轮顺序固定为:
- 消费内存中的 completion 通知;
- 扫描 cancellation/deadline control;
- 执行一页 lost retry;
- 到慢周期时清理 completion receipt 与 Run log Artifact。
启动顺序仍为 startup recovery → 首次 execution-control cycle → scheduler。因而启动恢复刚写入的 lost 会在 Scheduler 首轮前进入安全 retry/终态。停机的 control drain 不再创建 retry work。
Edge 每轮最多 2 条,沿用 5 秒 control cadence;Standalone 每轮最多 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 audit:18 个 package、1055 个 source file、1037 个 nested source file,
singleSourcePackages=[]、shallowSourcePackages=[]; - cluster dependency audit:
findings=[];Edge import audit:121 个 imported module,root dependency 与 import 违规均为空; - 本切片未新增 package、migration、表、索引、timer 或 SQLite connection。
被否决的替代方案
- 新增
ql3-local-retrypackage:只有一个 SQLite adapter,继续放大 package 碎片化,拒绝。 - 复制 Cluster transition 为 Local 版本:会让安全与终态规则漂移,拒绝。
- 独立 retry timer:增加路由设备唤醒、竞态和 shutdown 故障域,拒绝。
- 启动时递归清空全部 lost backlog:无法约束启动延迟与写放大,拒绝。
safety=unknown也自动重试:可能复制不可逆外部副作用,拒绝。- 把人工 retry API 一并塞入本切片:认证、Policy、审计与新 mutation 语义未冻结,拒绝。