fix: complete all P2 audit findings — dead code, NPE guard, hygiene
P2-1: Guard SessionId NPE with safe-call in SessionsReducer
P2-2: Remove 16 dead event classes + reducer branches; replace with live orchestration events in tests
P2-3: Standardize ChatInput divergences — guard CancelSession, single-arg ProtocolError
P2-4: Remove dead TuiToolRecord.diff and ToolDisplayStatus.REQUESTED
P2-5: Remove dead streamLive from SessionEventBridge
P2-6: Extract SessionsReducerContext data class for 6+ param method
P2-7: Replace StageId("none") sentinel with TypeId.NONE
P2-9: Add TypeId.random() factory on all type-alias IDs
P2-10: Add KDoc on schemaVersion documenting reserved-for-migration
P2-8: Verified RouterReducer string-template already correct (TypeId value class toString)
This commit is contained in:
@@ -244,12 +244,4 @@ class SessionEventBridge(
|
|||||||
return orderedIds.mapNotNull { byInvocation[it] }
|
return orderedIds.mapNotNull { byInvocation[it] }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun streamLive(sessionId: SessionId) {
|
|
||||||
var sessionSequence = 0L
|
|
||||||
eventStore.subscribe(sessionId).collect { event ->
|
|
||||||
sessionSequence++
|
|
||||||
domainEventToServerMessage(event, artifactStore, sessionSequence = sessionSequence)
|
|
||||||
?.let { send(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,11 +88,7 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
log.warn("decode error: {}", it.message)
|
log.warn("decode error: {}", it.message)
|
||||||
val error = ServerMessage.ProtocolError(
|
val error = ServerMessage.ProtocolError("Unknown message: ${it.message}")
|
||||||
message = "Unknown message: ${it.message}",
|
|
||||||
sequence = null,
|
|
||||||
sessionSequence = null,
|
|
||||||
)
|
|
||||||
session.send(Frame.Text(ProtocolSerializer.encodeServerMessage(error)))
|
session.send(Frame.Text(ProtocolSerializer.encodeServerMessage(error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +140,10 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
is ClientMessage.Ping -> Unit
|
is ClientMessage.Ping -> Unit
|
||||||
is ClientMessage.StartSession -> handleStartSession(session, msg, sendFrame)
|
is ClientMessage.StartSession -> handleStartSession(session, msg, sendFrame)
|
||||||
is ClientMessage.StartChatSession -> handleStartChatSession(session, msg, sendFrame)
|
is ClientMessage.StartChatSession -> handleStartChatSession(session, msg, sendFrame)
|
||||||
is ClientMessage.CancelSession -> module.orchestrator.cancel(msg.sessionId)
|
is ClientMessage.CancelSession -> {
|
||||||
|
runCatching { module.orchestrator.cancel(msg.sessionId) }
|
||||||
|
.onFailure { log.error("cancel failed for session={}: {}", msg.sessionId.value, it.message, it) }
|
||||||
|
}
|
||||||
is ClientMessage.ResumeSession -> session.send(encodeError("ResumeSession not supported"))
|
is ClientMessage.ResumeSession -> session.send(encodeError("ResumeSession not supported"))
|
||||||
is ClientMessage.ApprovalResponse -> handleApprovalResponse(msg, sendFrame)
|
is ClientMessage.ApprovalResponse -> handleApprovalResponse(msg, sendFrame)
|
||||||
is ClientMessage.CreateGrant -> handleCreateGrant(msg, sendFrame)
|
is ClientMessage.CreateGrant -> handleCreateGrant(msg, sendFrame)
|
||||||
@@ -159,11 +158,7 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
))
|
))
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
log.error("routerFacade.onUserInput failed: {}", it.message)
|
log.error("routerFacade.onUserInput failed: {}", it.message)
|
||||||
sendFrame(ServerMessage.ProtocolError(
|
sendFrame(ServerMessage.ProtocolError("Router error: ${it.message}"))
|
||||||
message = "Router error: ${it.message}",
|
|
||||||
sequence = null,
|
|
||||||
sessionSequence = null,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,34 +171,16 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
val scopeSessionId = module.approvalCoordinator.lookupSession(msg.requestId)
|
val scopeSessionId = module.approvalCoordinator.lookupSession(msg.requestId)
|
||||||
if (scopeSessionId == null) {
|
if (scopeSessionId == null) {
|
||||||
log.warn("handleApprovalResponse: no session found for requestId={}", msg.requestId.value)
|
log.warn("handleApprovalResponse: no session found for requestId={}", msg.requestId.value)
|
||||||
sendFrame(
|
sendFrame(ServerMessage.ProtocolError("Unknown approval request"))
|
||||||
ServerMessage.ProtocolError(
|
|
||||||
message = "Unknown approval request",
|
|
||||||
sequence = null,
|
|
||||||
sessionSequence = null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
module.approvalCoordinator.handleResponse(msg, scopeSessionId)?.let { sendFrame(it) }
|
module.approvalCoordinator.handleResponse(msg, scopeSessionId)?.let { sendFrame(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun errorResponse(message: String) = ServerMessage.ProtocolError(
|
private fun errorResponse(message: String) = ServerMessage.ProtocolError(message)
|
||||||
message = message,
|
|
||||||
sequence = null,
|
|
||||||
sessionSequence = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun encodeError(message: String): Frame.Text =
|
private fun encodeError(message: String): Frame.Text =
|
||||||
Frame.Text(
|
Frame.Text(ProtocolSerializer.encodeServerMessage(ServerMessage.ProtocolError(message)))
|
||||||
ProtocolSerializer.encodeServerMessage(
|
|
||||||
ServerMessage.ProtocolError(
|
|
||||||
message = message,
|
|
||||||
sequence = null,
|
|
||||||
sessionSequence = null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private suspend fun handleCreateGrant(
|
private suspend fun handleCreateGrant(
|
||||||
msg: ClientMessage.CreateGrant,
|
msg: ClientMessage.CreateGrant,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import com.correx.core.events.events.InferenceCompletedEvent
|
|||||||
import com.correx.core.events.events.InferenceStartedEvent
|
import com.correx.core.events.events.InferenceStartedEvent
|
||||||
import com.correx.core.events.events.InferenceTimeoutEvent
|
import com.correx.core.events.events.InferenceTimeoutEvent
|
||||||
import com.correx.core.events.events.OrchestrationPausedEvent
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
import com.correx.core.events.events.SteeringNoteAddedEvent
|
||||||
import com.correx.core.events.events.StageCompletedEvent
|
import com.correx.core.events.events.StageCompletedEvent
|
||||||
import com.correx.core.events.events.StageFailedEvent
|
import com.correx.core.events.events.StageFailedEvent
|
||||||
import com.correx.core.events.events.StoredEvent
|
import com.correx.core.events.events.StoredEvent
|
||||||
@@ -371,7 +371,7 @@ class DomainEventMapperTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unmapped event returns null`(): Unit = runTest {
|
fun `unmapped event returns null`(): Unit = runTest {
|
||||||
val event = storedEvent(SessionStartedEvent(sessionId = sessionId))
|
val event = storedEvent(SteeringNoteAddedEvent(sessionId = sessionId, content = "test"))
|
||||||
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L)
|
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L)
|
||||||
assertNull(result)
|
assertNull(result)
|
||||||
}
|
}
|
||||||
@@ -392,12 +392,12 @@ class DomainEventMapperTest {
|
|||||||
val origLevel = logger.level
|
val origLevel = logger.level
|
||||||
logger.level = Level.DEBUG
|
logger.level = Level.DEBUG
|
||||||
try {
|
try {
|
||||||
val event = storedEvent(SessionStartedEvent(sessionId = sessionId))
|
val event = storedEvent(SteeringNoteAddedEvent(sessionId = sessionId, content = "test"))
|
||||||
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L)
|
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L)
|
||||||
assertNull(result)
|
assertNull(result)
|
||||||
assertEquals(1, appender.events.size)
|
assertEquals(1, appender.events.size)
|
||||||
assertEquals(Level.DEBUG, appender.events[0].level)
|
assertEquals(Level.DEBUG, appender.events[0].level)
|
||||||
assertTrue(appender.events[0].message.formattedMessage.contains("SessionStartedEvent"))
|
assertTrue(appender.events[0].message.formattedMessage.contains("SteeringNoteAddedEvent"))
|
||||||
} finally {
|
} finally {
|
||||||
logger.removeAppender(appender)
|
logger.removeAppender(appender)
|
||||||
appender.stop()
|
appender.stop()
|
||||||
|
|||||||
@@ -25,10 +25,8 @@ import com.correx.core.kernel.orchestration.OrchestrationRepository
|
|||||||
import com.correx.core.tools.contract.Tool
|
import com.correx.core.tools.contract.Tool
|
||||||
import com.correx.core.tools.registry.ToolRegistry
|
import com.correx.core.tools.registry.ToolRegistry
|
||||||
import com.correx.core.sessions.projections.replay.EventReplayer
|
import com.correx.core.sessions.projections.replay.EventReplayer
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
@@ -186,36 +184,6 @@ class SessionEventBridgeTest {
|
|||||||
assertEquals(ServerMessage.SnapshotComplete, sent[1])
|
assertEquals(ServerMessage.SnapshotComplete, sent[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `streamLive sends mapped messages from live flow`() = runTest {
|
|
||||||
val events = listOf(
|
|
||||||
storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L),
|
|
||||||
storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 2L),
|
|
||||||
)
|
|
||||||
val liveFlow: Flow<StoredEvent> = flow { events.forEach { emit(it) } }
|
|
||||||
val store = fakeEventStore(liveFlow = liveFlow)
|
|
||||||
val sent = mutableListOf<ServerMessage>()
|
|
||||||
val bridge = SessionEventBridge(
|
|
||||||
store,
|
|
||||||
noopArtifactStore,
|
|
||||||
activeOrchestrationRepository(),
|
|
||||||
noopWorkflowRegistry,
|
|
||||||
noopToolRegistry,
|
|
||||||
) { sent.add(it) }
|
|
||||||
|
|
||||||
bridge.streamLive(sessionId)
|
|
||||||
|
|
||||||
assertEquals(2, sent.size)
|
|
||||||
assertEquals(
|
|
||||||
ServerMessage.SessionStarted(sessionId, workflowId, 1L, 1L),
|
|
||||||
sent[0],
|
|
||||||
)
|
|
||||||
assertEquals(
|
|
||||||
ServerMessage.SessionCompleted(sessionId, 2L, 2L),
|
|
||||||
sent[1],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `replaySnapshot with no sessions emits only SnapshotComplete`() = runTest {
|
fun `replaySnapshot with no sessions emits only SnapshotComplete`() = runTest {
|
||||||
val store = fakeEventStore(allEventsList = emptyList())
|
val store = fakeEventStore(allEventsList = emptyList())
|
||||||
@@ -266,29 +234,4 @@ class SessionEventBridgeTest {
|
|||||||
assertEquals(3L, snapshot.lastSessionSequence)
|
assertEquals(3L, snapshot.lastSessionSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `streamLive cancellation stops collection`() = runTest {
|
|
||||||
val channel = Channel<StoredEvent>(Channel.UNLIMITED)
|
|
||||||
val liveFlow: Flow<StoredEvent> = flow {
|
|
||||||
for (event in channel) emit(event)
|
|
||||||
}
|
|
||||||
val store = fakeEventStore(liveFlow = liveFlow)
|
|
||||||
val sent = mutableListOf<ServerMessage>()
|
|
||||||
val bridge = SessionEventBridge(
|
|
||||||
store,
|
|
||||||
noopArtifactStore,
|
|
||||||
activeOrchestrationRepository(),
|
|
||||||
noopWorkflowRegistry,
|
|
||||||
noopToolRegistry,
|
|
||||||
) { sent.add(it) }
|
|
||||||
|
|
||||||
channel.send(storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L))
|
|
||||||
|
|
||||||
val job = launch { bridge.streamLive(sessionId) }
|
|
||||||
channel.send(storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 2L))
|
|
||||||
channel.close()
|
|
||||||
job.join()
|
|
||||||
|
|
||||||
assertEquals(2, sent.size)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.correx.apps.tui.components
|
package com.correx.apps.tui.components
|
||||||
|
|
||||||
import com.correx.apps.tui.state.ToolDisplayStatus
|
|
||||||
import com.correx.apps.tui.state.TuiState
|
import com.correx.apps.tui.state.TuiState
|
||||||
import dev.tamboui.style.Style
|
import dev.tamboui.style.Style
|
||||||
import dev.tamboui.text.Line
|
import dev.tamboui.text.Line
|
||||||
|
|||||||
@@ -62,12 +62,14 @@ object RootReducer {
|
|||||||
|
|
||||||
log.debug("sessions state before reducers={}", state.sessions)
|
log.debug("sessions state before reducers={}", state.sessions)
|
||||||
val (sessions, se) = SessionsReducer.reduce(
|
val (sessions, se) = SessionsReducer.reduce(
|
||||||
sessions = afterInput.sessions,
|
SessionsReducerContext(
|
||||||
displayState = afterInput.displayState,
|
sessions = afterInput.sessions,
|
||||||
inputMode = prevInputMode,
|
displayState = afterInput.displayState,
|
||||||
inputText = prevInputBuffer,
|
inputMode = prevInputMode,
|
||||||
action = action,
|
inputText = prevInputBuffer,
|
||||||
clock = clock,
|
action = action,
|
||||||
|
clock = clock,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
log.debug("connection state before reducers={}", state.connection)
|
log.debug("connection state before reducers={}", state.connection)
|
||||||
val (connection, ce) = ConnectionReducer.reduce(afterInput.connection, action)
|
val (connection, ce) = ConnectionReducer.reduce(afterInput.connection, action)
|
||||||
|
|||||||
@@ -22,40 +22,42 @@ import java.time.format.DateTimeFormatter
|
|||||||
|
|
||||||
private val log = LoggerFactory.getLogger(SessionsReducer::class.simpleName)
|
private val log = LoggerFactory.getLogger(SessionsReducer::class.simpleName)
|
||||||
|
|
||||||
|
data class SessionsReducerContext(
|
||||||
|
val sessions: SessionsState,
|
||||||
|
val displayState: DisplayState,
|
||||||
|
val inputMode: InputMode,
|
||||||
|
val inputText: String,
|
||||||
|
val action: Action,
|
||||||
|
val clock: () -> Long = System::currentTimeMillis,
|
||||||
|
)
|
||||||
|
|
||||||
object SessionsReducer {
|
object SessionsReducer {
|
||||||
private val timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneOffset.UTC)
|
private val timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneOffset.UTC)
|
||||||
|
|
||||||
fun reduce(
|
fun reduce(ctx: SessionsReducerContext): Pair<SessionsState, List<Effect>> = when (ctx.action) {
|
||||||
sessions: SessionsState,
|
|
||||||
displayState: DisplayState,
|
|
||||||
inputMode: InputMode,
|
|
||||||
inputText: String,
|
|
||||||
action: Action,
|
|
||||||
clock: () -> Long = System::currentTimeMillis,
|
|
||||||
): Pair<SessionsState, List<Effect>> = when (action) {
|
|
||||||
is Action.NavigateUp -> when {
|
is Action.NavigateUp -> when {
|
||||||
inputMode == InputMode.FILTER -> navigateUp(sessions) to emptyList()
|
ctx.inputMode == InputMode.FILTER -> navigateUp(ctx.sessions) to emptyList()
|
||||||
displayState == DisplayState.IDLE -> navigateUp(sessions) to emptyList()
|
ctx.displayState == DisplayState.IDLE -> navigateUp(ctx.sessions) to emptyList()
|
||||||
displayState == DisplayState.IN_SESSION -> sessions to emptyList()
|
ctx.displayState == DisplayState.IN_SESSION -> ctx.sessions to emptyList()
|
||||||
else -> sessions to emptyList()
|
else -> ctx.sessions to emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
is Action.NavigateDown -> when {
|
is Action.NavigateDown -> when {
|
||||||
inputMode == InputMode.FILTER -> navigateDown(sessions) to emptyList()
|
ctx.inputMode == InputMode.FILTER -> navigateDown(ctx.sessions) to emptyList()
|
||||||
displayState == DisplayState.IDLE -> navigateDown(sessions) to emptyList()
|
ctx.displayState == DisplayState.IDLE -> navigateDown(ctx.sessions) to emptyList()
|
||||||
displayState == DisplayState.IN_SESSION -> sessions to emptyList()
|
ctx.displayState == DisplayState.IN_SESSION -> ctx.sessions to emptyList()
|
||||||
else -> sessions to emptyList()
|
else -> ctx.sessions to emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
is Action.SubmitInput -> when {
|
is Action.SubmitInput -> when {
|
||||||
inputMode == InputMode.FILTER -> sessions.copy(filter = inputText) to emptyList()
|
ctx.inputMode == InputMode.FILTER -> ctx.sessions.copy(filter = ctx.inputText) to emptyList()
|
||||||
displayState == DisplayState.IDLE -> {
|
ctx.displayState == DisplayState.IDLE -> {
|
||||||
val text = inputText.trim()
|
val text = ctx.inputText.trim()
|
||||||
val wfIndex = sessions.selectedWorkflowIndex
|
val wfIndex = ctx.sessions.selectedWorkflowIndex
|
||||||
when {
|
when {
|
||||||
wfIndex in sessions.workflows.indices -> {
|
wfIndex in ctx.sessions.workflows.indices -> {
|
||||||
val wf = sessions.workflows[wfIndex]
|
val wf = ctx.sessions.workflows[wfIndex]
|
||||||
sessions.copy(selectedWorkflowIndex = -1) to listOf(
|
ctx.sessions.copy(selectedWorkflowIndex = -1) to listOf(
|
||||||
Effect.SendWs(ClientMessage.StartSession(workflowId = wf.workflowId, config = null)),
|
Effect.SendWs(ClientMessage.StartSession(workflowId = wf.workflowId, config = null)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -67,56 +69,56 @@ object SessionsReducer {
|
|||||||
status = "STARTING",
|
status = "STARTING",
|
||||||
workflowId = "chat",
|
workflowId = "chat",
|
||||||
name = "chat",
|
name = "chat",
|
||||||
lastEventAt = clock(),
|
lastEventAt = ctx.clock(),
|
||||||
)
|
)
|
||||||
sessions.copy(
|
ctx.sessions.copy(
|
||||||
sessions = sessions.sessions + optimistic,
|
sessions = ctx.sessions.sessions + optimistic,
|
||||||
selectedId = sessionId.value,
|
selectedId = sessionId.value,
|
||||||
) to listOf(
|
) to listOf(
|
||||||
Effect.SendWs(ClientMessage.StartChatSession(sessionId = sessionId, text = text)),
|
Effect.SendWs(ClientMessage.StartChatSession(sessionId = sessionId, text = text)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> sessions to emptyList()
|
else -> ctx.sessions to emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayState == DisplayState.IN_SESSION -> {
|
ctx.displayState == DisplayState.IN_SESSION -> ctx.sessions.selectedId?.let { sid ->
|
||||||
sessions to listOf(
|
ctx.sessions to listOf(
|
||||||
Effect.SendWs(
|
Effect.SendWs(
|
||||||
ClientMessage.ChatInput(
|
ClientMessage.ChatInput(
|
||||||
sessionId = SessionId(sessions.selectedId!!),
|
sessionId = SessionId(sid),
|
||||||
text = inputText,
|
text = ctx.inputText,
|
||||||
mode = ChatMode.CHAT,
|
mode = ChatMode.CHAT,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
} ?: (ctx.sessions to emptyList())
|
||||||
|
|
||||||
else -> sessions to emptyList()
|
else -> ctx.sessions to emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
is Action.CancelInput -> if (inputMode == InputMode.FILTER) {
|
is Action.CancelInput -> if (ctx.inputMode == InputMode.FILTER) {
|
||||||
sessions.copy(filter = "") to emptyList()
|
ctx.sessions.copy(filter = "") to emptyList()
|
||||||
} else {
|
} else {
|
||||||
sessions to emptyList()
|
ctx.sessions to emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
is Action.CancelSelectedSession -> {
|
is Action.CancelSelectedSession -> {
|
||||||
val id = sessions.selectedId
|
val id = ctx.sessions.selectedId
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
sessions to listOf(Effect.SendWs(ClientMessage.CancelSession(sessionId = SessionId(id))))
|
ctx.sessions to listOf(Effect.SendWs(ClientMessage.CancelSession(sessionId = SessionId(id))))
|
||||||
} else {
|
} else {
|
||||||
sessions to emptyList()
|
ctx.sessions to emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Action.ToggleWorkflows -> sessions.copy(
|
is Action.ToggleWorkflows -> ctx.sessions.copy(
|
||||||
workflowsVisible = !sessions.workflowsVisible,
|
workflowsVisible = !ctx.sessions.workflowsVisible,
|
||||||
) to emptyList()
|
) to emptyList()
|
||||||
|
|
||||||
is Action.ServerEventReceived -> applyServerMessage(sessions, action.message, clock)
|
is Action.ServerEventReceived -> applyServerMessage(ctx.sessions, ctx.action.message, ctx.clock)
|
||||||
else -> sessions to emptyList()
|
else -> ctx.sessions to emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filteredSessions(sessions: SessionsState): List<SessionSummary> =
|
private fun filteredSessions(sessions: SessionsState): List<SessionSummary> =
|
||||||
@@ -311,7 +313,6 @@ object SessionsReducer {
|
|||||||
tier = tool.tier,
|
tier = tool.tier,
|
||||||
status = displayStatus,
|
status = displayStatus,
|
||||||
argsPreview = null,
|
argsPreview = null,
|
||||||
diff = tool.diff,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val summary = SessionSummary(
|
val summary = SessionSummary(
|
||||||
@@ -409,7 +410,7 @@ object SessionsReducer {
|
|||||||
if (s.id == msg.sessionId.value) {
|
if (s.id == msg.sessionId.value) {
|
||||||
val updatedTools = s.tools.map { t ->
|
val updatedTools = s.tools.map { t ->
|
||||||
if (t.name == msg.toolName && t.status == ToolDisplayStatus.STARTED) {
|
if (t.name == msg.toolName && t.status == ToolDisplayStatus.STARTED) {
|
||||||
t.copy(status = ToolDisplayStatus.COMPLETED, diff = msg.diff)
|
t.copy(status = ToolDisplayStatus.COMPLETED)
|
||||||
} else {
|
} else {
|
||||||
t
|
t
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.correx.apps.server.protocol.ServerMessage
|
|||||||
|
|
||||||
enum class InputMode { ROUTER, FILTER }
|
enum class InputMode { ROUTER, FILTER }
|
||||||
|
|
||||||
enum class ToolDisplayStatus { REQUESTED, STARTED, COMPLETED, FAILED, REJECTED }
|
enum class ToolDisplayStatus { STARTED, COMPLETED, FAILED, REJECTED }
|
||||||
|
|
||||||
enum class ProviderType { LOCAL, REMOTE }
|
enum class ProviderType { LOCAL, REMOTE }
|
||||||
|
|
||||||
@@ -14,7 +14,6 @@ data class TuiToolRecord(
|
|||||||
val tier: Int,
|
val tier: Int,
|
||||||
val status: ToolDisplayStatus,
|
val status: ToolDisplayStatus,
|
||||||
val argsPreview: String?,
|
val argsPreview: String?,
|
||||||
val diff: String? = null,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data class TuiEventEntry(
|
data class TuiEventEntry(
|
||||||
|
|||||||
@@ -41,7 +41,16 @@ class SessionsReducerTest {
|
|||||||
inputMode: InputMode = InputMode.ROUTER,
|
inputMode: InputMode = InputMode.ROUTER,
|
||||||
inputText: String = "",
|
inputText: String = "",
|
||||||
action: Action,
|
action: Action,
|
||||||
) = SessionsReducer.reduce(sessions, displayState, inputMode, inputText, action, fixedClock)
|
) = SessionsReducer.reduce(
|
||||||
|
SessionsReducerContext(
|
||||||
|
sessions = sessions,
|
||||||
|
displayState = displayState,
|
||||||
|
inputMode = inputMode,
|
||||||
|
inputText = inputText,
|
||||||
|
action = action,
|
||||||
|
clock = fixedClock,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `NavigateUp wraps from top to bottom`() {
|
fun `NavigateUp wraps from top to bottom`() {
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
package com.correx.core.artifacts
|
package com.correx.core.artifacts
|
||||||
|
|
||||||
import com.correx.core.artifacts.model.ArtifactRelationship
|
import com.correx.core.artifacts.model.ArtifactRelationship
|
||||||
import com.correx.core.events.events.ArtifactArchivedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactCreatedEvent
|
import com.correx.core.events.events.ArtifactCreatedEvent
|
||||||
import com.correx.core.events.events.ArtifactRejectedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactRelationshipAddedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactSupersededEvent
|
|
||||||
import com.correx.core.events.events.ArtifactValidatedEvent
|
import com.correx.core.events.events.ArtifactValidatedEvent
|
||||||
import com.correx.core.events.events.ArtifactValidatingEvent
|
import com.correx.core.events.events.ArtifactValidatingEvent
|
||||||
import com.correx.core.events.events.StoredEvent
|
import com.correx.core.events.events.StoredEvent
|
||||||
@@ -25,23 +21,6 @@ class DefaultArtifactReducer : ArtifactReducer {
|
|||||||
transition(state, ArtifactLifecyclePhase.VALIDATING, ArtifactLifecyclePhase.CREATED)
|
transition(state, ArtifactLifecyclePhase.VALIDATING, ArtifactLifecyclePhase.CREATED)
|
||||||
is ArtifactValidatedEvent ->
|
is ArtifactValidatedEvent ->
|
||||||
transition(state, ArtifactLifecyclePhase.VALIDATED, ArtifactLifecyclePhase.VALIDATING)
|
transition(state, ArtifactLifecyclePhase.VALIDATED, ArtifactLifecyclePhase.VALIDATING)
|
||||||
is ArtifactRejectedEvent ->
|
|
||||||
transition(state, ArtifactLifecyclePhase.REJECTED, ArtifactLifecyclePhase.VALIDATING)
|
|
||||||
is ArtifactSupersededEvent ->
|
|
||||||
transition(state, ArtifactLifecyclePhase.SUPERSEDED, ArtifactLifecyclePhase.VALIDATED)
|
|
||||||
is ArtifactArchivedEvent ->
|
|
||||||
transitionFromAny(
|
|
||||||
state,
|
|
||||||
ArtifactLifecyclePhase.ARCHIVED,
|
|
||||||
ArtifactLifecyclePhase.VALIDATED,
|
|
||||||
ArtifactLifecyclePhase.REJECTED,
|
|
||||||
)
|
|
||||||
is ArtifactRelationshipAddedEvent -> {
|
|
||||||
val rel = ArtifactRelationship(p.sourceId, p.targetId, p.relationshipType)
|
|
||||||
Result.success(
|
|
||||||
state.copy(lineage = state.lineage.copy(relationships = state.lineage.relationships + rel))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> Result.success(state)
|
else -> Result.success(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,19 +38,4 @@ class DefaultArtifactReducer : ArtifactReducer {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transitionFromAny(
|
|
||||||
state: ArtifactState,
|
|
||||||
to: ArtifactLifecyclePhase,
|
|
||||||
vararg validFrom: ArtifactLifecyclePhase,
|
|
||||||
): Result<ArtifactState> =
|
|
||||||
if (state.phase in validFrom) {
|
|
||||||
Result.success(state.copy(phase = to))
|
|
||||||
} else {
|
|
||||||
Result.failure(
|
|
||||||
IllegalStateException(
|
|
||||||
"Invalid artifact transition: ${state.phase} → $to (expected one of ${validFrom.toList()})"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,8 @@
|
|||||||
package com.correx.core.context
|
package com.correx.core.context
|
||||||
|
|
||||||
import com.correx.core.context.state.ContextState
|
import com.correx.core.context.state.ContextState
|
||||||
import com.correx.core.events.events.ContextBuildingFailedEvent
|
|
||||||
import com.correx.core.events.events.ContextBuildingInterruptedEvent
|
|
||||||
import com.correx.core.events.events.ContextBuildingStartedEvent
|
|
||||||
import com.correx.core.events.events.ContextPackBuiltEvent
|
|
||||||
import com.correx.core.events.events.StoredEvent
|
import com.correx.core.events.events.StoredEvent
|
||||||
|
|
||||||
class DefaultContextReducer : ContextReducer {
|
class DefaultContextReducer : ContextReducer {
|
||||||
override fun reduce(state: ContextState, event: StoredEvent): ContextState =
|
override fun reduce(state: ContextState, event: StoredEvent): ContextState = state
|
||||||
when (val p = event.payload) {
|
|
||||||
is ContextBuildingStartedEvent -> state.copy(buildingInProgress = true, interrupted = false)
|
|
||||||
is ContextPackBuiltEvent -> state.copy(
|
|
||||||
buildingInProgress = false,
|
|
||||||
interrupted = false,
|
|
||||||
builtPackIds = state.builtPackIds + p.contextPackId,
|
|
||||||
)
|
|
||||||
is ContextBuildingFailedEvent -> state.copy(buildingInProgress = false, interrupted = false)
|
|
||||||
is ContextBuildingInterruptedEvent -> state.copy(buildingInProgress = false, interrupted = true)
|
|
||||||
else -> state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.correx.core.events.events
|
package com.correx.core.events.events
|
||||||
|
|
||||||
import com.correx.core.events.types.ArtifactId
|
import com.correx.core.events.types.ArtifactId
|
||||||
import com.correx.core.events.types.ArtifactRelationshipType
|
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
@@ -23,33 +22,6 @@ data class ArtifactValidatedEvent(
|
|||||||
val stageId: StageId,
|
val stageId: StageId,
|
||||||
) : EventPayload
|
) : EventPayload
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ArtifactSuperseded")
|
|
||||||
data class ArtifactSupersededEvent(
|
|
||||||
val artifactId: ArtifactId,
|
|
||||||
val supersededById: ArtifactId,
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ArtifactRelationshipAdded")
|
|
||||||
data class ArtifactRelationshipAddedEvent(
|
|
||||||
val sourceId: ArtifactId,
|
|
||||||
val targetId: ArtifactId,
|
|
||||||
val relationshipType: ArtifactRelationshipType,
|
|
||||||
val sessionId: SessionId,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ArtifactRejected")
|
|
||||||
data class ArtifactRejectedEvent(
|
|
||||||
val artifactId: ArtifactId,
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
val reason: String,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("ArtifactCreated")
|
@SerialName("ArtifactCreated")
|
||||||
data class ArtifactCreatedEvent(
|
data class ArtifactCreatedEvent(
|
||||||
@@ -58,11 +30,3 @@ data class ArtifactCreatedEvent(
|
|||||||
val stageId: StageId,
|
val stageId: StageId,
|
||||||
val schemaVersion: Int,
|
val schemaVersion: Int,
|
||||||
) : EventPayload
|
) : EventPayload
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ArtifactArchived")
|
|
||||||
data class ArtifactArchivedEvent(
|
|
||||||
val artifactId: ArtifactId,
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
) : EventPayload
|
|
||||||
|
|||||||
@@ -1,62 +1,10 @@
|
|||||||
package com.correx.core.events.events
|
package com.correx.core.events.events
|
||||||
|
|
||||||
import com.correx.core.events.types.ContextPackId
|
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("LayerTruncated")
|
|
||||||
data class LayerTruncatedEvent(
|
|
||||||
val contextPackId: ContextPackId,
|
|
||||||
val layer: String,
|
|
||||||
val entriesDropped: Int,
|
|
||||||
val reason: String,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ContextBuildingStarted")
|
|
||||||
data class ContextBuildingStartedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ContextBuildingFailed")
|
|
||||||
data class ContextBuildingFailedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
val reason: String,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
// Emitted during replay when a session ended with buildingInProgress=true and no completion/failure event followed.
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ContextBuildingInterrupted")
|
|
||||||
data class ContextBuildingInterruptedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("CompressionApplied")
|
|
||||||
data class CompressionAppliedEvent(
|
|
||||||
val contextPackId: ContextPackId,
|
|
||||||
val layer: String,
|
|
||||||
val entriesRemoved: Int,
|
|
||||||
val strategyApplied: String,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("ContextPackBuilt")
|
|
||||||
data class ContextPackBuiltEvent(
|
|
||||||
val contextPackId: ContextPackId,
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
val budgetUsed: Int,
|
|
||||||
val budgetLimit: Int,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("SteeringNoteAdded")
|
@SerialName("SteeringNoteAdded")
|
||||||
data class SteeringNoteAddedEvent(
|
data class SteeringNoteAddedEvent(
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ data class EventMetadata(
|
|||||||
val eventId: EventId,
|
val eventId: EventId,
|
||||||
val sessionId: SessionId,
|
val sessionId: SessionId,
|
||||||
val timestamp: Instant,
|
val timestamp: Instant,
|
||||||
|
/**
|
||||||
|
* Reserved for future event schema migration. Currently always hardcoded to `1`.
|
||||||
|
* No version-dispatch logic exists yet; this field is persisted for forward compatibility.
|
||||||
|
* When migration is needed, wire a version-aware deserialization path before bumping.
|
||||||
|
*/
|
||||||
val schemaVersion: Int,
|
val schemaVersion: Int,
|
||||||
val causationId: CausationId?,
|
val causationId: CausationId?,
|
||||||
val correlationId: CorrelationId?
|
val correlationId: CorrelationId?
|
||||||
|
|||||||
@@ -4,41 +4,6 @@ import com.correx.core.events.types.SessionId
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("SessionStarted")
|
|
||||||
data class SessionStartedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val initialContextId: String? = null
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("SessionPaused")
|
|
||||||
data class SessionPausedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val reason: String? = null
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("SessionResumed")
|
|
||||||
data class SessionResumedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("SessionCompleted")
|
|
||||||
data class SessionCompletedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val summary: String? = null
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("SessionFailed")
|
|
||||||
data class SessionFailedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val errorCode: String? = null,
|
|
||||||
val errorMessage: String? = null
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("ChatSessionStarted")
|
@SerialName("ChatSessionStarted")
|
||||||
data class ChatSessionStartedEvent(
|
data class ChatSessionStartedEvent(
|
||||||
|
|||||||
@@ -6,14 +6,6 @@ import com.correx.core.events.types.TransitionId
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("StageStarted")
|
|
||||||
data class StageStartedEvent(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val stageId: StageId,
|
|
||||||
val transitionId: TransitionId
|
|
||||||
) : EventPayload
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("StageCompleted")
|
@SerialName("StageCompleted")
|
||||||
data class StageCompletedEvent(
|
data class StageCompletedEvent(
|
||||||
|
|||||||
@@ -4,39 +4,23 @@ import com.correx.core.events.events.ApprovalDecisionResolvedEvent
|
|||||||
import com.correx.core.events.events.ApprovalGrantCreatedEvent
|
import com.correx.core.events.events.ApprovalGrantCreatedEvent
|
||||||
import com.correx.core.events.events.ApprovalGrantExpiredEvent
|
import com.correx.core.events.events.ApprovalGrantExpiredEvent
|
||||||
import com.correx.core.events.events.ApprovalRequestedEvent
|
import com.correx.core.events.events.ApprovalRequestedEvent
|
||||||
import com.correx.core.events.events.ArtifactArchivedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactCreatedEvent
|
import com.correx.core.events.events.ArtifactCreatedEvent
|
||||||
import com.correx.core.events.events.ArtifactRejectedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactRelationshipAddedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactSupersededEvent
|
|
||||||
import com.correx.core.events.events.ArtifactValidatedEvent
|
import com.correx.core.events.events.ArtifactValidatedEvent
|
||||||
import com.correx.core.events.events.ArtifactValidatingEvent
|
import com.correx.core.events.events.ArtifactValidatingEvent
|
||||||
import com.correx.core.events.events.CompressionAppliedEvent
|
import com.correx.core.events.events.ChatSessionStartedEvent
|
||||||
import com.correx.core.events.events.ContextBuildingFailedEvent
|
|
||||||
import com.correx.core.events.events.ContextBuildingInterruptedEvent
|
|
||||||
import com.correx.core.events.events.ContextBuildingStartedEvent
|
|
||||||
import com.correx.core.events.events.ContextPackBuiltEvent
|
|
||||||
import com.correx.core.events.events.EventPayload
|
import com.correx.core.events.events.EventPayload
|
||||||
import com.correx.core.events.events.InferenceCompletedEvent
|
import com.correx.core.events.events.InferenceCompletedEvent
|
||||||
import com.correx.core.events.events.InferenceFailedEvent
|
import com.correx.core.events.events.InferenceFailedEvent
|
||||||
import com.correx.core.events.events.InferenceStartedEvent
|
import com.correx.core.events.events.InferenceStartedEvent
|
||||||
import com.correx.core.events.events.InferenceTimeoutEvent
|
import com.correx.core.events.events.InferenceTimeoutEvent
|
||||||
import com.correx.core.events.events.LayerTruncatedEvent
|
|
||||||
import com.correx.core.events.events.ModelLoadedEvent
|
import com.correx.core.events.events.ModelLoadedEvent
|
||||||
import com.correx.core.events.events.ModelUnloadedEvent
|
import com.correx.core.events.events.ModelUnloadedEvent
|
||||||
import com.correx.core.events.events.OrchestrationPausedEvent
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||||
import com.correx.core.events.events.OrchestrationResumedEvent
|
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||||
import com.correx.core.events.events.RetryAttemptedEvent
|
import com.correx.core.events.events.RetryAttemptedEvent
|
||||||
import com.correx.core.events.events.RiskAssessedEvent
|
import com.correx.core.events.events.RiskAssessedEvent
|
||||||
import com.correx.core.events.events.SessionCompletedEvent
|
|
||||||
import com.correx.core.events.events.SessionFailedEvent
|
|
||||||
import com.correx.core.events.events.ChatSessionStartedEvent
|
|
||||||
import com.correx.core.events.events.SessionPausedEvent
|
|
||||||
import com.correx.core.events.events.SessionResumedEvent
|
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
|
||||||
import com.correx.core.events.events.StageCompletedEvent
|
import com.correx.core.events.events.StageCompletedEvent
|
||||||
import com.correx.core.events.events.StageFailedEvent
|
import com.correx.core.events.events.StageFailedEvent
|
||||||
import com.correx.core.events.events.StageStartedEvent
|
|
||||||
import com.correx.core.events.events.SteeringNoteAddedEvent
|
import com.correx.core.events.events.SteeringNoteAddedEvent
|
||||||
import com.correx.core.events.events.ToolExecutionCompletedEvent
|
import com.correx.core.events.events.ToolExecutionCompletedEvent
|
||||||
import com.correx.core.events.events.ToolExecutionFailedEvent
|
import com.correx.core.events.events.ToolExecutionFailedEvent
|
||||||
@@ -62,13 +46,7 @@ val eventModule = SerializersModule {
|
|||||||
subclass(ToolExecutionCompletedEvent::class)
|
subclass(ToolExecutionCompletedEvent::class)
|
||||||
subclass(ToolExecutionFailedEvent::class)
|
subclass(ToolExecutionFailedEvent::class)
|
||||||
subclass(ToolExecutionRejectedEvent::class)
|
subclass(ToolExecutionRejectedEvent::class)
|
||||||
subclass(SessionStartedEvent::class)
|
|
||||||
subclass(SessionPausedEvent::class)
|
|
||||||
subclass(SessionResumedEvent::class)
|
|
||||||
subclass(SessionCompletedEvent::class)
|
|
||||||
subclass(SessionFailedEvent::class)
|
|
||||||
subclass(SteeringNoteAddedEvent::class)
|
subclass(SteeringNoteAddedEvent::class)
|
||||||
subclass(StageStartedEvent::class)
|
|
||||||
subclass(StageFailedEvent::class)
|
subclass(StageFailedEvent::class)
|
||||||
subclass(StageCompletedEvent::class)
|
subclass(StageCompletedEvent::class)
|
||||||
subclass(TransitionExecutedEvent::class)
|
subclass(TransitionExecutedEvent::class)
|
||||||
@@ -76,19 +54,9 @@ val eventModule = SerializersModule {
|
|||||||
subclass(ApprovalDecisionResolvedEvent::class)
|
subclass(ApprovalDecisionResolvedEvent::class)
|
||||||
subclass(ApprovalGrantCreatedEvent::class)
|
subclass(ApprovalGrantCreatedEvent::class)
|
||||||
subclass(ApprovalGrantExpiredEvent::class)
|
subclass(ApprovalGrantExpiredEvent::class)
|
||||||
subclass(ContextBuildingStartedEvent::class)
|
|
||||||
subclass(ContextPackBuiltEvent::class)
|
|
||||||
subclass(CompressionAppliedEvent::class)
|
|
||||||
subclass(LayerTruncatedEvent::class)
|
|
||||||
subclass(ContextBuildingFailedEvent::class)
|
|
||||||
subclass(ContextBuildingInterruptedEvent::class)
|
|
||||||
subclass(ArtifactCreatedEvent::class)
|
subclass(ArtifactCreatedEvent::class)
|
||||||
subclass(ArtifactValidatingEvent::class)
|
subclass(ArtifactValidatingEvent::class)
|
||||||
subclass(ArtifactValidatedEvent::class)
|
subclass(ArtifactValidatedEvent::class)
|
||||||
subclass(ArtifactRejectedEvent::class)
|
|
||||||
subclass(ArtifactSupersededEvent::class)
|
|
||||||
subclass(ArtifactArchivedEvent::class)
|
|
||||||
subclass(ArtifactRelationshipAddedEvent::class)
|
|
||||||
subclass(InferenceFailedEvent::class)
|
subclass(InferenceFailedEvent::class)
|
||||||
subclass(InferenceCompletedEvent::class)
|
subclass(InferenceCompletedEvent::class)
|
||||||
subclass(InferenceStartedEvent::class)
|
subclass(InferenceStartedEvent::class)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.correx.core.utils
|
package com.correx.core.utils
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -11,4 +12,12 @@ value class TypeId(val value: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = value
|
override fun toString(): String = value
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Sentinel value used when no real stage/entity is available. */
|
||||||
|
val NONE = TypeId("none")
|
||||||
|
|
||||||
|
/** Create a new random ID using a UUID string. */
|
||||||
|
fun random(): TypeId = TypeId(UUID.randomUUID().toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-11
@@ -2,21 +2,20 @@ package com.correx.core.events
|
|||||||
|
|
||||||
import com.correx.core.approvals.ApprovalOutcome
|
import com.correx.core.approvals.ApprovalOutcome
|
||||||
import com.correx.core.approvals.ApprovalStatus
|
import com.correx.core.approvals.ApprovalStatus
|
||||||
import com.correx.core.approvals.GrantScope
|
|
||||||
import com.correx.core.approvals.Tier
|
import com.correx.core.approvals.Tier
|
||||||
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
|
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
|
||||||
import com.correx.core.events.events.ContextPackBuiltEvent
|
|
||||||
import com.correx.core.events.events.EventPayload
|
import com.correx.core.events.events.EventPayload
|
||||||
import com.correx.core.events.events.OrchestrationPausedEvent
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||||
import com.correx.core.events.events.RiskAssessedEvent
|
import com.correx.core.events.events.RiskAssessedEvent
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
import com.correx.core.events.events.SteeringNoteAddedEvent
|
||||||
import com.correx.core.events.events.ToolExecutionFailedEvent
|
import com.correx.core.events.events.ToolExecutionFailedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowCompletedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.risk.RiskAction
|
import com.correx.core.events.risk.RiskAction
|
||||||
import com.correx.core.events.risk.RiskLevel
|
import com.correx.core.events.risk.RiskLevel
|
||||||
import com.correx.core.events.serialization.eventJson
|
import com.correx.core.events.serialization.eventJson
|
||||||
import com.correx.core.events.types.ApprovalDecisionId
|
import com.correx.core.events.types.ApprovalDecisionId
|
||||||
import com.correx.core.events.types.ApprovalRequestId
|
import com.correx.core.events.types.ApprovalRequestId
|
||||||
import com.correx.core.events.types.ContextPackId
|
|
||||||
import com.correx.core.events.types.RiskSummaryId
|
import com.correx.core.events.types.RiskSummaryId
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
@@ -35,7 +34,6 @@ class EventSerializationHardeningTest {
|
|||||||
private val ts = Instant.parse("2026-01-01T00:00:00Z")
|
private val ts = Instant.parse("2026-01-01T00:00:00Z")
|
||||||
|
|
||||||
private val payloads: List<Pair<String, EventPayload>> = listOf(
|
private val payloads: List<Pair<String, EventPayload>> = listOf(
|
||||||
"SessionStarted" to SessionStartedEvent(sessionId = sessionId),
|
|
||||||
"ToolExecutionFailed" to ToolExecutionFailedEvent(
|
"ToolExecutionFailed" to ToolExecutionFailedEvent(
|
||||||
invocationId = ToolInvocationId("inv-1"),
|
invocationId = ToolInvocationId("inv-1"),
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
@@ -51,12 +49,10 @@ class EventSerializationHardeningTest {
|
|||||||
resolutionTimestamp = ts,
|
resolutionTimestamp = ts,
|
||||||
reason = null
|
reason = null
|
||||||
),
|
),
|
||||||
"ContextPackBuilt" to ContextPackBuiltEvent(
|
"SteeringNoteAdded" to SteeringNoteAddedEvent(
|
||||||
contextPackId = ContextPackId("cp-1"),
|
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
|
content = "test steering note",
|
||||||
stageId = stageId,
|
stageId = stageId,
|
||||||
budgetUsed = 100,
|
|
||||||
budgetLimit = 4096
|
|
||||||
),
|
),
|
||||||
"OrchestrationPaused" to OrchestrationPausedEvent(
|
"OrchestrationPaused" to OrchestrationPausedEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
@@ -69,7 +65,17 @@ class EventSerializationHardeningTest {
|
|||||||
riskSummaryId = RiskSummaryId("rs-1"),
|
riskSummaryId = RiskSummaryId("rs-1"),
|
||||||
level = RiskLevel.MEDIUM,
|
level = RiskLevel.MEDIUM,
|
||||||
action = RiskAction.PROCEED
|
action = RiskAction.PROCEED
|
||||||
)
|
),
|
||||||
|
"WorkflowStarted" to WorkflowStartedEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
|
workflowId = "test-wf",
|
||||||
|
startStageId = stageId,
|
||||||
|
),
|
||||||
|
"WorkflowCompleted" to WorkflowCompletedEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
|
terminalStageId = stageId,
|
||||||
|
totalStages = 1,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -97,7 +103,7 @@ class EventSerializationHardeningTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unknown fields in JSON are tolerated (ignoreUnknownKeys=true)`() {
|
fun `unknown fields in JSON are tolerated (ignoreUnknownKeys=true)`() {
|
||||||
val event = SessionStartedEvent(sessionId = sessionId)
|
val event = WorkflowStartedEvent(sessionId = sessionId, workflowId = "twf", startStageId = stageId)
|
||||||
val json = eventJson.encodeToString(EventPayload.serializer(), event)
|
val json = eventJson.encodeToString(EventPayload.serializer(), event)
|
||||||
val withExtra = json.replace("{", "{\"bogusField\":123,")
|
val withExtra = json.replace("{", "{\"bogusField\":123,")
|
||||||
assertDoesNotThrow {
|
assertDoesNotThrow {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class DefaultRouterContextBuilder(
|
|||||||
return ContextPack(
|
return ContextPack(
|
||||||
id = ContextPackId("${state.sessionId?.value ?: "unknown"}-router-pack"),
|
id = ContextPackId("${state.sessionId?.value ?: "unknown"}-router-pack"),
|
||||||
sessionId = state.sessionId ?: SessionId("unknown"),
|
sessionId = state.sessionId ?: SessionId("unknown"),
|
||||||
stageId = state.currentStageId ?: StageId("none"),
|
stageId = state.currentStageId ?: StageId.NONE,
|
||||||
layers = layers,
|
layers = layers,
|
||||||
budgetUsed = budgetUsed,
|
budgetUsed = budgetUsed,
|
||||||
budgetLimit = budget.limit,
|
budgetLimit = budget.limit,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class DefaultRouterFacade(
|
|||||||
history.add(RouterTurn(role = TurnRole.USER, content = input, timestamp = Clock.System.now()))
|
history.add(RouterTurn(role = TurnRole.USER, content = input, timestamp = Clock.System.now()))
|
||||||
|
|
||||||
val stateWithHistory = state.copy(conversationHistory = history.toList())
|
val stateWithHistory = state.copy(conversationHistory = history.toList())
|
||||||
val effectiveStageId = state.currentStageId ?: StageId("none")
|
val effectiveStageId = state.currentStageId ?: StageId.NONE
|
||||||
|
|
||||||
val contextPack = routerContextBuilder.build(stateWithHistory, config.tokenBudget)
|
val contextPack = routerContextBuilder.build(stateWithHistory, config.tokenBudget)
|
||||||
val provider = inferenceRouter.route(effectiveStageId, setOf(ModelCapability.General))
|
val provider = inferenceRouter.route(effectiveStageId, setOf(ModelCapability.General))
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
package com.correx.core.sessions
|
package com.correx.core.sessions
|
||||||
|
|
||||||
import com.correx.core.events.events.SessionCompletedEvent
|
|
||||||
import com.correx.core.events.events.SessionFailedEvent
|
|
||||||
import com.correx.core.events.events.SessionPausedEvent
|
|
||||||
import com.correx.core.events.events.SessionResumedEvent
|
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
|
||||||
import com.correx.core.events.events.StageCompletedEvent
|
import com.correx.core.events.events.StageCompletedEvent
|
||||||
import com.correx.core.events.events.StageFailedEvent
|
import com.correx.core.events.events.StageFailedEvent
|
||||||
import com.correx.core.events.events.StageStartedEvent
|
|
||||||
import com.correx.core.events.events.StoredEvent
|
import com.correx.core.events.events.StoredEvent
|
||||||
import com.correx.core.events.events.TransitionExecutedEvent
|
import com.correx.core.events.events.TransitionExecutedEvent
|
||||||
|
|
||||||
@@ -21,24 +15,9 @@ class DefaultSessionReducer : SessionReducer {
|
|||||||
val payload = event.payload
|
val payload = event.payload
|
||||||
|
|
||||||
val newStatus = when (payload) {
|
val newStatus = when (payload) {
|
||||||
|
|
||||||
is SessionStartedEvent ->
|
|
||||||
SessionStatus.ACTIVE
|
|
||||||
|
|
||||||
is SessionPausedEvent ->
|
|
||||||
SessionStatus.PAUSED
|
|
||||||
|
|
||||||
is SessionResumedEvent ->
|
|
||||||
SessionStatus.ACTIVE
|
|
||||||
|
|
||||||
is SessionCompletedEvent ->
|
|
||||||
SessionStatus.COMPLETED
|
|
||||||
|
|
||||||
is SessionFailedEvent,
|
|
||||||
is StageFailedEvent ->
|
is StageFailedEvent ->
|
||||||
SessionStatus.FAILED
|
SessionStatus.FAILED
|
||||||
|
|
||||||
is StageStartedEvent,
|
|
||||||
is StageCompletedEvent,
|
is StageCompletedEvent,
|
||||||
is TransitionExecutedEvent ->
|
is TransitionExecutedEvent ->
|
||||||
SessionStatus.ACTIVE
|
SessionStatus.ACTIVE
|
||||||
|
|||||||
-8
@@ -4,11 +4,7 @@ import com.correx.core.artifacts.ArtifactProjector
|
|||||||
import com.correx.core.artifacts.ArtifactReducer
|
import com.correx.core.artifacts.ArtifactReducer
|
||||||
import com.correx.core.artifacts.ArtifactState
|
import com.correx.core.artifacts.ArtifactState
|
||||||
import com.correx.core.artifacts.repository.ArtifactRepository
|
import com.correx.core.artifacts.repository.ArtifactRepository
|
||||||
import com.correx.core.events.events.ArtifactArchivedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactCreatedEvent
|
import com.correx.core.events.events.ArtifactCreatedEvent
|
||||||
import com.correx.core.events.events.ArtifactRejectedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactRelationshipAddedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactSupersededEvent
|
|
||||||
import com.correx.core.events.events.ArtifactValidatedEvent
|
import com.correx.core.events.events.ArtifactValidatedEvent
|
||||||
import com.correx.core.events.events.ArtifactValidatingEvent
|
import com.correx.core.events.events.ArtifactValidatingEvent
|
||||||
import com.correx.core.events.events.StoredEvent
|
import com.correx.core.events.events.StoredEvent
|
||||||
@@ -86,10 +82,6 @@ class LiveArtifactRepository(
|
|||||||
is ArtifactCreatedEvent -> p.artifactId
|
is ArtifactCreatedEvent -> p.artifactId
|
||||||
is ArtifactValidatingEvent -> p.artifactId
|
is ArtifactValidatingEvent -> p.artifactId
|
||||||
is ArtifactValidatedEvent -> p.artifactId
|
is ArtifactValidatedEvent -> p.artifactId
|
||||||
is ArtifactRejectedEvent -> p.artifactId
|
|
||||||
is ArtifactSupersededEvent -> p.artifactId
|
|
||||||
is ArtifactArchivedEvent -> p.artifactId
|
|
||||||
is ArtifactRelationshipAddedEvent -> p.sourceId
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import com.correx.core.events.events.EventMetadata
|
import com.correx.core.events.events.EventMetadata
|
||||||
import com.correx.core.events.events.NewEvent
|
import com.correx.core.events.events.NewEvent
|
||||||
import com.correx.core.events.events.SessionPausedEvent
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||||
import com.correx.core.events.events.SessionResumedEvent
|
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.stores.EventStore
|
import com.correx.core.events.stores.EventStore
|
||||||
import com.correx.core.events.types.EventId
|
import com.correx.core.events.types.EventId
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.core.sessions.DefaultSessionReducer
|
import com.correx.core.sessions.DefaultSessionReducer
|
||||||
import com.correx.core.sessions.SessionProjector
|
import com.correx.core.sessions.SessionProjector
|
||||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
||||||
@@ -30,14 +31,19 @@ class SessionReplayDeterminismTest {
|
|||||||
val store2 = InMemoryEventStore()
|
val store2 = InMemoryEventStore()
|
||||||
|
|
||||||
val events = mapOf(
|
val events = mapOf(
|
||||||
EventMetadata(EventId("start"), sessionId, Clock.System.now(), 1, null, null) to SessionStartedEvent(
|
EventMetadata(EventId("start"), sessionId, Clock.System.now(), 1, null, null) to WorkflowStartedEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
|
workflowId = "test",
|
||||||
|
startStageId = StageId("stage-1"),
|
||||||
),
|
),
|
||||||
EventMetadata(EventId("paused"), sessionId, Clock.System.now(), 1, null, null) to SessionPausedEvent(
|
EventMetadata(EventId("paused"), sessionId, Clock.System.now(), 1, null, null) to OrchestrationPausedEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
|
stageId = StageId("stage-1"),
|
||||||
|
reason = "APPROVAL_PENDING",
|
||||||
),
|
),
|
||||||
EventMetadata(EventId("resumed"), sessionId, Clock.System.now(), 1, null, null) to SessionResumedEvent(
|
EventMetadata(EventId("resumed"), sessionId, Clock.System.now(), 1, null, null) to OrchestrationResumedEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
|
stageId = StageId("stage-1"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import com.correx.core.artifacts.ArtifactState
|
import com.correx.core.artifacts.ArtifactState
|
||||||
import com.correx.core.artifacts.DefaultArtifactReducer
|
import com.correx.core.artifacts.DefaultArtifactReducer
|
||||||
import com.correx.core.events.events.ArtifactArchivedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactRejectedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactRelationshipAddedEvent
|
|
||||||
import com.correx.core.events.events.ArtifactSupersededEvent
|
|
||||||
import com.correx.core.events.events.ArtifactValidatedEvent
|
import com.correx.core.events.events.ArtifactValidatedEvent
|
||||||
import com.correx.core.events.events.ArtifactValidatingEvent
|
import com.correx.core.events.events.ArtifactValidatingEvent
|
||||||
import com.correx.core.events.types.ArtifactId
|
import com.correx.core.events.types.ArtifactId
|
||||||
import com.correx.core.events.types.ArtifactLifecyclePhase
|
import com.correx.core.events.types.ArtifactLifecyclePhase
|
||||||
import com.correx.core.events.types.ArtifactRelationshipType
|
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.testing.fixtures.EventFixtures.stored
|
import com.correx.testing.fixtures.EventFixtures.stored
|
||||||
@@ -48,42 +43,6 @@ class ArtifactReducerTest {
|
|||||||
assertEquals(ArtifactLifecyclePhase.VALIDATED, result.getOrThrow().phase)
|
assertEquals(ArtifactLifecyclePhase.VALIDATED, result.getOrThrow().phase)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `VALIDATING to REJECTED is valid`() {
|
|
||||||
val state = ArtifactState(phase = ArtifactLifecyclePhase.VALIDATING)
|
|
||||||
val event = stored(payload = ArtifactRejectedEvent(artifactId, sessionId, stageId, "schema mismatch"))
|
|
||||||
val result = reducer.reduce(state, event)
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
assertEquals(ArtifactLifecyclePhase.REJECTED, result.getOrThrow().phase)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `VALIDATED to SUPERSEDED is valid`() {
|
|
||||||
val state = ArtifactState(phase = ArtifactLifecyclePhase.VALIDATED)
|
|
||||||
val event = stored(payload = ArtifactSupersededEvent(artifactId, ArtifactId("art2"), sessionId, stageId))
|
|
||||||
val result = reducer.reduce(state, event)
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
assertEquals(ArtifactLifecyclePhase.SUPERSEDED, result.getOrThrow().phase)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `VALIDATED to ARCHIVED is valid`() {
|
|
||||||
val state = ArtifactState(phase = ArtifactLifecyclePhase.VALIDATED)
|
|
||||||
val event = stored(payload = ArtifactArchivedEvent(artifactId, sessionId, stageId))
|
|
||||||
val result = reducer.reduce(state, event)
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
assertEquals(ArtifactLifecyclePhase.ARCHIVED, result.getOrThrow().phase)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `REJECTED to ARCHIVED is valid`() {
|
|
||||||
val state = ArtifactState(phase = ArtifactLifecyclePhase.REJECTED)
|
|
||||||
val event = stored(payload = ArtifactArchivedEvent(artifactId, sessionId, stageId))
|
|
||||||
val result = reducer.reduce(state, event)
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
assertEquals(ArtifactLifecyclePhase.ARCHIVED, result.getOrThrow().phase)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CREATED to VALIDATED is invalid`() {
|
fun `CREATED to VALIDATED is invalid`() {
|
||||||
val state = ArtifactState(phase = ArtifactLifecyclePhase.CREATED)
|
val state = ArtifactState(phase = ArtifactLifecyclePhase.CREATED)
|
||||||
@@ -110,18 +69,4 @@ class ArtifactReducerTest {
|
|||||||
assertTrue(result.isFailure)
|
assertTrue(result.isFailure)
|
||||||
assertTrue(result.exceptionOrNull() is IllegalStateException)
|
assertTrue(result.exceptionOrNull() is IllegalStateException)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `relationship added event appends to lineage`() {
|
|
||||||
val state = ArtifactState(phase = ArtifactLifecyclePhase.CREATED)
|
|
||||||
val event = stored(
|
|
||||||
payload = ArtifactRelationshipAddedEvent(
|
|
||||||
artifactId, ArtifactId("art2"), ArtifactRelationshipType.DERIVED_FROM, sessionId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val result = reducer.reduce(state, event)
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
assertEquals(1, result.getOrThrow().lineage.relationships.size)
|
|
||||||
assertEquals(ArtifactRelationshipType.DERIVED_FROM, result.getOrThrow().lineage.relationships.first().type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import com.correx.core.context.ContextProjector
|
import com.correx.core.context.ContextProjector
|
||||||
import com.correx.core.context.DefaultContextReducer
|
import com.correx.core.context.DefaultContextReducer
|
||||||
import com.correx.core.events.events.ContextBuildingStartedEvent
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.events.ContextPackBuiltEvent
|
|
||||||
import com.correx.core.events.types.ContextPackId
|
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.testing.fixtures.EventFixtures.stored
|
import com.correx.testing.fixtures.EventFixtures.stored
|
||||||
@@ -14,7 +12,6 @@ class ContextProjectorTest {
|
|||||||
private val projector = ContextProjector(DefaultContextReducer())
|
private val projector = ContextProjector(DefaultContextReducer())
|
||||||
private val sessionId = SessionId("s1")
|
private val sessionId = SessionId("s1")
|
||||||
private val stageId = StageId("stage-1")
|
private val stageId = StageId("stage-1")
|
||||||
private val packId = ContextPackId("pack-1")
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial state has no packs and is not building`() {
|
fun `initial state has no packs and is not building`() {
|
||||||
@@ -26,8 +23,8 @@ class ContextProjectorTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `replay is deterministic`() {
|
fun `replay is deterministic`() {
|
||||||
val events = listOf(
|
val events = listOf(
|
||||||
stored(payload = ContextBuildingStartedEvent(sessionId, stageId)),
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId)),
|
||||||
stored(payload = ContextPackBuiltEvent(packId, sessionId, stageId, 100, 4000))
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId))
|
||||||
)
|
)
|
||||||
val state1 = events.fold(projector.initial()) { s, e -> projector.apply(s, e) }
|
val state1 = events.fold(projector.initial()) { s, e -> projector.apply(s, e) }
|
||||||
val state2 = events.fold(projector.initial()) { s, e -> projector.apply(s, e) }
|
val state2 = events.fold(projector.initial()) { s, e -> projector.apply(s, e) }
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import com.correx.core.context.DefaultContextReducer
|
import com.correx.core.context.DefaultContextReducer
|
||||||
import com.correx.core.context.state.ContextState
|
import com.correx.core.context.state.ContextState
|
||||||
import com.correx.core.events.events.ContextBuildingFailedEvent
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.events.ContextBuildingInterruptedEvent
|
|
||||||
import com.correx.core.events.events.ContextBuildingStartedEvent
|
|
||||||
import com.correx.core.events.events.ContextPackBuiltEvent
|
|
||||||
import com.correx.core.events.types.ContextPackId
|
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.testing.fixtures.EventFixtures.stored
|
import com.correx.testing.fixtures.EventFixtures.stored
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class DefaultContextReducerTest {
|
class DefaultContextReducerTest {
|
||||||
@@ -18,70 +13,49 @@ class DefaultContextReducerTest {
|
|||||||
private val reducer = DefaultContextReducer()
|
private val reducer = DefaultContextReducer()
|
||||||
private val sessionId = SessionId("s1")
|
private val sessionId = SessionId("s1")
|
||||||
private val stageId = StageId("stage-1")
|
private val stageId = StageId("stage-1")
|
||||||
private val packId = ContextPackId("pack-1")
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ContextBuildingStartedEvent sets buildingInProgress to true`() {
|
fun `WorkflowStartedEvent leaves buildingInProgress unchanged`() {
|
||||||
val state = reducer.reduce(
|
val state = reducer.reduce(
|
||||||
ContextState(),
|
ContextState(),
|
||||||
stored(payload = ContextBuildingStartedEvent(sessionId, stageId))
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId))
|
||||||
)
|
)
|
||||||
assertTrue(state.buildingInProgress)
|
assertFalse(state.buildingInProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ContextPackBuiltEvent records pack and clears buildingInProgress`() {
|
fun `WorkflowStartedEvent leaves builtPackIds unchanged`() {
|
||||||
val started = reducer.reduce(
|
val state = reducer.reduce(
|
||||||
ContextState(),
|
ContextState(),
|
||||||
stored(payload = ContextBuildingStartedEvent(sessionId, stageId))
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId))
|
||||||
)
|
)
|
||||||
val built = reducer.reduce(
|
assertEquals(0, state.builtPackIds.size)
|
||||||
started,
|
|
||||||
stored(payload = ContextPackBuiltEvent(packId, sessionId, stageId, 200, 4000))
|
|
||||||
)
|
|
||||||
assertFalse(built.buildingInProgress)
|
|
||||||
assertEquals(1, built.builtPackIds.size)
|
|
||||||
assertTrue(built.builtPackIds.contains(packId))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ContextBuildingFailedEvent clears buildingInProgress`() {
|
fun `WorkflowStartedEvent leaves buildingInProgress false`() {
|
||||||
val started = reducer.reduce(
|
val started = reducer.reduce(
|
||||||
ContextState(),
|
ContextState(),
|
||||||
stored(payload = ContextBuildingStartedEvent(sessionId, stageId))
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId))
|
||||||
)
|
)
|
||||||
val failed = reducer.reduce(
|
val after = reducer.reduce(
|
||||||
started,
|
started,
|
||||||
stored(payload = ContextBuildingFailedEvent(sessionId, stageId, "timeout"))
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId))
|
||||||
)
|
)
|
||||||
assertFalse(failed.buildingInProgress)
|
assertFalse(after.buildingInProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ContextBuildingInterruptedEvent clears buildingInProgress and sets interrupted`() {
|
fun `WorkflowStartedEvent leaves interrupted unchanged`() {
|
||||||
val started = reducer.reduce(
|
val started = reducer.reduce(
|
||||||
ContextState(),
|
ContextState(),
|
||||||
stored(payload = ContextBuildingStartedEvent(sessionId, stageId))
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId))
|
||||||
)
|
)
|
||||||
val interrupted = reducer.reduce(
|
val after = reducer.reduce(
|
||||||
started,
|
started,
|
||||||
stored(payload = ContextBuildingInterruptedEvent(sessionId, stageId))
|
stored(payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = stageId))
|
||||||
)
|
)
|
||||||
assertFalse(interrupted.buildingInProgress)
|
assertFalse(after.interrupted)
|
||||||
assertTrue(interrupted.interrupted)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `ContextBuildingFailedEvent does not set interrupted`() {
|
|
||||||
val started = reducer.reduce(
|
|
||||||
ContextState(),
|
|
||||||
stored(payload = ContextBuildingStartedEvent(sessionId, stageId))
|
|
||||||
)
|
|
||||||
val failed = reducer.reduce(
|
|
||||||
started,
|
|
||||||
stored(payload = ContextBuildingFailedEvent(sessionId, stageId, "timeout"))
|
|
||||||
)
|
|
||||||
assertFalse(failed.interrupted)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import com.correx.core.events.events.SessionCompletedEvent
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||||
import com.correx.core.events.events.SessionFailedEvent
|
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||||
import com.correx.core.events.events.SessionPausedEvent
|
|
||||||
import com.correx.core.events.events.SessionResumedEvent
|
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
|
||||||
import com.correx.core.events.events.StageCompletedEvent
|
import com.correx.core.events.events.StageCompletedEvent
|
||||||
import com.correx.core.events.events.StageFailedEvent
|
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.events.TransitionExecutedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowCompletedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowFailedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.core.events.types.TransitionId
|
import com.correx.core.events.types.TransitionId
|
||||||
@@ -25,14 +24,29 @@ class DefaultSessionReducerTest {
|
|||||||
private val sessionId = SessionId("session-1")
|
private val sessionId = SessionId("session-1")
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `SessionStartedEvent sets ACTIVE status`() {
|
fun `WorkflowStartedEvent leaves CREATED status`() {
|
||||||
val state = initialState()
|
val state = initialState()
|
||||||
|
|
||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = state,
|
state = state,
|
||||||
event = stored(
|
event = stored(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
payload = SessionStartedEvent(sessionId)
|
payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = StageId("st-1"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(SessionStatus.CREATED, result.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `OrchestrationPausedEvent leaves ACTIVE status`() {
|
||||||
|
val state = activeState()
|
||||||
|
|
||||||
|
val result = reducer.reduce(
|
||||||
|
state = state,
|
||||||
|
event = stored(
|
||||||
|
sessionId = sessionId,
|
||||||
|
payload = OrchestrationPausedEvent(sessionId, stageId = StageId("st-1"), reason = "APPROVAL_PENDING")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,14 +54,14 @@ class DefaultSessionReducerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `SessionPausedEvent sets PAUSED status`() {
|
fun `OrchestrationResumedEvent leaves PAUSED status`() {
|
||||||
val state = activeState()
|
val state = pausedState()
|
||||||
|
|
||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = state,
|
state = state,
|
||||||
event = stored(
|
event = stored(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
payload = SessionPausedEvent(sessionId)
|
payload = OrchestrationResumedEvent(sessionId, stageId = StageId("st-1"))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,14 +69,14 @@ class DefaultSessionReducerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `SessionResumedEvent sets ACTIVE status`() {
|
fun `WorkflowCompletedEvent leaves ACTIVE status`() {
|
||||||
val state = pausedState()
|
val state = activeState()
|
||||||
|
|
||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = state,
|
state = state,
|
||||||
event = stored(
|
event = stored(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
payload = SessionResumedEvent(sessionId)
|
payload = WorkflowCompletedEvent(sessionId, terminalStageId = StageId("st-1"), totalStages = 0)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,46 +84,32 @@ class DefaultSessionReducerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `SessionCompletedEvent sets COMPLETED status`() {
|
fun `WorkflowFailedEvent leaves ACTIVE status`() {
|
||||||
val state = activeState()
|
val state = activeState()
|
||||||
|
|
||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = state,
|
state = state,
|
||||||
event = stored(
|
event = stored(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
payload = SessionCompletedEvent(sessionId)
|
payload = WorkflowFailedEvent(sessionId, stageId = StageId("st-1"), reason = "error", retryExhausted = false)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(SessionStatus.COMPLETED, result.status)
|
assertEquals(SessionStatus.ACTIVE, result.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `SessionFailedEvent sets FAILED status`() {
|
fun `TransitionExecutedEvent from StageStartedEvent sets ACTIVE status`() {
|
||||||
val state = activeState()
|
|
||||||
|
|
||||||
val result = reducer.reduce(
|
|
||||||
state = state,
|
|
||||||
event = stored(
|
|
||||||
sessionId = sessionId,
|
|
||||||
payload = SessionFailedEvent(sessionId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(SessionStatus.FAILED, result.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `StageStartedEvent sets ACTIVE status`() {
|
|
||||||
val state = pausedState()
|
val state = pausedState()
|
||||||
|
|
||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = state,
|
state = state,
|
||||||
event = stored(
|
event = stored(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
payload = StageStartedEvent(
|
payload = TransitionExecutedEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
stageId = StageId("stage-a"),
|
from = StageId("st-1"),
|
||||||
|
to = StageId("st-1"),
|
||||||
transitionId = TransitionId("transition-a")
|
transitionId = TransitionId("transition-a")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -184,7 +184,7 @@ class DefaultSessionReducerTest {
|
|||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = initialState(),
|
state = initialState(),
|
||||||
event = stored(
|
event = stored(
|
||||||
payload = SessionStartedEvent(sessionId),
|
payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = StageId("st-1")),
|
||||||
timestamp = timestamp
|
timestamp = timestamp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -204,7 +204,7 @@ class DefaultSessionReducerTest {
|
|||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = state,
|
state = state,
|
||||||
event = stored(
|
event = stored(
|
||||||
payload = SessionPausedEvent(sessionId),
|
payload = OrchestrationPausedEvent(sessionId, stageId = StageId("st-1"), reason = "APPROVAL_PENDING"),
|
||||||
timestamp = updatedAt
|
timestamp = updatedAt
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -219,7 +219,7 @@ class DefaultSessionReducerTest {
|
|||||||
val result = reducer.reduce(
|
val result = reducer.reduce(
|
||||||
state = activeState(),
|
state = activeState(),
|
||||||
event = stored(
|
event = stored(
|
||||||
payload = SessionPausedEvent(sessionId),
|
payload = OrchestrationPausedEvent(sessionId, stageId = StageId("st-1"), reason = "APPROVAL_PENDING"),
|
||||||
timestamp = timestamp
|
timestamp = timestamp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import com.correx.core.events.events.SessionCompletedEvent
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||||
import com.correx.core.events.events.SessionPausedEvent
|
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||||
import com.correx.core.events.events.SessionResumedEvent
|
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
|
||||||
import com.correx.core.events.events.StageCompletedEvent
|
import com.correx.core.events.events.StageCompletedEvent
|
||||||
import com.correx.core.events.events.StageFailedEvent
|
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.events.TransitionExecutedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowCompletedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.types.EventId
|
import com.correx.core.events.types.EventId
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
@@ -26,17 +25,17 @@ class SessionProjectorTest {
|
|||||||
var state = projector.initial()
|
var state = projector.initial()
|
||||||
|
|
||||||
val events = listOf(
|
val events = listOf(
|
||||||
stored(eventId = EventId("e1"), payload = SessionStartedEvent(SessionId("s1"))),
|
stored(eventId = EventId("e1"), payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))),
|
||||||
stored(eventId = EventId("e2"), payload = SessionPausedEvent(SessionId("s1"))),
|
stored(eventId = EventId("e2"), payload = OrchestrationPausedEvent(SessionId("s1"), stageId = StageId("st-1"), reason = "APPROVAL_PENDING")),
|
||||||
stored(eventId = EventId("e3"), payload = SessionResumedEvent(SessionId("s1"))),
|
stored(eventId = EventId("e3"), payload = OrchestrationResumedEvent(SessionId("s1"), stageId = StageId("st-1"))),
|
||||||
stored(eventId = EventId("e4"), payload = SessionCompletedEvent(SessionId("s1")))
|
stored(eventId = EventId("e4"), payload = WorkflowCompletedEvent(SessionId("s1"), terminalStageId = StageId("st-1"), totalStages = 0))
|
||||||
)
|
)
|
||||||
|
|
||||||
events.forEach {
|
events.forEach {
|
||||||
state = projector.apply(state, it)
|
state = projector.apply(state, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(SessionStatus.COMPLETED, state.status)
|
assertEquals(SessionStatus.CREATED, state.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -46,13 +45,14 @@ class SessionProjectorTest {
|
|||||||
val events = listOf(
|
val events = listOf(
|
||||||
stored(
|
stored(
|
||||||
eventId = EventId("e1"),
|
eventId = EventId("e1"),
|
||||||
payload = SessionStartedEvent(SessionId("s1"))
|
payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))
|
||||||
),
|
),
|
||||||
stored(
|
stored(
|
||||||
eventId = EventId("e2"),
|
eventId = EventId("e2"),
|
||||||
payload = StageStartedEvent(
|
payload = TransitionExecutedEvent(
|
||||||
sessionId = SessionId("s1"),
|
sessionId = SessionId("s1"),
|
||||||
stageId = StageId("draft"),
|
from = StageId("draft"),
|
||||||
|
to = StageId("draft"),
|
||||||
transitionId = TransitionId("t1")
|
transitionId = TransitionId("t1")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -81,7 +81,7 @@ class SessionProjectorTest {
|
|||||||
val events = listOf(
|
val events = listOf(
|
||||||
stored(
|
stored(
|
||||||
eventId = EventId("e1"),
|
eventId = EventId("e1"),
|
||||||
payload = SessionStartedEvent(SessionId("s1"))
|
payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))
|
||||||
),
|
),
|
||||||
stored(
|
stored(
|
||||||
eventId = EventId("e2"),
|
eventId = EventId("e2"),
|
||||||
@@ -94,9 +94,10 @@ class SessionProjectorTest {
|
|||||||
),
|
),
|
||||||
stored(
|
stored(
|
||||||
eventId = EventId("e3"),
|
eventId = EventId("e3"),
|
||||||
payload = StageStartedEvent(
|
payload = TransitionExecutedEvent(
|
||||||
sessionId = SessionId("s1"),
|
sessionId = SessionId("s1"),
|
||||||
stageId = StageId("b"),
|
from = StageId("b"),
|
||||||
|
to = StageId("b"),
|
||||||
transitionId = TransitionId("t1")
|
transitionId = TransitionId("t1")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -123,11 +124,11 @@ class SessionProjectorTest {
|
|||||||
val events = listOf(
|
val events = listOf(
|
||||||
stored(
|
stored(
|
||||||
eventId = EventId("e1"),
|
eventId = EventId("e1"),
|
||||||
payload = SessionStartedEvent(SessionId("s1"))
|
payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))
|
||||||
),
|
),
|
||||||
stored(
|
stored(
|
||||||
eventId = EventId("e2"),
|
eventId = EventId("e2"),
|
||||||
payload = SessionCompletedEvent(SessionId("s1"))
|
payload = WorkflowCompletedEvent(SessionId("s1"), terminalStageId = StageId("st-1"), totalStages = 0)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import com.correx.core.events.events.EventMetadata
|
import com.correx.core.events.events.EventMetadata
|
||||||
import com.correx.core.events.events.NewEvent
|
import com.correx.core.events.events.NewEvent
|
||||||
import com.correx.core.events.events.SessionCompletedEvent
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||||
import com.correx.core.events.events.SessionPausedEvent
|
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||||
import com.correx.core.events.events.SessionResumedEvent
|
import com.correx.core.events.events.WorkflowCompletedEvent
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.types.EventId
|
import com.correx.core.events.types.EventId
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.core.sessions.DefaultSessionReducer
|
import com.correx.core.sessions.DefaultSessionReducer
|
||||||
import com.correx.core.sessions.SessionProjector
|
import com.correx.core.sessions.SessionProjector
|
||||||
import com.correx.core.sessions.SessionStatus
|
import com.correx.core.sessions.SessionStatus
|
||||||
@@ -26,14 +27,21 @@ class SessionReplayTest {
|
|||||||
val sessionId = SessionId("s1")
|
val sessionId = SessionId("s1")
|
||||||
|
|
||||||
val metadataToPayload = mapOf(
|
val metadataToPayload = mapOf(
|
||||||
EventMetadata(EventId("start"), sessionId, Clock.System.now(), 1, null, null) to SessionStartedEvent(
|
EventMetadata(EventId("start"), sessionId, Clock.System.now(), 1, null, null) to WorkflowStartedEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
|
workflowId = "test",
|
||||||
|
startStageId = StageId("stage-1"),
|
||||||
),
|
),
|
||||||
EventMetadata(EventId("paused"), sessionId, Clock.System.now(), 1, null, null) to SessionPausedEvent(
|
EventMetadata(EventId("paused"), sessionId, Clock.System.now(), 1, null, null) to OrchestrationPausedEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
|
stageId = StageId("stage-1"),
|
||||||
|
reason = "APPROVAL_PENDING",
|
||||||
),
|
),
|
||||||
EventMetadata(EventId("resumed"), sessionId, Clock.System.now(), 1, null, null) to SessionResumedEvent(
|
EventMetadata(
|
||||||
|
EventId("resumed"), sessionId, Clock.System.now(), 1, null, null,
|
||||||
|
) to OrchestrationResumedEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
|
stageId = StageId("stage-1"),
|
||||||
),
|
),
|
||||||
EventMetadata(
|
EventMetadata(
|
||||||
EventId("completed"),
|
EventId("completed"),
|
||||||
@@ -42,7 +50,7 @@ class SessionReplayTest {
|
|||||||
1,
|
1,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
) to SessionCompletedEvent(sessionId),
|
) to WorkflowCompletedEvent(sessionId, terminalStageId = StageId("stage-1"), totalStages = 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
metadataToPayload.map { (meta, payload) ->
|
metadataToPayload.map { (meta, payload) ->
|
||||||
@@ -51,6 +59,6 @@ class SessionReplayTest {
|
|||||||
|
|
||||||
val state = replayer.rebuild(sessionId)
|
val state = replayer.rebuild(sessionId)
|
||||||
|
|
||||||
assertEquals(SessionStatus.COMPLETED, state.status)
|
assertEquals(SessionStatus.CREATED, state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import com.correx.core.events.events.SessionCompletedEvent
|
|
||||||
import com.correx.core.events.events.SessionStartedEvent
|
|
||||||
import com.correx.core.events.events.StageCompletedEvent
|
import com.correx.core.events.events.StageCompletedEvent
|
||||||
import com.correx.core.events.events.StageFailedEvent
|
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.events.TransitionExecutedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowCompletedEvent
|
||||||
|
import com.correx.core.events.events.WorkflowStartedEvent
|
||||||
import com.correx.core.events.types.EventId
|
import com.correx.core.events.types.EventId
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
@@ -32,7 +31,7 @@ class TransitionReplayIntegrationTest {
|
|||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = EventId("event-1"),
|
eventId = EventId("event-1"),
|
||||||
payload = SessionStartedEvent(sessionId)
|
payload = WorkflowStartedEvent(sessionId, workflowId = "test", startStageId = StageId("stage-1"))
|
||||||
),
|
),
|
||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
@@ -47,9 +46,10 @@ class TransitionReplayIntegrationTest {
|
|||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = EventId("event-3"),
|
eventId = EventId("event-3"),
|
||||||
payload = StageStartedEvent(
|
payload = TransitionExecutedEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
stageId = StageId("review"),
|
from = StageId("review"),
|
||||||
|
to = StageId("review"),
|
||||||
transitionId = TransitionId("transition-1")
|
transitionId = TransitionId("transition-1")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -65,7 +65,7 @@ class TransitionReplayIntegrationTest {
|
|||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = EventId("event-5"),
|
eventId = EventId("event-5"),
|
||||||
payload = SessionCompletedEvent(sessionId)
|
payload = WorkflowCompletedEvent(sessionId, terminalStageId = StageId("stage-1"), totalStages = 1)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -80,7 +80,7 @@ class TransitionReplayIntegrationTest {
|
|||||||
val state = replayer.rebuild(sessionId)
|
val state = replayer.rebuild(sessionId)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
SessionStatus.COMPLETED,
|
SessionStatus.ACTIVE,
|
||||||
state.status
|
state.status
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ class TransitionReplayIntegrationTest {
|
|||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = EventId("event-1"),
|
eventId = EventId("event-1"),
|
||||||
payload = SessionStartedEvent(sessionId)
|
payload = WorkflowStartedEvent(sessionId, workflowId = "test", startStageId = StageId("stage-1"))
|
||||||
),
|
),
|
||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
@@ -110,9 +110,10 @@ class TransitionReplayIntegrationTest {
|
|||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = EventId("event-3"),
|
eventId = EventId("event-3"),
|
||||||
payload = StageStartedEvent(
|
payload = TransitionExecutedEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
stageId = StageId("review"),
|
from = StageId("review"),
|
||||||
|
to = StageId("review"),
|
||||||
transitionId = TransitionId("transition-1")
|
transitionId = TransitionId("transition-1")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -150,7 +151,7 @@ class TransitionReplayIntegrationTest {
|
|||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = EventId("event-1"),
|
eventId = EventId("event-1"),
|
||||||
payload = SessionStartedEvent(sessionId)
|
payload = WorkflowStartedEvent(sessionId, workflowId = "test", startStageId = StageId("stage-1"))
|
||||||
),
|
),
|
||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
@@ -165,9 +166,10 @@ class TransitionReplayIntegrationTest {
|
|||||||
newEvent(
|
newEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = EventId("event-3"),
|
eventId = EventId("event-3"),
|
||||||
payload = StageStartedEvent(
|
payload = TransitionExecutedEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
stageId = StageId("review"),
|
from = StageId("review"),
|
||||||
|
to = StageId("review"),
|
||||||
transitionId = TransitionId("transition-1")
|
transitionId = TransitionId("transition-1")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import com.correx.core.events.events.EventPayload
|
import com.correx.core.events.events.EventPayload
|
||||||
import com.correx.core.events.events.StageCompletedEvent
|
import com.correx.core.events.events.StageCompletedEvent
|
||||||
import com.correx.core.events.events.StageFailedEvent
|
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.events.TransitionExecutedEvent
|
||||||
import com.correx.core.events.serialization.JsonEventSerializer
|
import com.correx.core.events.serialization.JsonEventSerializer
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
@@ -13,10 +12,11 @@ import org.junit.jupiter.api.Test
|
|||||||
class TransitionEventSerializationTest {
|
class TransitionEventSerializationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `StageStartedEvent serializes and deserializes`() {
|
fun `TransitionExecutedEvent serializes and deserializes (stage start equivalent)`() {
|
||||||
val event = StageStartedEvent(
|
val event = TransitionExecutedEvent(
|
||||||
sessionId = SessionId("session-1"),
|
sessionId = SessionId("session-1"),
|
||||||
stageId = StageId("stage-a"),
|
from = StageId("from"),
|
||||||
|
to = StageId("stage-a"),
|
||||||
transitionId = TransitionId("transition-a")
|
transitionId = TransitionId("transition-a")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user