feat(kernel): inject decision journal into stage context + wire repository

This commit is contained in:
2026-06-04 00:48:07 +04:00
parent bd4dd91bf1
commit bcc20509e0
16 changed files with 129 additions and 2 deletions
+1
View File
@@ -13,6 +13,7 @@ dependencies {
testImplementation(project(":core:context"))
testImplementation(project(":core:inference"))
testImplementation(project(":core:kernel"))
testImplementation(project(":core:journal"))
testImplementation(project(":core:risk"))
testImplementation(project(":core:tools"))
testImplementation(project(":core:toolintent"))
@@ -52,6 +52,9 @@ import com.correx.core.inference.Tokenizer
import com.correx.core.inference.ToolCallFunction
import com.correx.core.inference.ToolCallRequest
import com.correx.core.kernel.orchestration.DefaultOrchestrationReducer
import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator
import com.correx.core.kernel.orchestration.OrchestrationConfig
import com.correx.core.kernel.orchestration.OrchestrationProjector
@@ -124,6 +127,10 @@ class SessionOrchestratorIntegrationTest {
DefaultEventReplayer(eventStore, ApprovalProjector(DefaultApprovalReducer())),
)
private val decisionJournalRepository = DefaultDecisionJournalRepository(
DefaultEventReplayer(eventStore, DecisionJournalProjector(DefaultDecisionJournalReducer())),
)
private val repositories = OrchestratorRepositories(
eventStore = eventStore,
inferenceRepository = inferenceRepository,
@@ -147,6 +154,7 @@ class SessionOrchestratorIntegrationTest {
engines = engines,
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
@Test
@@ -194,6 +202,7 @@ class SessionOrchestratorIntegrationTest {
),
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
failingOrchestrator.run(sessionId, graph, config)
@@ -221,6 +230,7 @@ class SessionOrchestratorIntegrationTest {
engines = engines.copy(validationPipeline = approvingPipeline),
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
val runJob = launch { orchestrator.run(sessionId, graph, config) }
@@ -269,6 +279,7 @@ class SessionOrchestratorIntegrationTest {
engines = engines.copy(validationPipeline = approvingPipeline),
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
val runJob = launch { approvalOrchestrator.run(sessionId, graph, config) }
@@ -322,6 +333,7 @@ class SessionOrchestratorIntegrationTest {
engines = engines,
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
val sessionId = SessionId("s6")
@@ -364,6 +376,7 @@ class SessionOrchestratorIntegrationTest {
engines = engines.copy(contextPackBuilder = droppingBuilder, promptResolver = PromptResolver { it }),
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
val sessionId = SessionId("s-trunc")
val config = OrchestrationConfig(retryPolicy = RetryPolicy(maxAttempts = 1, backoffMs = 0))
@@ -386,6 +399,7 @@ class SessionOrchestratorIntegrationTest {
engines = engines,
retryCoordinator = retryCoordinator,
artifactStore = recordingStore,
decisionJournalRepository = decisionJournalRepository,
)
recordingOrchestrator.run(sessionId, graph, config)
@@ -480,6 +494,7 @@ class SessionOrchestratorIntegrationTest {
engines = engines,
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
livenessOrchestrator.run(sessionId, livenessGraph, config)
@@ -596,6 +611,7 @@ class SessionOrchestratorIntegrationTest {
),
retryCoordinator = retryCoordinator,
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
livenessOrchestrator.run(sessionId, livenessGraph, config)
@@ -38,6 +38,9 @@ import com.correx.core.inference.ToolCallFunction
import com.correx.core.inference.ToolCallRequest
import com.correx.core.inference.TokenUsage
import com.correx.core.kernel.orchestration.DefaultOrchestrationReducer
import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator
import com.correx.core.kernel.orchestration.OrchestrationConfig
import com.correx.core.kernel.orchestration.OrchestrationProjector
@@ -220,11 +223,15 @@ class ToolCallGateTest {
workspacePolicy = policy,
)
val decisionJournalRepository = DefaultDecisionJournalRepository(
DefaultEventReplayer(eventStore, DecisionJournalProjector(DefaultDecisionJournalReducer())),
)
val orchestrator = DefaultSessionOrchestrator(
repositories = repositories,
engines = engines,
retryCoordinator = DefaultRetryCoordinator(eventStore),
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
)
return Triple(orchestrator, eventStore, provider)
@@ -31,6 +31,9 @@ import com.correx.core.inference.ToolCallFunction
import com.correx.core.inference.ToolCallRequest
import com.correx.core.inference.TokenUsage
import com.correx.core.kernel.orchestration.DefaultOrchestrationReducer
import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator
import com.correx.core.kernel.orchestration.OrchestrationConfig
import com.correx.core.kernel.orchestration.OrchestrationProjector
@@ -210,11 +213,15 @@ class WorkspaceScopedToolRegistryTest {
workspaceToolRegistryProvider = workspaceProvider,
)
val decisionJournalRepository = DefaultDecisionJournalRepository(
DefaultEventReplayer(eventStore, DecisionJournalProjector(DefaultDecisionJournalReducer())),
)
return DefaultSessionOrchestrator(
repositories = repositories,
engines = engines,
retryCoordinator = DefaultRetryCoordinator(eventStore),
artifactStore = artifactStore,
decisionJournalRepository = decisionJournalRepository,
) to eventStore
}
+1
View File
@@ -6,6 +6,7 @@ plugins {
dependencies {
testImplementation(project(":core:events"))
testImplementation(project(":core:journal"))
testImplementation(project(":core:sessions"))
testImplementation(project(":core:transitions"))
testImplementation(project(":core:validation"))
@@ -0,0 +1,50 @@
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.SteeringNoteAddedEvent
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.journal.DecisionJournalProjector
import com.correx.core.journal.DecisionJournalRenderer
import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Clock
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class DecisionJournalReplayTest {
private fun meta(sessionId: SessionId, id: String) =
EventMetadata(EventId(id), sessionId, Clock.System.now(), 1, null, null)
@Test
fun `journal replay is deterministic`(): Unit = runBlocking {
val sessionId = SessionId("s")
val store = InMemoryEventStore()
store.append(NewEvent(meta(sessionId, "steer"), SteeringNoteAddedEvent(sessionId, "use jwt")))
store.append(
NewEvent(
meta(sessionId, "trans"),
TransitionExecutedEvent(sessionId, StageId("a"), StageId("b"), TransitionId("t")),
),
)
val repo = DefaultDecisionJournalRepository(
DefaultEventReplayer(store, DecisionJournalProjector(DefaultDecisionJournalReducer())),
)
val renderer = DecisionJournalRenderer()
val a = renderer.render(repo.getJournal(sessionId))
val b = renderer.render(repo.getJournal(sessionId))
assertEquals(a, b)
assertTrue(a.contains("use jwt"))
assertTrue(a.contains("a → b"))
}
}