fix(server): boot/event-driven resume paths get rehydrate + freestyle handoff
resumeAbandonedSessions (boot pickup of RUNNING sessions) and the restart-while-approval-pending subscription still used the bare launchSessionResume: no artifact-cache rehydrate and no freestyle plan-lock handoff. A freestyle session killed mid-planning was grabbed by the boot path before the operator could POST /resume, completed the planning graph, and stranded — WorkflowCompleted but never locked (live repro: session b36b0e45, 2026-06-12). Both paths now use launchSessionResumeWithRehydrate; the bare launcher is deleted.
This commit is contained in:
@@ -168,7 +168,7 @@ class ServerModule(
|
|||||||
val orchState = orchestrationRepository.getState(sessionId)
|
val orchState = orchestrationRepository.getState(sessionId)
|
||||||
if (orchState.status != OrchestrationStatus.RUNNING) return@onEach
|
if (orchState.status != OrchestrationStatus.RUNNING) return@onEach
|
||||||
val graph = workflowRegistry.find(orchState.workflowId) ?: return@onEach
|
val graph = workflowRegistry.find(orchState.workflowId) ?: return@onEach
|
||||||
launchSessionResume(sessionId, graph)
|
launchSessionResumeWithRehydrate(sessionId, graph)
|
||||||
}
|
}
|
||||||
.launchIn(moduleScope)
|
.launchIn(moduleScope)
|
||||||
|
|
||||||
@@ -309,23 +309,12 @@ class ServerModule(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchSessionResume(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) {
|
|
||||||
activeSessionJobs.computeIfAbsent(sessionId) {
|
|
||||||
moduleScope.launch {
|
|
||||||
runCatching {
|
|
||||||
orchestrator.resume(sessionId, graph, orchestrationConfig())
|
|
||||||
}.onFailure { e ->
|
|
||||||
log.error("resumeSession: session={} failed", sessionId.value, e)
|
|
||||||
}
|
|
||||||
activeSessionJobs.remove(sessionId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rehydrates [artifactContentCache] from durable events and then resumes the session.
|
* Rehydrates [artifactContentCache] from durable events and then resumes the session.
|
||||||
* Called by [POST /sessions/{id}/resume] to restart a session after a server restart
|
* Used by [POST /sessions/{id}/resume], boot-time abandoned-session pickup, and the
|
||||||
* without re-running already-completed stages.
|
* restart-while-approval-pending subscription — every resume needs the rehydrate
|
||||||
|
* (pre-restart `needs`-artifacts live only in events/CAS) and the freestyle handoff
|
||||||
|
* (a planning graph that completes on resume must still lock and run phase 2).
|
||||||
*/
|
*/
|
||||||
fun launchSessionResumeWithRehydrate(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) {
|
fun launchSessionResumeWithRehydrate(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) {
|
||||||
activeSessionJobs.computeIfAbsent(sessionId) {
|
activeSessionJobs.computeIfAbsent(sessionId) {
|
||||||
@@ -365,7 +354,7 @@ class ServerModule(
|
|||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
log.info("resumeAbandoned: launching session={} workflow={}", sessionId.value, orchState.workflowId)
|
log.info("resumeAbandoned: launching session={} workflow={}", sessionId.value, orchState.workflowId)
|
||||||
launchSessionResume(sessionId, graph)
|
launchSessionResumeWithRehydrate(sessionId, graph)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user