Files
qinglong/docs/adr/ADR-0156-durable-step-run-aggregate-and-run-fence.md
T

115 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-0156:耐久 StepRun Aggregate 与 Run Fence
- 状态:Accepted
- 日期:2026-07-26
- 关联:ADR-0001、ADR-0133、ADR-0154、ADR-0155RFC 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@v1` record
- `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 必须在一个短事务中:
1. 串行化同一 Run
2. 优先识别 exact mutation replay
3. 校验 Run version/Event sequence 且拒绝 terminal Run
4. create 或 compare-and-set StepRun
5. 同时推进 Run version/Event sequence
6. 插入 canonical RunEvent
7. 插入 immutable StepRun mutation ledger
8. 提交后才返回 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。
## 被否决方案
1. **每种 Step kind 一张表**:放大 migration、查询与恢复复杂度,拒绝。
2. **只写 RunEvent,不建 StepRun aggregate**:没有独立状态和 CAS fence,拒绝。
3. **StepRun 独立提交后再更新 Run/Event**:崩溃窗口会产生不可解释的半提交,拒绝。
4. **用 `(run_id, step_key)` 作为唯一 identity**:重命名和子工作流展开会混淆稳定
identity,拒绝。
5. **为 StepRun 新增 workspace package**:没有独立部署/权限生命周期,拒绝。
6. **PG 只用应用层检查同 Run 引用**:旁路 SQL 可以制造跨 Run 引用,拒绝。
7. **旧 mutation 只返回当前 StepRun**:不能证明响应丢失前实际提交的结果,拒绝。
## 验证
- runtime-core StepRun 定向领域测试:10/10,完整 runtime-core297/297
- local-sqlite 完整测试:100/100
- cluster-postgres package 测试:153 pass、1 条件 skip
- PostgreSQL 18.4 arm64 六角色真实 integration39 pass、1 条件 skip,覆盖 StepRun
create、transition、历史 replay、查询和同 Run foreign key
- PostgreSQL 18.4 arm64 physical HA:主备复制、fencing、promotion、`pg_rewind`
双 control replica 与总 gate `passed=true`
- 未新增 workspace package、第三方依赖、timer、watcher、socket 或常驻进程。
## 后续门禁
1. 实现有界、append-only 的 Tool Trace span 与 Audit evidence receipt
2. 实现 opaque/encrypted plan 和 redacted preview Artifact
3. 把 plan/dispatch、StepRun、Trace、Audit 在同一 start barrier 中提交或精确重放;
4. 为 built-in、isolated process、MCP、HTTP 和 Worker adapter 分别定义启动后恢复证据;
5. 完成双方言故障注入、物理 edge 和 PostgreSQL HA 后,才解除 production
unreachable。