5.6 KiB
ADR-0156:耐久 StepRun Aggregate 与 Run Fence
- 状态:Accepted
- 日期:2026-07-26
- 关联:ADR-0001、ADR-0133、ADR-0154、ADR-0155;RFC D-03/D-131/D-148/D-149
背景
ADR-0155 已经要求任何 Tool adapter 在产生外部副作用前绑定独立的 StepRun、Trace 和
Audit evidence,但当时只有 evidence receipt 的纯契约。RunEvent.step_run_id 和
RunAttempt.step_run_id 仍是 nullable 占位列,不能证明:
- 工作流中的逻辑步骤具有独立、可恢复的状态;
- 同一次 mutation 没有在响应丢失后被重复应用;
- StepRun 变更和所属 Run 的 version/Event sequence 属于同一个提交;
- Attempt/Event 引用的 StepRun 确实属于同一个 Run。
直接为 StepRun 新建 workspace package 也没有独立部署、权限或版本生命周期依据,会让 低配路由器承担不必要的 importer 和依赖闭包。
决策
1. 领域契约留在 runtime-core 的显式 subpath
@qinglong/runtime-core/step-run 提供 profile-neutral 的 exact contract,不新增
workspace package 或第三方依赖:
- immutable
qinglong/step-run@v1record; task|tool|model|agent|condition|approval|subworkflow七种 kind;pending → ready → waiting_approval/running → lost/terminal的封闭状态机;- 每个 Run 最多 128 个 StepRun、每个 StepRun 最多 64 次 attempt;
- create/transition mutation 同时绑定预期 Run version/Event sequence、旧 StepRun version/digest/status 和 canonical RunEvent;
- repository 只公开 exact replay、按 identity 查询和稳定 keyset pagination。
StepRun 不保存 handler、input 明文、Secret、任意异常或 execute seam。inputRef、
outputRef 只能指向后续受控 Artifact 边界。
2. StepRun mutation 是 Run aggregate 的一部分
双方言 repository 必须在一个短事务中:
- 串行化同一 Run;
- 优先识别 exact mutation replay;
- 校验 Run version/Event sequence 且拒绝 terminal Run;
- create 或 compare-and-set StepRun;
- 同时推进 Run version/Event sequence;
- 插入 canonical RunEvent;
- 插入 immutable StepRun mutation ledger;
- 提交后才返回 applied。
历史 mutation 的 exact replay 必须返回当时的 StepRun、Run version 和 Event sequence, 即使当前 StepRun 已继续推进。相同 mutation identity 绑定不同内容必须失败关闭。
3. SQLite 与 PostgreSQL 保持语义对等
SQLite capability v26 使用 0051-step-runs 与 0052-capability-v26:
StepRuns保存完整镜像列和 canonical JSON,并用完整 CHECK 双向绑定;StepRunMutations保存历史结果和 Event/Run fence;- 四个 exact readiness-audited trigger 限制 RunAttempt/RunEvent 只能引用同 Run StepRun;
- repository 复用单个
LocalSqliteOperationAuthority和BEGIN IMMEDIATE。
PostgreSQL capability v27 使用 pg-0028-step-runs:
ql3.step_runs与ql3.step_run_mutations采用同一记录和 ledger 语义;- 复合 foreign key 在数据库层绑定
(run_id, step_run_id); - repository 使用
SERIALIZABLE、固定 statement/lock/idle timeout 和最多三次 serialization/deadlock retry; - 只有
ql3_runtime具有 StepRun 的 SELECT/INSERT/UPDATE 和 mutation ledger 的 SELECT/INSERT,其他产品角色保持零表权限。
两种方言允许使用不同数据库机制,但状态机、digest、fence、重放和上限必须一致。
4. 继续保持 production execution 不可达
显式 local-sqlite/step-run 与 cluster-postgres/step-run subpath 只提供存储能力;
root、通用 runtime、admin、worker ingress 不重新导出该 authority。当前不把仓储接入
任何 Tool adapter,也不提供 execute API。
只有后续同一 Tool start-barrier transaction 能够精确证明 plan/dispatch、StepRun、 Trace 和 Audit 全部耐久化,才允许产品 composition 取得 adapter authority。
被否决方案
- 每种 Step kind 一张表:放大 migration、查询与恢复复杂度,拒绝。
- 只写 RunEvent,不建 StepRun aggregate:没有独立状态和 CAS fence,拒绝。
- StepRun 独立提交后再更新 Run/Event:崩溃窗口会产生不可解释的半提交,拒绝。
- 用
(run_id, step_key)作为唯一 identity:重命名和子工作流展开会混淆稳定 identity,拒绝。 - 为 StepRun 新增 workspace package:没有独立部署/权限生命周期,拒绝。
- PG 只用应用层检查同 Run 引用:旁路 SQL 可以制造跨 Run 引用,拒绝。
- 旧 mutation 只返回当前 StepRun:不能证明响应丢失前实际提交的结果,拒绝。
验证
- runtime-core StepRun 定向领域测试:10/10,完整 runtime-core:297/297;
- local-sqlite 完整测试:100/100;
- cluster-postgres package 测试:153 pass、1 条件 skip;
- PostgreSQL 18.4 arm64 六角色真实 integration:39 pass、1 条件 skip,覆盖 StepRun create、transition、历史 replay、查询和同 Run foreign key;
- PostgreSQL 18.4 arm64 physical HA:主备复制、fencing、promotion、
pg_rewind、 双 control replica 与总 gatepassed=true; - 未新增 workspace package、第三方依赖、timer、watcher、socket 或常驻进程。
后续门禁
- 实现有界、append-only 的 Tool Trace span 与 Audit evidence receipt;
- 实现 opaque/encrypted plan 和 redacted preview Artifact;
- 把 plan/dispatch、StepRun、Trace、Audit 在同一 start barrier 中提交或精确重放;
- 为 built-in、isolated process、MCP、HTTP 和 Worker adapter 分别定义启动后恢复证据;
- 完成双方言故障注入、物理 edge 和 PostgreSQL HA 后,才解除 production unreachable。