在做 #4470(让验证 harness 走真实持久化路径)时发现的,与那张卡本身无关,单独记录。
问题
AutomationEngine.getSuspendedScreen 只查内存热缓存:
// packages/services/service-automation/src/engine.ts
getSuspendedScreen(runId: string): ScreenSpec | null {
return this.suspendedRuns.get(runId)?.screen ?? null;
}
它是同步的,所以没有能力去问挂起存储。而 SuspendedRun.screen 是会持久化的(sys_automation_run.screen_json),resume() 也确实会在热缓存未命中时经 loadSuspendedRunStrict 从库里冷读回来。
于是两条路径对同一个 run 给出相反的答案:
| 进程重启后,对一个 durably 挂起的 screen run |
结果 |
POST …/runs/:runId/resume |
正常继续(从库里 rehydrate) |
GET …/runs/:runId/screen |
404 No pending screen for run |
为什么这有实际影响
这条路由的存在意义就是「刷新安全的重新拉取」——屏幕流 runner 在用户刷新页面、或换设备继续时用它把表单再画出来。而它恰恰在最需要它的那一刻失效:进程重启之后。用户看到的是「这个审批/表单不见了」,但 run 其实活得好好的,resume 也还能打通——只是客户端已经拿不到该渲染什么了。
这也正是 ADR-0019 持久化挂起要解决的场景:暂停要能跨进程存活。存活了,但它的渲染契约没跟上。
复现
packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts(#4502 引入)最初就是这样写的,在这一步红:
AssertionError: screen not rehydrated:
{"success":false,"error":{"code":"RESOURCE_NOT_FOUND","message":"No pending screen for run","httpStatus":404}}
修复方向(需要决策)
直接的修法是让它跟 resume 一样能读存储,但那要把方法变成异步,而它是公开契约的一部分:
// packages/spec/src/contracts/automation-service.ts
getSuspendedScreen?(runId: string): ScreenSpec | null;
改成 Promise<ScreenSpec | null> 是破坏性变更,需要同时改 packages/runtime/src/domains/automation.ts 里的路由调用点和任何其它消费方。所以这条没有顺手修,留给能拍板协议变更的人。
另一种不破坏签名的思路是在 resume 之外增加一个异步的 loadSuspendedScreen(runId),让路由改用它,把同步版本留作热路径快查——代价是两个名字做一件事。
参考
在做 #4470(让验证 harness 走真实持久化路径)时发现的,与那张卡本身无关,单独记录。
问题
AutomationEngine.getSuspendedScreen只查内存热缓存:它是同步的,所以没有能力去问挂起存储。而
SuspendedRun.screen是会持久化的(sys_automation_run.screen_json),resume()也确实会在热缓存未命中时经loadSuspendedRunStrict从库里冷读回来。于是两条路径对同一个 run 给出相反的答案:
POST …/runs/:runId/resumeGET …/runs/:runId/screenNo pending screen for run为什么这有实际影响
这条路由的存在意义就是「刷新安全的重新拉取」——屏幕流 runner 在用户刷新页面、或换设备继续时用它把表单再画出来。而它恰恰在最需要它的那一刻失效:进程重启之后。用户看到的是「这个审批/表单不见了」,但 run 其实活得好好的,
resume也还能打通——只是客户端已经拿不到该渲染什么了。这也正是 ADR-0019 持久化挂起要解决的场景:暂停要能跨进程存活。存活了,但它的渲染契约没跟上。
复现
packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts(#4502 引入)最初就是这样写的,在这一步红:修复方向(需要决策)
直接的修法是让它跟
resume一样能读存储,但那要把方法变成异步,而它是公开契约的一部分:改成
Promise<ScreenSpec | null>是破坏性变更,需要同时改packages/runtime/src/domains/automation.ts里的路由调用点和任何其它消费方。所以这条没有顺手修,留给能拍板协议变更的人。另一种不破坏签名的思路是在
resume之外增加一个异步的loadSuspendedScreen(runId),让路由改用它,把同步版本留作热路径快查——代价是两个名字做一件事。参考
packages/services/service-automation/src/engine.ts——getSuspendedScreen、loadSuspendedRunStrictpackages/services/service-automation/src/suspended-run-store.ts——screen_json的序列化/反序列化packages/runtime/src/domains/automation.ts——GET /:name/runs/:runId/screen