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
@@ -368,6 +368,7 @@ fun main() {
compactionService = journalCompactionService,
artifactKindRegistry = artifactKindRegistry,
repoKnowledgeRetriever = repoKnowledgeRetriever,
readyTaskCounter = com.correx.apps.server.tasks.ProjectReadyTaskCounter(eventStore, taskService),
)
val workflowRegistry = FileSystemWorkflowRegistry(
InfrastructureModule.createWorkflowLoader(configArtifactKindsEarly),
@@ -0,0 +1,31 @@
package com.correx.apps.server.tasks
import com.correx.core.approvals.ProjectIdentity
import com.correx.core.events.events.SessionWorkspaceBoundEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId
import com.correx.core.kernel.orchestration.ReadyTaskCounter
import com.correx.core.tasks.TaskService
/**
* [ReadyTaskCounter] over the task projection. Resolves the session to its project the same way the
* approval gate does (bound workspace root → [ProjectIdentity.of]) and counts the ready tasks there.
* Pure read over event-derived state, so it stays replay-safe.
*
* Caveat for the execution loop: tasks are keyed by whatever `project` string the decomposer passes
* to task tools; this counts the project derived from the workspace root. Those must agree for the
* loop predicate to fire — Slice 2/4 pins that down.
*/
class ProjectReadyTaskCounter(
private val eventStore: EventStore,
private val taskService: TaskService,
) : ReadyTaskCounter {
override fun count(sessionId: SessionId): Int {
val workspaceRoot = eventStore.read(sessionId)
.mapNotNull { it.payload as? SessionWorkspaceBoundEvent }
.lastOrNull()
?.workspaceRoot
?: return 0
return taskService.ready(ProjectIdentity.of(workspaceRoot)).size
}
}