import com.correx.core.approvals.ApprovalProjector import com.correx.core.approvals.DefaultApprovalReducer import com.correx.core.approvals.DefaultApprovalRepository import com.correx.core.approvals.domain.DefaultApprovalEngine import com.correx.core.artifacts.DefaultArtifactReducer import com.correx.testing.fixtures.context.ContextFixtures import com.correx.core.events.events.TransitionExecutedEvent import com.correx.core.events.events.WorkflowCompletedEvent import com.correx.core.events.execution.RetryPolicy import com.correx.core.events.types.SessionId import com.correx.core.events.types.StageId import com.correx.core.events.types.TransitionId import com.correx.core.inference.InferenceRepository import com.correx.core.inference.InferenceState import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalRepository import com.correx.core.kernel.orchestration.DefaultOrchestrationReducer import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator import com.correx.core.kernel.orchestration.OrchestrationConfig import com.correx.core.kernel.orchestration.OrchestrationProjector import com.correx.core.kernel.orchestration.OrchestrationRepository import com.correx.core.kernel.orchestration.OrchestratorEngines import com.correx.core.kernel.orchestration.OrchestratorRepositories import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.risk.DefaultRiskAssessor import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.sessions.projections.replay.EventReplayer import com.correx.core.transitions.graph.StageConfig import com.correx.core.transitions.graph.TransitionEdge import com.correx.core.transitions.graph.WorkflowGraph import com.correx.core.validation.pipeline.ValidationPipeline import com.correx.infrastructure.persistence.InMemoryEventStore import com.correx.infrastructure.persistence.artifact.LiveArtifactRepository import com.correx.testing.contracts.fixtures.artifactstore.NoopArtifactStore import com.correx.testing.fixtures.InferenceFixtures import com.correx.testing.fixtures.cyclePolicyMissingValidator import com.correx.testing.fixtures.transitions.TransitionFixtures import com.correx.testing.kernel.MockSessionEventReplayer import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test /** * Regression guard for the [com.correx.core.kernel.orchestration.subagent.SubagentRunner] seam: * stage execution is now dispatched through the runner rather than calling executeStage inline. * The default in-session runner must produce behaviour identical to the prior path — a workflow * still runs every non-terminal stage to completion. (executeStage is module-internal, so the * runner cannot be wrapped from this test module; we assert per-stage execution end-to-end.) */ class SubagentRunnerSeamTest { private val eventStore = InMemoryEventStore() private val sessionReplayer = MockSessionEventReplayer() private val sessionRepository = DefaultSessionRepository(sessionReplayer) private val orchestrationRepository = OrchestrationRepository( DefaultEventReplayer(eventStore, OrchestrationProjector(DefaultOrchestrationReducer())), ) private val inferenceRepository = InferenceRepository( object : EventReplayer { override fun rebuild(sessionId: SessionId) = InferenceState() }, ) private val approvalRepository = DefaultApprovalRepository( DefaultEventReplayer(eventStore, ApprovalProjector(DefaultApprovalReducer())), ) private val decisionJournalRepository = DefaultDecisionJournalRepository( DefaultEventReplayer(eventStore, DecisionJournalProjector(DefaultDecisionJournalReducer())), ) private val artifactStore = NoopArtifactStore() private val retryCoordinator = DefaultRetryCoordinator(eventStore) private val repositories = OrchestratorRepositories( eventStore = eventStore, inferenceRepository = inferenceRepository, orchestrationRepository = orchestrationRepository, sessionRepository = sessionRepository, artifactRepository = LiveArtifactRepository(eventStore, DefaultArtifactReducer()), approvalRepository = approvalRepository, ) private val orchestrator = DefaultSessionOrchestrator( repositories = repositories, engines = OrchestratorEngines( transitionResolver = TransitionFixtures.simpleResolver(), contextPackBuilder = ContextFixtures.simpleBuilder(), inferenceRouter = InferenceFixtures.fixedRouter(), validationPipeline = ValidationPipeline(validators = listOf(cyclePolicyMissingValidator())), approvalEngine = DefaultApprovalEngine(), riskAssessor = DefaultRiskAssessor(), ), retryCoordinator = retryCoordinator, artifactStore = artifactStore, decisionJournalRepository = decisionJournalRepository, ) private val defaultConfig = OrchestrationConfig( retryPolicy = RetryPolicy(maxAttempts = 1, backoffMs = 0), ) @Test fun `workflow runs every non-terminal stage through the runner and completes`(): Unit = runBlocking { val sessionId = SessionId("seam-test-1") val stageA = StageId("A") val stageB = StageId("B") val done = StageId("done") val graph = WorkflowGraph( id = "seam-test", stages = mapOf( stageA to StageConfig(allowedTools = emptySet()), stageB to StageConfig(allowedTools = emptySet()), done to StageConfig(), ), transitions = setOf( TransitionEdge(id = TransitionId("t1"), from = stageA, to = stageB, condition = { true }), TransitionEdge(id = TransitionId("t2"), from = stageB, to = done, condition = { true }), ), start = stageA, ) orchestrator.run(sessionId, graph, defaultConfig) val events = eventStore.read(sessionId) assertNotNull( events.find { it.payload is WorkflowCompletedEvent }, "workflow should complete through the subagent runner seam", ) // Stage A is the start (entered, no transition-to event); B is entered via the runner // after the A->B transition; `done` is a terminal sentinel (no TransitionExecuted). val transitions = events.mapNotNull { it.payload as? TransitionExecutedEvent } assertEquals( listOf(stageB), transitions.map { it.to }, "stage B should be entered via the runner after stage A completes", ) } }