Skip to content

[automation] getSuspendedScreen 只读内存热缓存 —— 重启后一个仍然活着的挂起 run 取不到它的 screen(404) #4515

Description

@os-zhuang

在做 #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),让路由改用它,把同步版本留作热路径快查——代价是两个名字做一件事。

参考

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions