feat(transitions): tasks_ready predicate for execution loop

New TasksReady condition driven by EvaluationContext.readyTaskCount,
fed at resolve time via a kernel-declared ReadyTaskCounter interface
implemented in apps/server over TaskService (keeps core:kernel
decoupled from core:tasks). Loops a graph while ready>0, exits at 0.
This commit is contained in:
2026-06-29 00:45:33 +04:00
parent d26f20c316
commit c1e4c7b25e
14 changed files with 164 additions and 1 deletions
@@ -1,5 +1,7 @@
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.transitions.conditions.Not
import com.correx.core.transitions.conditions.TasksReady
import com.correx.core.events.types.TransitionId
import com.correx.core.transitions.evaluation.EvaluationContext
import com.correx.core.transitions.evaluation.TransitionConditionEvaluator
@@ -210,6 +212,28 @@ class DefaultTransitionResolverTest {
)
}
@Test
fun `TasksReady loops while ready tasks remain and exits at zero`() {
val graph = graph(
start = "claim",
transitions = setOf(
edge(id = "loop", from = "claim", to = "claim", condition = TasksReady),
edge(id = "exit", from = "claim", to = "done", condition = Not(TasksReady)),
),
stages = setOf("claim", "done"),
)
val base = context(currentStage = "claim")
assertEquals(
StageId("claim"),
(resolver.resolve(graph, base.copy(readyTaskCount = 2)) as TransitionDecision.Move).to,
)
assertEquals(
StageId("done"),
(resolver.resolve(graph, base.copy(readyTaskCount = 0)) as TransitionDecision.Move).to,
)
}
private fun alwaysTrue() =
TransitionCondition { true }