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
+17
View File
@@ -0,0 +1,17 @@
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
id 'org.jetbrains.kotlin.plugin.serialization'
}
dependencies {
implementation(project(":core:events"))
implementation(project(":core:kernel"))
implementation(project(":core:sessions"))
implementation(project(":core:inference"))
implementation project(':testing:fixtures')
testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test"
testImplementation project(':infrastructure:persistence')
}
@@ -0,0 +1,42 @@
package com.correx.testing.kernel
import com.correx.core.events.orchestration.OrchestrationState
import com.correx.core.events.orchestration.OrchestrationStatus
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.sessions.SessionState
import com.correx.core.sessions.SessionStatus
import com.correx.core.sessions.projections.replay.EventReplayer
import kotlinx.datetime.Clock
class MockOrchestrationEventReplayer : EventReplayer<OrchestrationState> {
private val states = mutableMapOf<SessionId, OrchestrationState>()
fun setSessionState(sessionId: SessionId, state: OrchestrationState) {
states[sessionId] = state
}
override fun rebuild(sessionId: SessionId): OrchestrationState {
return states[sessionId] ?: OrchestrationState(
status = OrchestrationStatus.RUNNING,
currentStageId = StageId("stage-1"),
)
}
}
class MockSessionEventReplayer : EventReplayer<SessionState> {
private val states = mutableMapOf<SessionId, SessionState>()
fun setSessionState(sessionId: SessionId, state: SessionState) {
states[sessionId] = state
}
override fun rebuild(sessionId: SessionId): SessionState {
return states[sessionId] ?: SessionState(
status = SessionStatus.ACTIVE,
createdAt = Clock.System.now(),
updatedAt = Clock.System.now(),
invalidTransitions = 0,
)
}
}
@@ -0,0 +1,168 @@
import com.correx.core.events.events.RetryAttemptedEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.execution.RetryPolicy
import com.correx.core.kernel.retry.DefaultRetryCoordinator
import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.*
import kotlin.test.assertEquals
class DefaultRetryCoordinatorTest {
private lateinit var eventStore: EventStore
private lateinit var retryCoordinator: DefaultRetryCoordinator
@BeforeEach
fun setup() {
eventStore = InMemoryEventStore()
retryCoordinator = DefaultRetryCoordinator(eventStore)
}
@Test
fun `should return true when currentAttempt is 0 and maxAttempts is 3`() = runTest {
val sessionId = SessionId(UUID.randomUUID().toString())
val stageId = StageId("stage1")
val policy = RetryPolicy(maxAttempts = 3, backoffMs = 0L)
val result = retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 0,
policy = policy,
failureReason = "test failure",
)
Assertions.assertTrue(result)
val events = eventStore.read(sessionId)
Assertions.assertEquals(1, events.size)
val event = events.first().payload as RetryAttemptedEvent
Assertions.assertEquals(1, event.attemptNumber)
Assertions.assertEquals(stageId, event.stageId)
Assertions.assertEquals("test failure", event.failureReason)
}
@Test
fun `should return true when currentAttempt is 2 and maxAttempts is 3`() = runTest {
val sessionId = SessionId(UUID.randomUUID().toString())
val stageId = StageId("stage1")
val policy = RetryPolicy(maxAttempts = 3, backoffMs = 0L)
// First retry
retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 0,
policy = policy,
failureReason = "test failure",
)
// Second retry (currentAttempt = 2, which is the third attempt)
val result = retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 2,
policy = policy,
failureReason = "test failure",
)
Assertions.assertTrue(result)
val events = eventStore.read(sessionId)
Assertions.assertEquals(2, events.size)
val lastEvent = events.last().payload as RetryAttemptedEvent
Assertions.assertEquals(3, lastEvent.attemptNumber)
Assertions.assertEquals("test failure", lastEvent.failureReason)
}
@Test
fun `should return false when currentAttempt equals maxAttempts`() = runTest {
val sessionId = SessionId(UUID.randomUUID().toString())
val stageId = StageId("stage1")
val policy = RetryPolicy(maxAttempts = 3, backoffMs = 0L)
// First two retries
retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 0,
policy = policy,
failureReason = "test failure",
)
retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 1,
policy = policy,
failureReason = "test failure",
)
// currentAttempt = 3 equals maxAttempts = 3, should not retry
val result = retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 3,
policy = policy,
failureReason = "test failure",
)
Assertions.assertFalse(result)
val events = eventStore.read(sessionId)
Assertions.assertEquals(2, events.size)
}
@Test
fun `should return false when currentAttempt is greater than maxAttempts`() = runTest {
val sessionId = SessionId(UUID.randomUUID().toString())
val stageId = StageId("stage1")
val policy = RetryPolicy(maxAttempts = 3, backoffMs = 0L)
// First retry
retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 0,
policy = policy,
failureReason = "test failure",
)
// currentAttempt = 5 > maxAttempts = 3, should not retry
val result = retryCoordinator.shouldRetry(
sessionId = sessionId,
stageId = stageId,
currentAttempt = 5,
policy = policy,
failureReason = "test failure",
)
Assertions.assertFalse(result)
val events = eventStore.read(sessionId)
Assertions.assertEquals(1, events.size)
}
@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun `should delay by backoffMs before retrying`() = runTest {
val policy = RetryPolicy(maxAttempts = 3, backoffMs = 1000L)
retryCoordinator.shouldRetry(
sessionId = SessionId(UUID.randomUUID().toString()),
stageId = StageId("stage1"),
currentAttempt = 0,
policy = policy,
failureReason = "test failure",
)
// virtual time should have advanced by backoffMs
assertEquals(1000L, currentTime)
}
}
@@ -0,0 +1,161 @@
import com.correx.core.events.types.InferenceRequestId
import com.correx.core.events.types.ProviderId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.inference.FinishReason
import com.correx.core.inference.ProviderHealth
import com.correx.core.inference.TokenUsage
import com.correx.core.kernel.replay.ReplayInferenceProvider
import com.correx.core.kernel.retry.exception.ReplayArtifactMissingException
import com.correx.infrastructure.persistence.InMemoryEventStore
import com.correx.testing.fixtures.InferenceFixtures
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
class ReplayInferenceProviderTest {
private lateinit var eventStore: InMemoryEventStore
private lateinit var replayProvider: ReplayInferenceProvider
@BeforeEach
fun setup() {
eventStore = InMemoryEventStore()
replayProvider = ReplayInferenceProvider(eventStore)
}
@Test
fun `response carries correct tokensUsed and latencyMs when matching stageId`() = runTest {
val sessionId = SessionId("s1")
val stageId = StageId("stage1")
val requestId = InferenceRequestId("req1")
val providerId = ProviderId("test-provider")
val tokensUsed = TokenUsage(promptTokens = 100, completionTokens = 200)
val latencyMs = 500L
// Record an inference completed event
eventStore.append(
InferenceFixtures.newInferenceCompletedEvent(
requestId = requestId,
sessionId = sessionId,
stageId = stageId,
providerId = providerId,
tokensUsed = tokensUsed,
latencyMs = latencyMs,
),
)
val request = InferenceFixtures.inferenceRequest(
sessionId = sessionId,
stageId = stageId,
requestId = requestId,
)
val response = replayProvider.infer(request)
Assertions.assertEquals(request.requestId, response.requestId)
Assertions.assertEquals(tokensUsed, response.tokensUsed)
Assertions.assertEquals(latencyMs, response.latencyMs)
Assertions.assertEquals("", response.text)
Assertions.assertEquals(FinishReason.Stop, response.finishReason)
}
@Test
fun `ReplayArtifactMissingException thrown when event for different stageId`(): Unit = runBlocking {
val sessionId = SessionId("s1")
val stageId1 = StageId("stage1")
val stageId2 = StageId("stage2")
val requestId = InferenceRequestId("req1")
val providerId = ProviderId("test-provider")
// Record event for stage1
eventStore.append(
InferenceFixtures.newInferenceCompletedEvent(
requestId = requestId,
sessionId = sessionId,
stageId = stageId1,
providerId = providerId,
tokensUsed = TokenUsage(promptTokens = 100, completionTokens = 200),
latencyMs = 500L,
),
)
val request = InferenceFixtures.inferenceRequest(
sessionId = sessionId,
stageId = stageId2, // Different stage
requestId = requestId,
)
assertThrows<ReplayArtifactMissingException> {
replayProvider.infer(request)
}
}
@Test
fun `ReplayArtifactMissingException thrown when event store is empty`(): Unit = runBlocking {
val sessionId = SessionId("s1")
val stageId = StageId("stage1")
val requestId = InferenceRequestId("req1")
val request = InferenceFixtures.inferenceRequest(
sessionId = sessionId,
stageId = stageId,
requestId = requestId,
)
assertThrows<ReplayArtifactMissingException> {
replayProvider.infer(request)
}
}
@Test
fun `text is empty string by design`() = runTest {
val sessionId = SessionId("s1")
val stageId = StageId("stage1")
val requestId = InferenceRequestId("req1")
val providerId = ProviderId("test-provider")
eventStore.append(
InferenceFixtures.newInferenceCompletedEvent(
requestId = requestId,
sessionId = sessionId,
stageId = stageId,
providerId = providerId,
tokensUsed = TokenUsage(promptTokens = 100, completionTokens = 50),
latencyMs = 300L,
),
)
val request = InferenceFixtures.inferenceRequest(
sessionId = sessionId,
stageId = stageId,
requestId = requestId,
)
val response = replayProvider.infer(request)
Assertions.assertEquals("", response.text)
}
@Test
fun `capabilities returns empty set`() {
val capabilities = replayProvider.capabilities()
Assertions.assertTrue(capabilities.isEmpty())
}
@Test
fun `healthCheck returns Healthy`() = runTest {
val health = replayProvider.healthCheck()
Assertions.assertEquals(ProviderHealth.Healthy, health)
}
@Test
fun `id is ReplayProvider`() {
Assertions.assertEquals(ProviderId("replay-provider"), replayProvider.id)
}
}