import com.correx.core.events.types.StageId import com.correx.core.events.types.TransitionId 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.transitions.resolution.TransitionOrdering import com.correx.core.validation.approval.ApprovalTrigger import com.correx.core.validation.graph.GraphValidator import com.correx.core.validation.model.ValidationContext import com.correx.core.validation.pipeline.ValidationOutcome import com.correx.core.validation.pipeline.ValidationPipeline import com.correx.core.validation.semantic.SemanticValidator import com.correx.core.validation.semantic.rules.CyclePolicyBindingRule import com.correx.core.validation.transition.TransitionValidator import com.correx.testing.fixtures.CycleFixtures import com.correx.testing.fixtures.WorkflowFixtures import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Test class ValidationPipelineIntegrationTest { private val pipeline = ValidationPipeline( validators = listOf( GraphValidator(), TransitionValidator(TransitionOrdering.comparator), SemanticValidator( rules = listOf( CyclePolicyBindingRule(requirePolicyForCycles = true), ), ), ), approvalTrigger = ApprovalTrigger() ) @Test fun `full validation pipeline returns NeedsApproval when cycles are unbound`(): Unit = runBlocking { val graph = WorkflowFixtures.simpleGraph() val cycles = listOf(CycleFixtures.simpleCycle()) val context = ValidationContext( graph = graph, detectedCycles = cycles, cyclePolicies = emptySet(), ) val outcome = pipeline.validate(context) assertInstanceOf(ValidationOutcome.NeedsApproval::class.java, outcome) } @Test fun `rejected outcome retryable is false - set by validator, not orchestrator`(): Unit = runBlocking { // A dangling transition triggers GraphValidator ERROR → Rejected(retryable=false). // Ownership rule: the validator sets retryable; the orchestrator must only read it. val a = StageId("A") val ghost = StageId("GHOST") val graphWithDanglingTransition = WorkflowGraph( id = "workflow-test", stages = mapOf(a to StageConfig()), transitions = setOf(TransitionEdge(TransitionId("t1"), from = a, to = ghost) { true }), start = a, ) val pipeline = ValidationPipeline(validators = listOf(GraphValidator()), approvalTrigger = null) val outcome = pipeline.validate(ValidationContext(graph = graphWithDanglingTransition)) assertInstanceOf(ValidationOutcome.Rejected::class.java, outcome) assertFalse((outcome as ValidationOutcome.Rejected).retryable) } }