BLOGE
0.9.9-RC1在线版 · 编制 2026-11-02 · English
第 26 章 —— Durable 与 Temporal 验证
本章承诺: 你会用有限 action plan 验证同一条 retained execution 的暂停、信号、恢复和逻辑时间,并把「最终结果」「不能提前发生」「限定时间内必须发生」写成不同的可观察合同。
学习目标
- 使用
EXECUTE、SIGNAL、ADVANCE_TIME和RESUME,不把长流程压成一次方法调用。 - 区分
DurableVerificationRunner的本地执行报告和DurableSourceBoundVerifier的源码绑定证据。 - 用
DurableTemporalContract表达 step、trace 和 effect 的有限事实。 - 识别 streaming、timer、checkpoint 和非协作超时的停止边界。
最终 output 看不到等待过程
人工复核的最终结果可能都是 approved,但两个流程仍然不同:一个在收到人工决定后才通知,另一个在等待期间提前通知。只比较最终 output 会把第二个错误漏掉。
DurableVerificationRunner 为一个 Case 创建真实 DurableGraphEngine 和 retained session。默认使用隔离的 in-memory durable stores。action 按列表串行执行,并绑定同一个 execution identity;逻辑时间由 verifier 控制,不依赖测试运行时经过了多少墙钟时间。
Action plan 是有限脚本,不是工作流语言
下面是「开始后暂停,24 小时内收到人工批准」路径:
schemaVersion: 1
actions:
- actionId: start
type: EXECUTE
- actionId: wait-before-decision
type: ADVANCE_TIME
duration: PT23H
- actionId: approve
type: SIGNAL
nodeId: approval
signalData: approved
约束比语法更重要:
- action 数量必须在 1 到 64 之间。
- 第一条且只有第一条 action 必须是
EXECUTE。 ADVANCE_TIME的 ISO-8601 duration 必须为正。SIGNAL必须精确命中当前 suspended node。RESUME只用于已经失败且已写入 checkpoint 的 execution;仍在 suspended 的 execution 应先SIGNAL。CANCEL没有稳定 durable seam,sidecar 会返回DURABLE_ACTION_UNSUPPORTED。
超时路径应使用另一份 action plan,把逻辑时间推进到 PT24H 或业务确认的边界。不要把「收到信号」和「没有收到信号」硬塞进一条线性 plan;它们是两个业务 Case。
同时检查 step 与事件顺序
schemaVersion: 1
stepExpectations:
- expectationId: initially-suspended
afterActionId: start
outcome: SUSPENDED
suspendedNodeIds: [approval]
traceExpectations:
- expectationId: notify-after-approval
matcher: MUST_NOT_OCCUR_BEFORE
event: {type: NODE_COMPLETED, graphName: approval-flow, nodeId: notification}
afterActionId: approve
- expectationId: notify-within-five-minutes
matcher: MUST_OCCUR_WITHIN_LOGICAL_TIME
event: {type: NODE_COMPLETED, graphName: approval-flow, nodeId: notification}
within: PT5M
stepExpectations 检查某个 action 后的 outcome 和 suspended set。traceExpectations 使用 BLOGE listener 记录的 node identity、action 和逻辑时间偏移;它不执行脚本,也不匹配异常文本或原始业务 payload。
需要验证业务 effect 的重复发送时,使用 temporal v2:
schemaVersion: 2
effectExpectations:
- expectationId: ledger-booked-once
matcher: EXACTLY_ONCE_EFFECT_KEY
effect: {portId: ledger.book, key: LA-42}
所有参与 Operator 必须提供完整 dependency manifest,目标 EffectPort 必须声明稳定 idempotencyKey(input)。成功、失败和被拒绝的调用都计数,因为调用方看到失败之前,外部系统可能已经接受 effect。报告只保存类型化摘要,不保存原始 key。
运行本地 durable 验证
DurableVerificationPlan plan = DurableVerificationPlan.load(actionPath);
DurableTemporalContract temporal = DurableTemporalContract.load(temporalPath);
DurableVerificationReport report = DurableVerificationRunner.builder()
.harnessTimeout(Duration.ofSeconds(30))
.build()
.verify(
new DurableVerificationInput(
new VerificationCaseContext("loan-review", "approved-in-time"),
compiledGraph,
initialContext,
compiledOperators),
plan,
temporal);
这份 report 是本地 authoring evidence。若要发布 source-bound durable evidence,使用 DurableSourceBoundVerifier 从 tracked graph 和 sidecar 编译,并提供干净 Git worktree、可冻结的 registry/resolver、DURABLE execution profile,以及 ignored 且不在 classpath 中的输出目录。reader 不调用 bootstrap 重新编译 candidate。