import com.correx.core.artifacts.kind.ConfigArtifactKind import com.correx.core.artifacts.kind.DefaultArtifactKindRegistry import com.correx.core.artifacts.kind.JsonSchema import com.correx.core.events.events.EventMetadata import com.correx.core.events.events.ExecutionPlanLockedEvent import com.correx.core.events.events.NewEvent import com.correx.core.events.types.ArtifactId import com.correx.core.events.types.EventId import com.correx.core.events.types.SessionId import com.correx.infrastructure.persistence.InMemoryEventStore import com.correx.infrastructure.workflow.ExecutionPlanCompiler import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class ExecutionPlanLockedReplayTest { private val planJson = """ { "goal": "replay test plan", "stages": [ { "id": "fetch", "prompt": "Fetch the data", "produces": "patch", "needs": [], "tools": [] }, { "id": "transform", "prompt": "Transform the data", "produces": "patch", "needs": ["patch"], "tools": [] } ], "edges": [ { "from": "fetch", "to": "transform", "condition": { "type": "always_true" } }, { "from": "transform", "to": "done", "condition": { "type": "always_true" } } ] } """.trimIndent() @Test fun `locked plan event recompiles to identical WorkflowGraph`(): Unit = runBlocking { val sessionId = SessionId("replay-session-1") val planArtifactId = ArtifactId("cas://plan-artifact-001") // CAS store: keyed by ArtifactId -> plan JSON (no FS access — replay determinism) val casStore = mutableMapOf() casStore[planArtifactId] = planJson val lockedEvent = ExecutionPlanLockedEvent( sessionId = sessionId, planArtifactId = planArtifactId, workflowId = "replay-workflow", stageIds = listOf("fetch", "transform"), startStageId = "fetch", ) val store = InMemoryEventStore() store.append( NewEvent( metadata = EventMetadata( eventId = EventId("locked-evt-1"), sessionId = sessionId, timestamp = Clock.System.now(), schemaVersion = 1, causationId = null, correlationId = null, ), payload = lockedEvent, ) ) // Replay: read event back, fetch JSON from CAS, recompile — no FS, no LLM val replayedEvent = store.read(sessionId) .map { it.payload } .filterIsInstance() .single() val replayedJson = casStore.getValue(replayedEvent.planArtifactId) val trivialSchema = JsonSchema(type = "object") val registry = DefaultArtifactKindRegistry().also { it.register(ConfigArtifactKind(id = "patch", schema = trivialSchema, llmEmitted = true)) } val compiler = ExecutionPlanCompiler(registry) val graph = compiler.compile(replayedJson, replayedEvent.workflowId) assertEquals(replayedEvent.stageIds.toSet(), graph.stages.keys.map { it.value }.toSet()) assertEquals(replayedEvent.startStageId, graph.start.value) } }