feat(events,sessions): ProjectProfileBoundEvent reduced into session state
This commit is contained in:
@@ -27,3 +27,13 @@ data class OperatorProfileBoundEvent(
|
|||||||
val preferredModels: List<String>,
|
val preferredModels: List<String>,
|
||||||
val conventions: List<String>,
|
val conventions: List<String>,
|
||||||
) : EventPayload
|
) : EventPayload
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("ProjectProfileBound")
|
||||||
|
data class ProjectProfileBoundEvent(
|
||||||
|
val sessionId: SessionId,
|
||||||
|
val workspaceRoot: String,
|
||||||
|
val about: String,
|
||||||
|
val conventions: List<String>,
|
||||||
|
val commands: Map<String, String>,
|
||||||
|
) : EventPayload
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.correx.core.events.events.ChatSessionStartedEvent
|
|||||||
import com.correx.core.events.events.ChatTurnEvent
|
import com.correx.core.events.events.ChatTurnEvent
|
||||||
import com.correx.core.events.events.RouterNarrationEvent
|
import com.correx.core.events.events.RouterNarrationEvent
|
||||||
import com.correx.core.events.events.OperatorProfileBoundEvent
|
import com.correx.core.events.events.OperatorProfileBoundEvent
|
||||||
|
import com.correx.core.events.events.ProjectProfileBoundEvent
|
||||||
import com.correx.core.events.events.SessionWorkspaceBoundEvent
|
import com.correx.core.events.events.SessionWorkspaceBoundEvent
|
||||||
import com.correx.core.events.events.ContextTruncatedEvent
|
import com.correx.core.events.events.ContextTruncatedEvent
|
||||||
import com.correx.core.events.events.EventPayload
|
import com.correx.core.events.events.EventPayload
|
||||||
@@ -100,6 +101,7 @@ val eventModule = SerializersModule {
|
|||||||
subclass(RouterNarrationEvent::class)
|
subclass(RouterNarrationEvent::class)
|
||||||
subclass(SessionWorkspaceBoundEvent::class)
|
subclass(SessionWorkspaceBoundEvent::class)
|
||||||
subclass(OperatorProfileBoundEvent::class)
|
subclass(OperatorProfileBoundEvent::class)
|
||||||
|
subclass(ProjectProfileBoundEvent::class)
|
||||||
subclass(L3MemoryRetrievedEvent::class)
|
subclass(L3MemoryRetrievedEvent::class)
|
||||||
subclass(ContextTruncatedEvent::class)
|
subclass(ContextTruncatedEvent::class)
|
||||||
subclass(ExecutionPlanLockedEvent::class)
|
subclass(ExecutionPlanLockedEvent::class)
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.correx.core.events.serialization
|
||||||
|
|
||||||
|
import com.correx.core.events.events.EventPayload
|
||||||
|
import com.correx.core.events.events.ProjectProfileBoundEvent
|
||||||
|
import com.correx.core.events.types.SessionId
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class ProjectProfileBoundEventSerializationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ProjectProfileBoundEvent round-trips as polymorphic EventPayload`() {
|
||||||
|
val sample: EventPayload = ProjectProfileBoundEvent(
|
||||||
|
sessionId = SessionId("s1"),
|
||||||
|
workspaceRoot = "/repo",
|
||||||
|
about = "Event-sourced orchestration kernel",
|
||||||
|
conventions = listOf("no bare try-catch", "events are the only source of truth"),
|
||||||
|
commands = mapOf("build" to "./gradlew build", "test" to "./gradlew check"),
|
||||||
|
)
|
||||||
|
val encoded = eventJson.encodeToString(EventPayload.serializer(), sample)
|
||||||
|
assertTrue(encoded.contains("\"type\":\"ProjectProfileBound\""), "SerialName must be present: $encoded")
|
||||||
|
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
|
||||||
|
assertEquals(sample, decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `round-trips with empty fields`() {
|
||||||
|
val sample: EventPayload = ProjectProfileBoundEvent(
|
||||||
|
sessionId = SessionId("s0"),
|
||||||
|
workspaceRoot = "/empty",
|
||||||
|
about = "",
|
||||||
|
conventions = emptyList(),
|
||||||
|
commands = emptyMap(),
|
||||||
|
)
|
||||||
|
val encoded = eventJson.encodeToString(EventPayload.serializer(), sample)
|
||||||
|
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
|
||||||
|
assertEquals(sample, decoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.correx.core.sessions
|
||||||
|
|
||||||
|
data class BoundProjectProfile(
|
||||||
|
val about: String,
|
||||||
|
val conventions: List<String>,
|
||||||
|
val commands: Map<String, String>,
|
||||||
|
)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.correx.core.sessions
|
package com.correx.core.sessions
|
||||||
|
|
||||||
import com.correx.core.events.events.OperatorProfileBoundEvent
|
import com.correx.core.events.events.OperatorProfileBoundEvent
|
||||||
|
import com.correx.core.events.events.ProjectProfileBoundEvent
|
||||||
import com.correx.core.events.events.SessionWorkspaceBoundEvent
|
import com.correx.core.events.events.SessionWorkspaceBoundEvent
|
||||||
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
|
||||||
@@ -51,12 +52,22 @@ class DefaultSessionReducer : SessionReducer {
|
|||||||
else -> state.boundProfile
|
else -> state.boundProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val boundProjectProfile = when (payload) {
|
||||||
|
is ProjectProfileBoundEvent -> BoundProjectProfile(
|
||||||
|
about = payload.about,
|
||||||
|
conventions = payload.conventions,
|
||||||
|
commands = payload.commands,
|
||||||
|
)
|
||||||
|
else -> state.boundProjectProfile
|
||||||
|
}
|
||||||
|
|
||||||
return state.copy(
|
return state.copy(
|
||||||
status = newStatus,
|
status = newStatus,
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
updatedAt = event.metadata.timestamp,
|
updatedAt = event.metadata.timestamp,
|
||||||
boundWorkspace = boundWorkspace,
|
boundWorkspace = boundWorkspace,
|
||||||
boundProfile = boundProfile,
|
boundProfile = boundProfile,
|
||||||
|
boundProjectProfile = boundProjectProfile,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ data class SessionState(
|
|||||||
val invalidTransitions: Int = 0,
|
val invalidTransitions: Int = 0,
|
||||||
val boundWorkspace: BoundWorkspace? = null,
|
val boundWorkspace: BoundWorkspace? = null,
|
||||||
val boundProfile: BoundProfile? = null,
|
val boundProfile: BoundProfile? = null,
|
||||||
|
val boundProjectProfile: BoundProjectProfile? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ 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
|
||||||
|
import com.correx.core.events.events.ProjectProfileBoundEvent
|
||||||
|
import com.correx.core.sessions.BoundProjectProfile
|
||||||
import com.correx.core.sessions.BoundWorkspace
|
import com.correx.core.sessions.BoundWorkspace
|
||||||
import com.correx.core.sessions.DefaultSessionReducer
|
import com.correx.core.sessions.DefaultSessionReducer
|
||||||
import com.correx.core.sessions.SessionState
|
import com.correx.core.sessions.SessionState
|
||||||
@@ -278,6 +280,53 @@ class DefaultSessionReducerTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ProjectProfileBoundEvent reduces into boundProjectProfile`() {
|
||||||
|
val result = reducer.reduce(
|
||||||
|
state = initialState(),
|
||||||
|
event = stored(
|
||||||
|
sessionId = sessionId,
|
||||||
|
payload = ProjectProfileBoundEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
|
workspaceRoot = "/repo",
|
||||||
|
about = "kernel project",
|
||||||
|
conventions = listOf("no bare try-catch"),
|
||||||
|
commands = mapOf("test" to "./gradlew check"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
BoundProjectProfile(
|
||||||
|
about = "kernel project",
|
||||||
|
conventions = listOf("no bare try-catch"),
|
||||||
|
commands = mapOf("test" to "./gradlew check"),
|
||||||
|
),
|
||||||
|
result.boundProjectProfile,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `boundProjectProfile is preserved by subsequent unrelated events`() {
|
||||||
|
val withProfile = initialState().copy(
|
||||||
|
boundProjectProfile = BoundProjectProfile(
|
||||||
|
about = "kernel project",
|
||||||
|
conventions = listOf("no bare try-catch"),
|
||||||
|
commands = emptyMap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = reducer.reduce(
|
||||||
|
state = withProfile,
|
||||||
|
event = stored(
|
||||||
|
sessionId = sessionId,
|
||||||
|
payload = WorkflowStartedEvent(sessionId, workflowId = "wf", startStageId = StageId("s1")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals("kernel project", result.boundProjectProfile?.about)
|
||||||
|
}
|
||||||
|
|
||||||
private fun initialState() =
|
private fun initialState() =
|
||||||
SessionState(
|
SessionState(
|
||||||
status = SessionStatus.CREATED
|
status = SessionStatus.CREATED
|
||||||
|
|||||||
Reference in New Issue
Block a user