epic-12: after epic audit and init commit

This commit is contained in:
2026-05-16 11:42:00 +04:00
commit c77277af0b
461 changed files with 28958 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
id 'org.jetbrains.kotlin.plugin.serialization'
}
dependencies {
testImplementation(project(":core:transitions"))
testImplementation(project(":core:events"))
testImplementation(project(":core:inference"))
}
@@ -0,0 +1,225 @@
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.transitions.evaluation.EvaluationContext
import com.correx.core.transitions.evaluation.TransitionConditionEvaluator
import com.correx.core.transitions.graph.StageConfig
import com.correx.core.transitions.graph.TransitionCondition
import com.correx.core.transitions.graph.TransitionEdge
import com.correx.core.transitions.graph.WorkflowGraph
import com.correx.core.transitions.resolution.DefaultTransitionResolver
import com.correx.core.transitions.resolution.TransitionDecision
import com.correx.core.transitions.resolution.TransitionResolver
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertInstanceOf
class DefaultTransitionResolverTest {
private val evaluator = object : TransitionConditionEvaluator {
override fun evaluate(
condition: TransitionCondition,
context: EvaluationContext
): Boolean {
return condition.evaluate(context)
}
}
private val resolver: TransitionResolver = DefaultTransitionResolver(evaluator)
@Test
fun `returns NoMatch when no outgoing transitions exist for current stage`() {
val graph = graph(
start = "stage-a",
transitions = setOf(
edge(
id = "t1",
from = "stage-a",
to = "stage-b",
condition = alwaysTrue()
)
),
stages = setOf("stage-a", "stage-b")
)
val result = resolver.resolve(
graph = graph,
context = context(currentStage = "stage-b")
)
assertEquals(
TransitionDecision.NoMatch,
result
)
}
@Test
fun `returns Stay when all conditions evaluate to false`() {
val graph = graph(
start = "stage-a",
transitions = setOf(
edge(
id = "t1",
from = "stage-a",
to = "stage-b",
condition = alwaysFalse()
)
)
)
val result = resolver.resolve(
graph = graph,
context = context(currentStage = "stage-a")
)
assertEquals(
TransitionDecision.Stay,
result
)
}
@Test
fun `returns matching transition when condition evaluates to true`() {
val graph = graph(
start = "stage-a",
transitions = setOf(
edge(
id = "t1",
from = "stage-a",
to = "stage-b",
condition = alwaysTrue()
)
)
)
val result = resolver.resolve(
graph = graph,
context = context(currentStage = "stage-a")
)
val move = assertInstanceOf<TransitionDecision.Move>(result)
assertEquals(
TransitionId("t1"),
move.transitionId
)
assertEquals(
StageId("stage-b"),
move.to
)
}
@Test
fun `first matching transition wins deterministically`() {
val graph = graph(
start = "stage-a",
transitions = setOf(
edge(
id = "t2",
from = "stage-a",
to = "stage-c",
condition = alwaysTrue()
),
edge(
id = "t1",
from = "stage-a",
to = "stage-b",
condition = alwaysTrue()
)
)
)
val result = resolver.resolve(
graph = graph,
context = context(currentStage = "stage-a")
)
val move = assertInstanceOf<TransitionDecision.Move>(result)
assertEquals(
TransitionId("t1"),
move.transitionId
)
assertEquals(
StageId("stage-b"),
move.to
)
}
@Test
fun `only transitions from current stage are evaluated`() {
val graph = graph(
start = "stage-a",
transitions = setOf(
edge(
id = "t1",
from = "stage-x",
to = "stage-y",
condition = alwaysTrue()
),
edge(
id = "t2",
from = "stage-a",
to = "stage-b",
condition = alwaysTrue()
)
)
)
val result = resolver.resolve(
graph = graph,
context = context(currentStage = "stage-a")
)
val move = assertInstanceOf<TransitionDecision.Move>(result)
assertEquals(
TransitionId("t2"),
move.transitionId
)
}
private fun edge(
id: String,
from: String,
to: String,
condition: TransitionCondition
): TransitionEdge {
return TransitionEdge(
id = TransitionId(id),
from = StageId(from),
to = StageId(to),
condition = condition
)
}
private fun graph(
start: String,
transitions: Set<TransitionEdge>,
stages: Set<String> = transitions.flatMap { listOf(it.from.value, it.to.value) }.toSet()
) = WorkflowGraph(
start = StageId(start),
stages = stages.associate { StageId(it) to StageConfig() },
transitions = transitions
)
private fun context(
currentSession: String = "s1",
currentStage: String,
): EvaluationContext {
return EvaluationContext(
sessionId = SessionId(currentSession),
currentStage = StageId(currentStage),
variables = emptyMap(),
)
}
private fun alwaysTrue() =
TransitionCondition { true }
@Suppress("UnusedPrivateMember")
private fun alwaysFalse() =
TransitionCondition { false }
}
@@ -0,0 +1,72 @@
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StageFailedEvent
import com.correx.core.events.events.StageStartedEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.serialization.JsonEventSerializer
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.TransitionId
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class TransitionEventSerializationTest {
@Test
fun `StageStartedEvent serializes and deserializes`() {
val event = StageStartedEvent(
sessionId = SessionId("session-1"),
stageId = StageId("stage-a"),
transitionId = TransitionId("transition-a")
)
assertRoundTrip(event)
}
@Test
fun `StageCompletedEvent serializes and deserializes`() {
val event = StageCompletedEvent(
sessionId = SessionId("session-1"),
stageId = StageId("stage-a"),
transitionId = TransitionId("transition-a")
)
assertRoundTrip(event)
}
@Test
fun `StageFailedEvent serializes and deserializes`() {
val event = StageFailedEvent(
sessionId = SessionId("session-1"),
stageId = StageId("stage-a"),
transitionId = TransitionId("transition-a"),
reason = "boom"
)
assertRoundTrip(event)
}
@Test
fun `TransitionExecutedEvent serializes and deserializes`() {
val event = TransitionExecutedEvent(
sessionId = SessionId("session-1"),
from = StageId("stage-a"),
to = StageId("stage-b"),
transitionId = TransitionId("transition-a")
)
assertRoundTrip(event)
}
private inline fun <reified T : EventPayload> assertRoundTrip(
event: T
) {
val json = JsonEventSerializer()
val serialized = json.serialize(event)
val deserialized =
json.deserialize(serialized)
assertEquals(event, deserialized)
}
}