feat(cas): content-addressed artifact store (steps 1–8)
New core:artifacts-store interface + infrastructure/artifacts-cas implementation: segment files + SQLite index, Blake3 hashing, group-commit fsync via flushBefore, recovery tail-scan, manual compactor, and oldest-first disk-cap evictor. Inference events now carry promptArtifactId / responseArtifactId; orchestrators put bytes before emitting. SqliteEventStore wraps its txn in artifactStore.flushBefore so segment data is fsynced before the event commit, making the crash window non-corrupting (TailScanner re-indexes orphan tail records on reopen). Compactor and evictor are mutually exclusive via maintenanceMutex. Step 9 (cloud sync) deferred to a later epic. See docs/reviews/2026-05-18-cas-steps-1-8-review.md for the final review.
This commit is contained in:
@@ -17,7 +17,9 @@ dependencies {
|
||||
testImplementation(project(":core:context"))
|
||||
testFixturesImplementation(project(":core:events"))
|
||||
testFixturesImplementation(project(":core:sessions"))
|
||||
testFixturesImplementation(project(":core:artifacts-store"))
|
||||
testFixturesImplementation(project(":testing:fixtures"))
|
||||
testFixturesImplementation "org.jetbrains.kotlinx:kotlinx-datetime:$kotlinx_datetime_version"
|
||||
testFixturesImplementation "org.junit.jupiter:junit-jupiter:$junit_version"
|
||||
testFixturesImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.correx.testing.contracts.fixtures.artifactstore
|
||||
|
||||
import com.correx.core.artifactstore.ArtifactStore
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.utils.TypeId
|
||||
|
||||
class NoopArtifactStore : ArtifactStore {
|
||||
override suspend fun put(bytes: ByteArray): ArtifactId = TypeId("00".repeat(32))
|
||||
override suspend fun get(id: ArtifactId): ByteArray? = null
|
||||
override suspend fun flushBefore(commit: suspend () -> Unit) { commit() }
|
||||
}
|
||||
+25
-20
@@ -5,6 +5,7 @@ import com.correx.core.events.types.EventId
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.testing.contracts.utils.ConcurrencyRunner
|
||||
import com.correx.testing.fixtures.EventFixtures
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
@@ -13,7 +14,7 @@ abstract class EventStoreContractTest {
|
||||
protected abstract fun store(): EventStore
|
||||
|
||||
@Test
|
||||
fun `appends preserve ordering`() {
|
||||
fun `appends preserve ordering`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
store.append(EventFixtures.newEvent(EventId("e1"), SessionId("s1")))
|
||||
@@ -27,19 +28,19 @@ abstract class EventStoreContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `idempotency prevents duplicates`() {
|
||||
fun `idempotency prevents duplicates`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
val e = EventFixtures.newEvent(EventId("dup"), SessionId("s1"))
|
||||
|
||||
store.append(e)
|
||||
assertThrows<Exception> {
|
||||
store.append(e)
|
||||
runBlocking { store.append(e) }
|
||||
}
|
||||
Assertions.assertEquals(1, store.read(SessionId("s1")).size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sessions are isolated`() {
|
||||
fun `sessions are isolated`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
store.append(EventFixtures.newEvent(EventId("a"), SessionId("s1")))
|
||||
@@ -50,7 +51,7 @@ abstract class EventStoreContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `readFrom respects sequence cursor`() {
|
||||
fun `readFrom respects sequence cursor`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
store.append(EventFixtures.newEvent(EventId("1"), SessionId("s1")))
|
||||
@@ -67,9 +68,11 @@ abstract class EventStoreContractTest {
|
||||
val store = store()
|
||||
|
||||
ConcurrencyRunner.run(20, 100) { t, i ->
|
||||
store.append(
|
||||
EventFixtures.newEvent(EventId("e-$i-$t"), SessionId("s1"))
|
||||
)
|
||||
runBlocking {
|
||||
store.append(
|
||||
EventFixtures.newEvent(EventId("e-$i-$t"), SessionId("s1"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val all = store.read(SessionId("s1"))
|
||||
@@ -80,7 +83,7 @@ abstract class EventStoreContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sequences are strictly increasing per session`() {
|
||||
fun `sequences are strictly increasing per session`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
repeat(100) {
|
||||
@@ -95,7 +98,7 @@ abstract class EventStoreContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sequences are isolated per session`() {
|
||||
fun `sequences are isolated per session`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
repeat(50) {
|
||||
@@ -111,7 +114,7 @@ abstract class EventStoreContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `appendAll is atomic per session`() {
|
||||
fun `appendAll is atomic per session`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
val batch = (1..50).map {
|
||||
@@ -133,12 +136,14 @@ abstract class EventStoreContractTest {
|
||||
val store = store()
|
||||
|
||||
ConcurrencyRunner.run(10, 50) { t, i ->
|
||||
if (i % 2 == 0) {
|
||||
store.append(EventFixtures.newEvent(EventId("a-$t-$i"), SessionId("s1")))
|
||||
} else {
|
||||
store.appendAll(
|
||||
listOf(EventFixtures.newEvent(EventId("b-$t-$i"), SessionId("s1")))
|
||||
)
|
||||
runBlocking {
|
||||
if (i % 2 == 0) {
|
||||
store.append(EventFixtures.newEvent(EventId("a-$t-$i"), SessionId("s1")))
|
||||
} else {
|
||||
store.appendAll(
|
||||
listOf(EventFixtures.newEvent(EventId("b-$t-$i"), SessionId("s1")))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +155,7 @@ abstract class EventStoreContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read returns stable snapshot`() {
|
||||
fun `read returns stable snapshot`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
repeat(100) {
|
||||
@@ -164,7 +169,7 @@ abstract class EventStoreContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `readFrom behaves like streaming cursor`() {
|
||||
fun `readFrom behaves like streaming cursor`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
repeat(10) {
|
||||
@@ -177,4 +182,4 @@ abstract class EventStoreContractTest {
|
||||
Assertions.assertTrue(first.all { it.sequence > 3 })
|
||||
Assertions.assertTrue(second.all { it.sequence > 7 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ import com.correx.core.events.types.EventId
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.sessions.projections.replay.EventReplayer
|
||||
import com.correx.testing.fixtures.EventFixtures.newEvent
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@@ -17,7 +18,7 @@ abstract class ReplayContractTest<S> {
|
||||
): EventReplayer<S>
|
||||
|
||||
@Test
|
||||
fun `rebuild is deterministic`() {
|
||||
fun `rebuild is deterministic`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
repeat(50) {
|
||||
|
||||
+3
-2
@@ -6,6 +6,7 @@ import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.sessions.SessionCounterState
|
||||
import com.correx.core.sessions.projections.replay.EventReplayer
|
||||
import com.correx.testing.fixtures.EventFixtures.newEvent
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@@ -18,7 +19,7 @@ abstract class SessionReplayContractTest {
|
||||
): EventReplayer<SessionCounterState>
|
||||
|
||||
@Test
|
||||
fun `rebuild is deterministic`() {
|
||||
fun `rebuild is deterministic`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
repeat(50) {
|
||||
@@ -34,7 +35,7 @@ abstract class SessionReplayContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rebuild reflects complete stream`() {
|
||||
fun `rebuild reflects complete stream`(): Unit = runBlocking {
|
||||
val store = store()
|
||||
|
||||
repeat(42) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.correx.core.sessions.DefaultSessionReducer
|
||||
import com.correx.core.sessions.SessionProjector
|
||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
||||
import com.correx.infrastructure.persistence.InMemoryEventStore
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.Clock
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -23,7 +24,7 @@ class SessionReplayDeterminismTest {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `same events produce same state`() {
|
||||
fun `same events produce same state`(): Unit = runBlocking {
|
||||
val sessionId = SessionId("s1")
|
||||
|
||||
val store1 = InMemoryEventStore()
|
||||
|
||||
@@ -4,12 +4,14 @@ import com.correx.core.context.model.ContextPack
|
||||
import com.correx.core.events.events.EventMetadata
|
||||
import com.correx.core.events.events.InferenceCompletedEvent
|
||||
import com.correx.core.events.events.NewEvent
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.events.types.ContextPackId
|
||||
import com.correx.core.events.types.EventId
|
||||
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.utils.TypeId
|
||||
import com.correx.core.inference.FinishReason
|
||||
import com.correx.core.inference.GenerationConfig
|
||||
import com.correx.core.inference.InferenceProvider
|
||||
@@ -73,6 +75,9 @@ object InferenceFixtures {
|
||||
)
|
||||
}
|
||||
|
||||
private const val PLACEHOLDER_HEX_BYTES = 32
|
||||
private val PLACEHOLDER_ARTIFACT_ID: ArtifactId = TypeId("00".repeat(PLACEHOLDER_HEX_BYTES))
|
||||
|
||||
fun inferenceCompleted(
|
||||
requestId: InferenceRequestId,
|
||||
sessionId: SessionId,
|
||||
@@ -80,6 +85,7 @@ object InferenceFixtures {
|
||||
providerId: ProviderId,
|
||||
tokensUsed: TokenUsage,
|
||||
latencyMs: Long,
|
||||
responseArtifactId: ArtifactId = PLACEHOLDER_ARTIFACT_ID,
|
||||
): InferenceCompletedEvent {
|
||||
return InferenceCompletedEvent(
|
||||
requestId = requestId,
|
||||
@@ -88,6 +94,7 @@ object InferenceFixtures {
|
||||
providerId = providerId,
|
||||
tokensUsed = tokensUsed,
|
||||
latencyMs = latencyMs,
|
||||
responseArtifactId = responseArtifactId,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -127,6 +134,7 @@ object InferenceFixtures {
|
||||
providerId: ProviderId,
|
||||
tokensUsed: TokenUsage,
|
||||
latencyMs: Long,
|
||||
responseArtifactId: ArtifactId = PLACEHOLDER_ARTIFACT_ID,
|
||||
): NewEvent {
|
||||
return NewEvent(
|
||||
metadata = EventMetadata(
|
||||
@@ -144,6 +152,7 @@ object InferenceFixtures {
|
||||
providerId = providerId,
|
||||
tokensUsed = tokensUsed,
|
||||
latencyMs = latencyMs,
|
||||
responseArtifactId = responseArtifactId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ dependencies {
|
||||
testImplementation(project(":core:risk"))
|
||||
testImplementation(project(":infrastructure:persistence"))
|
||||
testImplementation(project(":core:artifacts"))
|
||||
testImplementation(project(":core:artifacts-store"))
|
||||
testImplementation(project(":testing:fixtures"))
|
||||
testImplementation(project(":testing:kernel"))
|
||||
testImplementation(project(":testing:contracts"))
|
||||
testImplementation(testFixtures(project(":testing:contracts")))
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
|
||||
}
|
||||
@@ -43,6 +43,12 @@ import com.correx.testing.fixtures.context.ContextFixtures
|
||||
import com.correx.testing.fixtures.cyclePolicyMissingValidator
|
||||
import com.correx.testing.fixtures.inference.MockInferenceProvider
|
||||
import com.correx.testing.fixtures.transitions.TransitionFixtures
|
||||
import com.correx.core.artifactstore.ArtifactStore
|
||||
import com.correx.core.events.events.InferenceCompletedEvent
|
||||
import com.correx.core.events.events.InferenceStartedEvent
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.utils.TypeId
|
||||
import com.correx.testing.contracts.fixtures.artifactstore.NoopArtifactStore
|
||||
import com.correx.testing.fixtures.transitions.TransitionFixtures.threeStageGraph
|
||||
import com.correx.testing.kernel.MockSessionEventReplayer
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -78,6 +84,7 @@ class SessionOrchestratorIntegrationTest {
|
||||
},
|
||||
)
|
||||
private val riskAssessor = DefaultRiskAssessor()
|
||||
private val artifactStore = NoopArtifactStore()
|
||||
|
||||
private val repositories = OrchestratorRepositories(
|
||||
eventStore = eventStore,
|
||||
@@ -100,6 +107,7 @@ class SessionOrchestratorIntegrationTest {
|
||||
repositories = repositories,
|
||||
engines = engines,
|
||||
retryCoordinator = retryCoordinator,
|
||||
artifactStore = artifactStore,
|
||||
)
|
||||
|
||||
@Test
|
||||
@@ -112,7 +120,6 @@ class SessionOrchestratorIntegrationTest {
|
||||
orchestrator.run(sessionId, graph, config)
|
||||
|
||||
val events = eventStore.read(sessionId)
|
||||
assertEquals(2, events.size)
|
||||
|
||||
val workflowStarted = events.find { it.payload is WorkflowStartedEvent }
|
||||
assertNotNull(workflowStarted)
|
||||
@@ -147,6 +154,7 @@ class SessionOrchestratorIntegrationTest {
|
||||
},
|
||||
),
|
||||
retryCoordinator = retryCoordinator,
|
||||
artifactStore = artifactStore,
|
||||
)
|
||||
failingOrchestrator.run(sessionId, graph, config)
|
||||
|
||||
@@ -173,6 +181,7 @@ class SessionOrchestratorIntegrationTest {
|
||||
repositories = repositories,
|
||||
engines = engines.copy(validationPipeline = approvingPipeline),
|
||||
retryCoordinator = retryCoordinator,
|
||||
artifactStore = artifactStore,
|
||||
)
|
||||
|
||||
val runJob = launch { orchestrator.run(sessionId, graph, config) }
|
||||
@@ -197,7 +206,20 @@ class SessionOrchestratorIntegrationTest {
|
||||
fun `workflow resumes after approval`(): Unit = runBlocking {
|
||||
val sessionId = SessionId("s4")
|
||||
val config = OrchestrationConfig(retryPolicy = RetryPolicy(maxAttempts = 3, backoffMs = 0))
|
||||
val graph = threeStageGraph()
|
||||
// Single-stage graph: enterStage(A) triggers exactly one approval; after resume,
|
||||
// the always-true transition moves to the terminal "done" and the workflow completes.
|
||||
val graph = WorkflowGraph(
|
||||
stages = mapOf(StageId("A") to StageConfig()),
|
||||
transitions = setOf(
|
||||
com.correx.core.transitions.graph.TransitionEdge(
|
||||
id = com.correx.core.events.types.TransitionId("t1"),
|
||||
from = StageId("A"),
|
||||
to = StageId("done"),
|
||||
condition = { true },
|
||||
),
|
||||
),
|
||||
start = StageId("A"),
|
||||
)
|
||||
val approvingPipeline = ValidationPipeline(
|
||||
validators = listOf(cyclePolicyMissingValidator()),
|
||||
approvalTrigger = ApprovalTrigger(),
|
||||
@@ -206,6 +228,7 @@ class SessionOrchestratorIntegrationTest {
|
||||
repositories = repositories,
|
||||
engines = engines.copy(validationPipeline = approvingPipeline),
|
||||
retryCoordinator = retryCoordinator,
|
||||
artifactStore = artifactStore,
|
||||
)
|
||||
|
||||
val runJob = launch { approvalOrchestrator.run(sessionId, graph, config) }
|
||||
@@ -257,6 +280,7 @@ class SessionOrchestratorIntegrationTest {
|
||||
repositories = repositories,
|
||||
engines = engines,
|
||||
retryCoordinator = retryCoordinator,
|
||||
artifactStore = artifactStore,
|
||||
)
|
||||
|
||||
val sessionId = SessionId("s6")
|
||||
@@ -269,6 +293,30 @@ class SessionOrchestratorIntegrationTest {
|
||||
assertNotNull(failed.reason)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `artifactStore put is called for prompt and response and ids appear on inference events`(): Unit = runBlocking {
|
||||
val recordingStore = RecordingArtifactStore()
|
||||
val sessionId = SessionId("s-artifact")
|
||||
val config = OrchestrationConfig(retryPolicy = RetryPolicy(maxAttempts = 1, backoffMs = 0))
|
||||
val recordingOrchestrator = DefaultSessionOrchestrator(
|
||||
repositories = repositories,
|
||||
engines = engines,
|
||||
retryCoordinator = retryCoordinator,
|
||||
artifactStore = recordingStore,
|
||||
)
|
||||
|
||||
recordingOrchestrator.run(sessionId, graph, config)
|
||||
|
||||
assertTrue(recordingStore.puts.size >= 2, "Expected at least 2 puts, got ${recordingStore.puts.size}")
|
||||
|
||||
val events = eventStore.read(sessionId)
|
||||
val started = events.mapNotNull { it.payload as? InferenceStartedEvent }.first()
|
||||
val completed = events.mapNotNull { it.payload as? InferenceCompletedEvent }.first()
|
||||
|
||||
assertEquals(recordingStore.idForIndex(0), started.promptArtifactId)
|
||||
assertEquals(recordingStore.idForIndex(1), completed.responseArtifactId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `run throws IllegalArgumentException when transition targets undeclared stage`(): Unit = runBlocking {
|
||||
val a = StageId("A")
|
||||
@@ -327,3 +375,21 @@ class SessionOrchestratorIntegrationTest {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class RecordingArtifactStore : ArtifactStore {
|
||||
val puts: MutableList<ByteArray> = mutableListOf()
|
||||
private val byId: MutableMap<ArtifactId, ByteArray> = mutableMapOf()
|
||||
|
||||
fun idForIndex(i: Int): ArtifactId = TypeId("0".repeat(63) + i.toString(16))
|
||||
|
||||
override suspend fun put(bytes: ByteArray): ArtifactId {
|
||||
val id = idForIndex(puts.size)
|
||||
puts.add(bytes)
|
||||
byId[id] = bytes
|
||||
return id
|
||||
}
|
||||
|
||||
override suspend fun get(id: ArtifactId): ByteArray? = byId[id]
|
||||
|
||||
override suspend fun flushBefore(commit: suspend () -> Unit) { commit() }
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.correx.core.inference.InferenceRecord
|
||||
import com.correx.core.inference.InferenceState
|
||||
import com.correx.core.inference.InferenceStatus
|
||||
import com.correx.core.inference.TokenUsage
|
||||
import com.correx.core.utils.TypeId
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
@@ -38,6 +39,7 @@ class InferenceProjectorTest {
|
||||
sessionId = SessionId("sess1"),
|
||||
stageId = StageId("stageA"),
|
||||
providerId = ProviderId("providerX"),
|
||||
promptArtifactId = TypeId("00".repeat(32)),
|
||||
),
|
||||
)
|
||||
val newState = projector.apply(initialState, event)
|
||||
@@ -67,6 +69,7 @@ class InferenceProjectorTest {
|
||||
providerId = ProviderId("providerX"),
|
||||
tokensUsed = TokenUsage(50, 50),
|
||||
latencyMs = 500L,
|
||||
responseArtifactId = TypeId("00".repeat(32)),
|
||||
),
|
||||
)
|
||||
val newState = projector.apply(initialState, event)
|
||||
@@ -88,6 +91,7 @@ class InferenceProjectorTest {
|
||||
sessionId = SessionId("sess1"),
|
||||
stageId = StageId("stageA"),
|
||||
providerId = ProviderId("providerX"),
|
||||
promptArtifactId = TypeId("00".repeat(32)),
|
||||
),
|
||||
)
|
||||
var currentState = projector.apply(initialState, startEvent)
|
||||
@@ -102,6 +106,7 @@ class InferenceProjectorTest {
|
||||
providerId = ProviderId("providerX"),
|
||||
tokensUsed = TokenUsage(100, 100),
|
||||
latencyMs = 1000L,
|
||||
responseArtifactId = TypeId("00".repeat(32)),
|
||||
),
|
||||
)
|
||||
currentState = projector.apply(currentState, completeEvent)
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.correx.core.inference.InferenceReducer
|
||||
import com.correx.core.inference.InferenceState
|
||||
import com.correx.core.inference.InferenceStatus
|
||||
import com.correx.core.inference.TokenUsage
|
||||
import com.correx.core.utils.TypeId
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
@@ -36,6 +37,7 @@ class InferenceReducerTest {
|
||||
sessionId = SessionId("sess1"),
|
||||
stageId = StageId("stageA"),
|
||||
providerId = ProviderId("providerX"),
|
||||
promptArtifactId = TypeId("00".repeat(32)),
|
||||
),
|
||||
)
|
||||
val newState = reducer.reduce(initialState, event)
|
||||
@@ -65,6 +67,7 @@ class InferenceReducerTest {
|
||||
providerId = ProviderId("providerX"),
|
||||
tokensUsed = TokenUsage(50, 50),
|
||||
latencyMs = 500L,
|
||||
responseArtifactId = TypeId("00".repeat(32)),
|
||||
),
|
||||
)
|
||||
val newState = reducer.reduce(initialState, event)
|
||||
@@ -176,6 +179,7 @@ class InferenceReducerTest {
|
||||
providerId = ProviderId("providerX"),
|
||||
tokensUsed = TokenUsage(50, 50),
|
||||
latencyMs = 500L,
|
||||
responseArtifactId = TypeId("00".repeat(32)),
|
||||
),
|
||||
)
|
||||
val newState = reducer.reduce(initialState, event)
|
||||
|
||||
@@ -23,7 +23,7 @@ class SessionReplayTest {
|
||||
private val replayer = DefaultEventReplayer(store, projector)
|
||||
|
||||
@Test
|
||||
fun `rebuild session from event stream`() {
|
||||
fun `rebuild session from event stream`(): Unit = kotlinx.coroutines.runBlocking {
|
||||
val sessionId = SessionId("s1")
|
||||
|
||||
val metadataToPayload = mapOf<EventMetadata, EventPayload>(
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.correx.core.sessions.SessionStatus
|
||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
||||
import com.correx.infrastructure.persistence.InMemoryEventStore
|
||||
import com.correx.testing.fixtures.EventFixtures.newEvent
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@@ -22,7 +23,7 @@ class TransitionReplayIntegrationTest {
|
||||
private val sessionId = SessionId("session-1")
|
||||
|
||||
@Test
|
||||
fun `workflow replay reconstructs COMPLETED session`() {
|
||||
fun `workflow replay reconstructs COMPLETED session`(): Unit = runBlocking {
|
||||
|
||||
val store = InMemoryEventStore()
|
||||
|
||||
@@ -85,7 +86,7 @@ class TransitionReplayIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `workflow replay reconstructs FAILED session`() {
|
||||
fun `workflow replay reconstructs FAILED session`(): Unit = runBlocking {
|
||||
|
||||
val store = InMemoryEventStore()
|
||||
|
||||
@@ -144,7 +145,7 @@ class TransitionReplayIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `replay is deterministic for identical event stream`() {
|
||||
fun `replay is deterministic for identical event stream`(): Unit = runBlocking {
|
||||
val events = listOf(
|
||||
newEvent(
|
||||
sessionId = sessionId,
|
||||
|
||||
Reference in New Issue
Block a user