epic-12: after epic audit and init commit

This commit is contained in:
2026-05-16 11:42:00 +04:00
commit c77277af0b
461 changed files with 28958 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
id 'org.jetbrains.kotlin.plugin.serialization'
}
dependencies {
testImplementation(project(":core:events"))
testImplementation(project(":core:sessions"))
testImplementation(project(":core:transitions"))
testImplementation(project(":core:validation"))
testImplementation(project(":core:approvals"))
testImplementation(project(":testing:fixtures"))
testImplementation(project(":infrastructure:persistence"))
}
@@ -0,0 +1,3 @@
package com.correx.testing.replay
object Module
@@ -0,0 +1,59 @@
import com.correx.core.approvals.Tier
import com.correx.core.approvals.domain.DefaultApprovalEngine
import com.correx.core.approvals.model.ApprovalContext
import com.correx.core.approvals.model.ApprovalGrant
import com.correx.core.approvals.model.ApprovalScopeIdentity
import com.correx.core.approvals.GrantScope
import com.correx.core.events.types.ApprovalDecisionId
import com.correx.core.events.types.GrantId
import com.correx.core.events.types.SessionId
import com.correx.core.sessions.ApprovalMode
import com.correx.testing.fixtures.request
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class ApprovalReplayTest {
@Test
fun `replaying same request with same inputs yields identical decision`() {
val engine1 = DefaultApprovalEngine()
val engine2 = DefaultApprovalEngine()
val req = request("req1", Tier.T2)
val ctx = ApprovalContext(
ApprovalScopeIdentity(SessionId("s1"), null, null),
ApprovalMode.AUTO
)
val grants = listOf(
ApprovalGrant(
GrantId("g1"),
GrantScope.SESSION,
setOf(Tier.T2),
"reason",
Instant.parse("2026-01-01T00:00:00Z")
)
)
val now = Instant.parse("2026-01-01T00:00:01Z")
val d1 = engine1.evaluate(req, ctx, grants, now)
val d2 = engine2.evaluate(req, ctx, grants, now)
assertEquals(d1, d2)
}
@Test
fun `decision ID determinism holds over multiple replays`() {
val engine = DefaultApprovalEngine()
val req = request("fixed", Tier.T3)
val ctx = ApprovalContext(
ApprovalScopeIdentity(SessionId("s"), null, null),
ApprovalMode.PROMPT
)
val now = Instant.parse("2026-01-01T00:00:01Z")
val d1 = engine.evaluate(req, ctx, emptyList(), now)
val d2 = engine.evaluate(req, ctx, emptyList(), now)
assertEquals(d1.id, d2.id)
assertEquals(ApprovalDecisionId("decision:fixed"), d1.id)
}
}
@@ -0,0 +1,24 @@
import com.correx.core.events.types.SessionId
import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.SessionProjector
import com.correx.core.sessions.SessionStatus
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class SessionEmptyReplayTest {
@Test
fun `empty session returns initial state`() {
val store = InMemoryEventStore()
val replayer = DefaultEventReplayer(
store,
SessionProjector(DefaultSessionReducer())
)
val state = replayer.rebuild(SessionId("missing"))
assertEquals(SessionStatus.CREATED, state.status)
}
}
@@ -0,0 +1,57 @@
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.SessionCompletedEvent
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.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.SessionProjector
import com.correx.core.sessions.SessionStatus
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.datetime.Clock
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class SessionReplayTest {
private val store = InMemoryEventStore()
private val projector = SessionProjector(DefaultSessionReducer())
private val replayer = DefaultEventReplayer(store, projector)
@Test
fun `rebuild session from event stream`() {
val sessionId = SessionId("s1")
val metadataToPayload = mapOf<EventMetadata, EventPayload>(
EventMetadata(EventId("start"), sessionId, Clock.System.now(), 1, null, null) to SessionStartedEvent(
sessionId
),
EventMetadata(EventId("paused"), sessionId, Clock.System.now(), 1, null, null) to SessionPausedEvent(
sessionId
),
EventMetadata(EventId("resumed"), sessionId, Clock.System.now(), 1, null, null) to SessionResumedEvent(
sessionId
),
EventMetadata(
EventId("completed"),
sessionId,
Clock.System.now(),
1,
null,
null
) to SessionCompletedEvent(sessionId),
)
metadataToPayload.map { (meta, payload) ->
store.append(NewEvent(meta, payload))
}
val state = replayer.rebuild(sessionId)
assertEquals(SessionStatus.COMPLETED, state.status)
}
}
@@ -0,0 +1,209 @@
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.StageFailedEvent
import com.correx.core.events.events.StageStartedEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.TransitionId
import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.SessionProjector
import com.correx.core.sessions.SessionStatus
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore
import com.correx.testing.fixtures.EventFixtures.newEvent
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class TransitionReplayIntegrationTest {
private val sessionId = SessionId("session-1")
@Test
fun `workflow replay reconstructs COMPLETED session`() {
val store = InMemoryEventStore()
store.appendAll(
listOf(
newEvent(
sessionId = sessionId,
eventId = EventId("event-1"),
payload = SessionStartedEvent(sessionId)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-2"),
payload = TransitionExecutedEvent(
sessionId = sessionId,
from = StageId("draft"),
to = StageId("review"),
transitionId = TransitionId("transition-1")
)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-3"),
payload = StageStartedEvent(
sessionId = sessionId,
stageId = StageId("review"),
transitionId = TransitionId("transition-1")
)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-4"),
payload = StageCompletedEvent(
sessionId = sessionId,
stageId = StageId("review"),
transitionId = TransitionId("transition-1")
)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-5"),
payload = SessionCompletedEvent(sessionId)
)
)
)
val replayer = DefaultEventReplayer(
store = store,
projection = SessionProjector(
reducer = DefaultSessionReducer()
)
)
val state = replayer.rebuild(sessionId)
assertEquals(
SessionStatus.COMPLETED,
state.status
)
}
@Test
fun `workflow replay reconstructs FAILED session`() {
val store = InMemoryEventStore()
store.appendAll(
listOf(
newEvent(
sessionId = sessionId,
eventId = EventId("event-1"),
payload = SessionStartedEvent(sessionId)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-2"),
payload = TransitionExecutedEvent(
sessionId = sessionId,
from = StageId("draft"),
to = StageId("review"),
transitionId = TransitionId("transition-1")
)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-3"),
payload = StageStartedEvent(
sessionId = sessionId,
stageId = StageId("review"),
transitionId = TransitionId("transition-1")
)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-4"),
payload = StageFailedEvent(
sessionId = sessionId,
stageId = StageId("review"),
transitionId = TransitionId("transition-1"),
reason = "validation failed"
)
)
)
)
val replayer = DefaultEventReplayer(
store = store,
projection = SessionProjector(
reducer = DefaultSessionReducer()
)
)
val state = replayer.rebuild(sessionId)
assertEquals(
SessionStatus.FAILED,
state.status
)
}
@Test
fun `replay is deterministic for identical event stream`() {
val events = listOf(
newEvent(
sessionId = sessionId,
eventId = EventId("event-1"),
payload = SessionStartedEvent(sessionId)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-2"),
payload = TransitionExecutedEvent(
sessionId = sessionId,
from = StageId("draft"),
to = StageId("review"),
transitionId = TransitionId("transition-1")
)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-3"),
payload = StageStartedEvent(
sessionId = sessionId,
stageId = StageId("review"),
transitionId = TransitionId("transition-1")
)
),
newEvent(
sessionId = sessionId,
eventId = EventId("event-4"),
payload = StageCompletedEvent(
sessionId = sessionId,
stageId = StageId("review"),
transitionId = TransitionId("transition-1")
)
)
)
val store1 = InMemoryEventStore()
val store2 = InMemoryEventStore()
store1.appendAll(events)
store2.appendAll(events)
val projection = SessionProjector(
reducer = DefaultSessionReducer()
)
val replayer1 = DefaultEventReplayer(
store = store1,
projection = projection
)
val replayer2 = DefaultEventReplayer(
store = store2,
projection = projection
)
val state1 = replayer1.rebuild(sessionId)
val state2 = replayer2.rebuild(sessionId)
assertEquals(state1, state2)
}
}
@@ -0,0 +1,32 @@
import com.correx.core.transitions.resolution.TransitionOrdering
import com.correx.core.validation.graph.GraphValidator
import com.correx.core.validation.model.ValidationContext
import com.correx.core.validation.pipeline.ValidationPipeline
import com.correx.core.validation.transition.TransitionValidator
import com.correx.testing.fixtures.WorkflowFixtures
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class ValidationReplayTest {
@Test
fun `validation must be replay deterministic`() {
val graph = WorkflowFixtures.simpleGraph()
val context = ValidationContext(graph)
val pipeline = ValidationPipeline(
listOf(
GraphValidator(),
TransitionValidator(TransitionOrdering.comparator)
)
)
val results = (1..10).map {
pipeline.validate(context)
}
assertTrue(results.distinct().size == 1)
}
}