From 595ec187bccf7563c0c8a563ac0e9d7351a30058 Mon Sep 17 00:00:00 2001 From: kami Date: Fri, 3 Jul 2026 13:26:00 +0400 Subject: [PATCH] refactor: rename the router subsystem to Talkie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'router' name was misleading — it's the always-on conversational front-end (CHAT triage + STEERING into a running workflow), not a routing layer, and it's distinct from InferenceRouter. Rename core:router -> core:talkie, package com.correx.core.router -> com.correx.core.talkie, and RouterFacade/Config/State/ Repository/ContextBuilder/Projector/Reducer/Response -> Talkie*. Config section [router] -> [talkie] (legacy [router] still read as fallback). Persisted wire formats are preserved: ChatTurnRole.ROUTER and the RouterNarrationEvent @SerialName("RouterNarration") stay, so existing event logs still replay. RouterNarrationEvent the class is now TalkieNarrationEvent. --- apps/server/build.gradle | 2 +- .../kotlin/com/correx/apps/server/Main.kt | 30 +- .../com/correx/apps/server/ServerModule.kt | 10 +- .../apps/server/bridge/DomainEventMapper.kt | 4 +- .../memory/ArchitectContradictionChecker.kt | 4 +- .../server/memory/L3RepoKnowledgeRetriever.kt | 4 +- .../server/memory/ProjectMemoryService.kt | 6 +- .../server/narration/NarrationSubscriber.kt | 6 +- .../apps/server/protocol/ClientMessage.kt | 2 +- .../apps/server/protocol/ServerMessage.kt | 2 +- .../apps/server/ws/GlobalStreamHandler.kt | 4 +- .../apps/server/ws/SessionStreamHandler.kt | 4 +- .../correx/apps/server/ws/StreamQueries.kt | 2 +- .../server/ArchitectContradictionHookTest.kt | 14 +- .../server/lifecycle/LifecycleTestSupport.kt | 6 +- .../ArchitectContradictionCheckerTest.kt | 8 +- .../memory/L3RepoKnowledgeRetrieverTest.kt | 4 +- .../ProjectMemoryServiceObservationTest.kt | 2 +- .../memory/ProjectMemoryServiceReuseTest.kt | 2 +- .../server/memory/ProjectMemoryServiceTest.kt | 2 +- .../narration/NarrationSubscriberTest.kt | 34 +- .../server/routes/EventInspectRoutesTest.kt | 6 +- .../apps/server/routes/TaskRoutesTest.kt | 6 +- .../apps/server/ws/WorkspaceHandshakeTest.kt | 4 +- .../com/correx/core/config/ConfigLoader.kt | 10 +- .../com/correx/core/config/CorrexConfig.kt | 4 +- .../correx/core/config/CorrexConfigWriter.kt | 48 +- .../com/correx/core/config/EditableConfig.kt | 24 +- .../correx/core/config/ConfigLoaderTest.kt | 66 +-- .../core/config/CorrexConfigWriterTest.kt | 2 +- .../correx/core/config/EditableConfigTest.kt | 2 +- .../{RouterEvents.kt => TalkieEvents.kt} | 2 +- .../events/serialization/Serialization.kt | 4 +- ... TalkieNarrationEventSerializationTest.kt} | 10 +- .../com/correx/core/router/RouterProjector.kt | 15 - .../correx/core/router/RouterRepository.kt | 16 - core/{router => talkie}/AGENTS.md | 0 core/{router => talkie}/build.gradle | 0 .../com/correx/core/talkie}/IdeaReader.kt | 4 +- .../kotlin/com/correx/core/talkie}/Ideas.kt | 2 +- .../core/talkie/TalkieContextBuilder.kt} | 38 +- .../com/correx/core/talkie/TalkieFacade.kt} | 46 +- .../com/correx/core/talkie/TalkieProjector.kt | 15 + .../com/correx/core/talkie/TalkieReducer.kt} | 46 +- .../correx/core/talkie/TalkieRepository.kt | 16 + .../correx/core/talkie}/WorkflowProposals.kt | 2 +- .../core/talkie}/l3/InMemoryL3MemoryStore.kt | 2 +- .../com/correx/core/talkie}/l3/L3Hit.kt | 2 +- .../correx/core/talkie}/l3/L3MemoryEntry.kt | 2 +- .../correx/core/talkie}/l3/L3MemoryStore.kt | 2 +- .../core/talkie}/l3/L3MetadataRehydrator.kt | 2 +- .../com/correx/core/talkie}/l3/L3Query.kt | 2 +- .../talkie}/l3/RehydratableL3MemoryStore.kt | 2 +- .../com/correx/core/talkie}/model/Idea.kt | 2 +- .../core/talkie}/model/NarrationTrigger.kt | 4 +- .../correx/core/talkie/model/TalkieConfig.kt} | 4 +- .../core/talkie/model/TalkieResponse.kt} | 4 +- .../correx/core/talkie/model/TalkieState.kt} | 8 +- .../core/talkie}/model/WorkflowSummary.kt | 2 +- .../com/correx/core/talkie}/IdeaReaderTest.kt | 2 +- .../com/correx/core/talkie}/IdeasTest.kt | 2 +- .../core/talkie}/WorkflowProposalsTest.kt | 2 +- .../talkie}/l3/InMemoryL3MemoryStoreTest.kt | 2 +- infrastructure/build.gradle | 2 +- infrastructure/router/build.gradle | 2 +- infrastructure/router/turbovec/build.gradle | 2 +- .../router/turbovec/TurboVecL3MemoryStore.kt | 8 +- .../infrastructure/InfrastructureModule.kt | 38 +- settings.gradle | 2 +- testing/deterministic/build.gradle | 2 +- .../test/kotlin/L3MetadataRehydratorTest.kt | 12 +- ...derTest.kt => TalkieContextBuilderTest.kt} | 240 +++++----- ...outerFacadeTest.kt => TalkieFacadeTest.kt} | 422 +++++++++--------- ...arrationTest.kt => TalkieNarrationTest.kt} | 104 ++--- ...rojectorTest.kt => TalkieProjectorTest.kt} | 20 +- ...terReducerTest.kt => TalkieReducerTest.kt} | 10 +- testing/integration/build.gradle | 2 +- .../logs/archive/correx-2026-06-28-1.log.gz | Bin 0 -> 67120 bytes .../logs/archive/correx-2026-06-30-1.log.gz | Bin 0 -> 11904 bytes .../logs/archive/correx-2026-07-01-1.log.gz | Bin 0 -> 18903 bytes .../logs/archive/correx-2026-07-02-1.log.gz | Bin 0 -> 52794 bytes .../kotlin/RepoMapReuseIntegrationTest.kt | 2 +- testing/projections/build.gradle | 2 +- ...rojectorTest.kt => TalkieProjectorTest.kt} | 14 +- 84 files changed, 736 insertions(+), 734 deletions(-) rename core/events/src/main/kotlin/com/correx/core/events/events/{RouterEvents.kt => TalkieEvents.kt} (97%) rename core/events/src/test/kotlin/com/correx/core/events/serialization/{RouterNarrationEventSerializationTest.kt => TalkieNarrationEventSerializationTest.kt} (79%) delete mode 100644 core/router/src/main/kotlin/com/correx/core/router/RouterProjector.kt delete mode 100644 core/router/src/main/kotlin/com/correx/core/router/RouterRepository.kt rename core/{router => talkie}/AGENTS.md (100%) rename core/{router => talkie}/build.gradle (100%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/IdeaReader.kt (96%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/Ideas.kt (98%) rename core/{router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt => talkie/src/main/kotlin/com/correx/core/talkie/TalkieContextBuilder.kt} (96%) rename core/{router/src/main/kotlin/com/correx/core/router/RouterFacade.kt => talkie/src/main/kotlin/com/correx/core/talkie/TalkieFacade.kt} (93%) create mode 100644 core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieProjector.kt rename core/{router/src/main/kotlin/com/correx/core/router/RouterReducer.kt => talkie/src/main/kotlin/com/correx/core/talkie/TalkieReducer.kt} (75%) create mode 100644 core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieRepository.kt rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/WorkflowProposals.kt (98%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/l3/InMemoryL3MemoryStore.kt (98%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/l3/L3Hit.kt (79%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/l3/L3MemoryEntry.kt (97%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/l3/L3MemoryStore.kt (95%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/l3/L3MetadataRehydrator.kt (97%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/l3/L3Query.kt (96%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/l3/RehydratableL3MemoryStore.kt (96%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/model/Idea.kt (83%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/model/NarrationTrigger.kt (88%) rename core/{router/src/main/kotlin/com/correx/core/router/model/RouterConfig.kt => talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieConfig.kt} (94%) rename core/{router/src/main/kotlin/com/correx/core/router/model/RouterResponse.kt => talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieResponse.kt} (66%) rename core/{router/src/main/kotlin/com/correx/core/router/model/RouterState.kt => talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieState.kt} (91%) rename core/{router/src/main/kotlin/com/correx/core/router => talkie/src/main/kotlin/com/correx/core/talkie}/model/WorkflowSummary.kt (75%) rename core/{router/src/test/kotlin/com/correx/core/router => talkie/src/test/kotlin/com/correx/core/talkie}/IdeaReaderTest.kt (99%) rename core/{router/src/test/kotlin/com/correx/core/router => talkie/src/test/kotlin/com/correx/core/talkie}/IdeasTest.kt (98%) rename core/{router/src/test/kotlin/com/correx/core/router => talkie/src/test/kotlin/com/correx/core/talkie}/WorkflowProposalsTest.kt (98%) rename core/{router/src/test/kotlin/com/correx/core/router => talkie/src/test/kotlin/com/correx/core/talkie}/l3/InMemoryL3MemoryStoreTest.kt (99%) rename testing/deterministic/src/test/kotlin/{RouterContextBuilderTest.kt => TalkieContextBuilderTest.kt} (87%) rename testing/deterministic/src/test/kotlin/{RouterFacadeTest.kt => TalkieFacadeTest.kt} (82%) rename testing/deterministic/src/test/kotlin/{RouterNarrationTest.kt => TalkieNarrationTest.kt} (83%) rename testing/deterministic/src/test/kotlin/{RouterProjectorTest.kt => TalkieProjectorTest.kt} (94%) rename testing/deterministic/src/test/kotlin/{RouterReducerTest.kt => TalkieReducerTest.kt} (97%) create mode 100644 testing/integration/logs/archive/correx-2026-06-28-1.log.gz create mode 100644 testing/integration/logs/archive/correx-2026-06-30-1.log.gz create mode 100644 testing/integration/logs/archive/correx-2026-07-01-1.log.gz create mode 100644 testing/integration/logs/archive/correx-2026-07-02-1.log.gz rename testing/projections/src/test/kotlin/{RouterProjectorTest.kt => TalkieProjectorTest.kt} (95%) diff --git a/apps/server/build.gradle b/apps/server/build.gradle index db59d600..8eec50e9 100644 --- a/apps/server/build.gradle +++ b/apps/server/build.gradle @@ -38,7 +38,7 @@ dependencies { implementation project(':infrastructure:inference:llama_cpp') implementation project(':infrastructure:inference:openai_compat') implementation project(':infrastructure:inference:commons') - implementation project(':core:router') + implementation project(':core:talkie') implementation project(':core:tools') implementation project(':core:toolintent') implementation project(':infrastructure:tools') diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt b/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt index 7375b645..4e29f8ef 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt @@ -44,9 +44,9 @@ import com.correx.core.kernel.orchestration.WorkspaceTools import com.correx.core.kernel.orchestration.WorkspaceToolRegistryProvider import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.risk.DefaultRiskAssessor -import com.correx.core.router.RouterFacade -import com.correx.core.router.l3.L3MetadataRehydrator -import com.correx.core.router.model.RouterConfig +import com.correx.core.talkie.TalkieFacade +import com.correx.core.talkie.l3.L3MetadataRehydrator +import com.correx.core.talkie.model.TalkieConfig import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.SessionProjector @@ -294,7 +294,7 @@ fun main() { // Constructed before the engines so the context builder can rank freeform turns by relevance // to the current query (pipeline §6, query-conditioned selection). Reused by L3 retrieval below. - val embedder = InfrastructureModule.createEmbedderFromConfig(correxConfig.router.embedder) + val embedder = InfrastructureModule.createEmbedderFromConfig(correxConfig.talkie.embedder) val engines = OrchestratorEngines( transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) }, contextPackBuilder = DefaultContextPackBuilder( @@ -345,7 +345,7 @@ fun main() { val artifactKindRegistry = DefaultArtifactKindRegistry().also { reg -> configArtifactKindsEarly.forEach { reg.register(it) } } - val l3MemoryStore = InfrastructureModule.createL3MemoryStoreFromConfig(correxConfig.router.l3) + val l3MemoryStore = InfrastructureModule.createL3MemoryStoreFromConfig(correxConfig.talkie.l3) // Rebuild the L3 metadata map from the ChatTurnEvent log before serving queries: // persisted vectors survive restart but the metadata map does not (no-op for // non-rehydratable backends like in_memory). @@ -390,11 +390,11 @@ fun main() { InfrastructureModule.createWorkflowLoader(configArtifactKindsEarly), ) // Builds the router facade from a config snapshot, mapping the [router] block onto the domain - // RouterConfig. Reused by ConfigService's rebuild hook so router knob edits apply live (the + // TalkieConfig. Reused by ConfigService's rebuild hook so router knob edits apply live (the // embedder and L3 store are construction-time and reused; the facade is what gets swapped). - fun buildRouterFacade(cfg: CorrexConfig): RouterFacade { - val rc = cfg.router - val domainRouterConfig = RouterConfig( + fun buildTalkieFacade(cfg: CorrexConfig): TalkieFacade { + val rc = cfg.talkie + val domainTalkieConfig = TalkieConfig( conversationKeepLast = rc.conversationKeepLast, retrievalK = rc.retrievalK, tokenBudget = TokenBudget(limit = rc.tokenBudget), @@ -410,16 +410,16 @@ fun main() { ), narrationModelId = rc.narration.modelId, ) - return InfrastructureModule.createRouterFacade( + return InfrastructureModule.createTalkieFacade( eventStore = eventStore, inferenceRouter = inferenceRouter, - config = domainRouterConfig, + config = domainTalkieConfig, tokenizer = firstProvider.tokenizer, embedder = embedder, l3MemoryStore = l3MemoryStore, workflowSummaryProvider = { workflowRegistry.listAll().map { s -> - com.correx.core.router.model.WorkflowSummary(s.workflowId, s.description, s.stageIds) + com.correx.core.talkie.model.WorkflowSummary(s.workflowId, s.description, s.stageIds) } }, sessionProfileProvider = { sid -> @@ -429,7 +429,7 @@ fun main() { }, ) } - val routerFacade = buildRouterFacade(correxConfig) + val routerFacade = buildTalkieFacade(correxConfig) val sessionUndoService = com.correx.apps.server.undo.SessionUndoService( eventStore = eventStore, artifactStore = artifactStore, @@ -546,7 +546,7 @@ fun main() { modelSwapper = modelSwapper, resourceProbe = resourceProbe, workspaceResolver = workspaceResolver, - narrationMaxPerRun = correxConfig.router.narration.maxPerRun, + narrationMaxPerRun = correxConfig.talkie.narration.maxPerRun, projectMemory = projectMemory, architectContradictionChecker = architectContradictionChecker, configHolder = configHolder, @@ -564,7 +564,7 @@ fun main() { // services. Built after the module so the rebuild hook can swap them in place. module.configService = ConfigService(configHolder, ConfigLoader.configPath()) { cfg -> module.applyRebuiltServices( - newRouterFacade = buildRouterFacade(cfg), + newTalkieFacade = buildTalkieFacade(cfg), newProjectMemory = buildProjectMemory(cfg), newProfileAdaptationService = buildProfileAdaptation(cfg), ) diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt b/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt index c5a78882..e2549341 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt @@ -39,7 +39,7 @@ import com.correx.core.kernel.execution.WorkflowResult import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator import com.correx.core.kernel.orchestration.OrchestrationConfig import com.correx.core.kernel.orchestration.OrchestrationRepository -import com.correx.core.router.RouterFacade +import com.correx.core.talkie.TalkieFacade import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.SessionSummary import com.correx.core.sessions.SessionSummaryProjector @@ -72,7 +72,7 @@ class ServerModule( val defaultOrchestrationConfig: OrchestrationConfig, // Rebuilt by ConfigService on a live config edit (router knobs); read at use-time by // GlobalStreamHandler.onUserInput so the next chat turn picks up the new facade. - routerFacade: RouterFacade, + routerFacade: TalkieFacade, val orchestrationRepository: OrchestrationRepository, val approvalRepository: DefaultApprovalRepository, val toolRegistry: ToolRegistry, @@ -134,7 +134,7 @@ class ServerModule( // Config-derived services that ConfigService swaps on a live edit. Reads happen at use-time // (next session / next chat turn), so in-flight sessions keep the config they started with. @Volatile - var routerFacade: RouterFacade = routerFacade + var routerFacade: TalkieFacade = routerFacade private set @Volatile @@ -154,11 +154,11 @@ class ServerModule( * In-flight sessions are untouched; the next session/turn reads the new instances. */ fun applyRebuiltServices( - newRouterFacade: RouterFacade, + newTalkieFacade: TalkieFacade, newProjectMemory: com.correx.apps.server.memory.ProjectMemoryService?, newProfileAdaptationService: com.correx.apps.server.personalization.ProfileAdaptationService?, ) { - this.routerFacade = newRouterFacade + this.routerFacade = newTalkieFacade this.projectMemory = newProjectMemory this.profileAdaptationService = newProfileAdaptationService } diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt b/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt index 18ada65c..fbdb1fe1 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt @@ -39,7 +39,7 @@ import com.correx.core.events.events.ToolCallAssessedEvent import com.correx.core.events.events.ToolInvocationRequestedEvent import com.correx.core.events.events.TransitionExecutedEvent import com.correx.core.events.events.WorkflowCompletedEvent -import com.correx.core.events.events.RouterNarrationEvent +import com.correx.core.events.events.TalkieNarrationEvent import com.correx.core.events.events.WorkflowFailedEvent import com.correx.core.events.events.WorkflowProposedEvent import com.correx.core.events.risk.RiskAction @@ -249,7 +249,7 @@ suspend fun domainEventToServerMessage( sequence = seq, sessionSequence = sessionSequence, ) - is RouterNarrationEvent -> ServerMessage.Narration( + is TalkieNarrationEvent -> ServerMessage.Narration( sessionId = p.sessionId, content = p.content, stageId = p.stageId, diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/memory/ArchitectContradictionChecker.kt b/apps/server/src/main/kotlin/com/correx/apps/server/memory/ArchitectContradictionChecker.kt index bafddd21..76d72f90 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/memory/ArchitectContradictionChecker.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/memory/ArchitectContradictionChecker.kt @@ -5,8 +5,8 @@ import com.correx.core.events.events.RelatedDecision import com.correx.core.events.types.SessionId import com.correx.core.events.types.StageId import com.correx.core.inference.Embedder -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query /** * Display-only architect contradiction surfacing (BACKLOG §B-§4). When the architect records a diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetriever.kt b/apps/server/src/main/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetriever.kt index 57b738fb..5a817e1d 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetriever.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetriever.kt @@ -4,8 +4,8 @@ import com.correx.core.events.events.RepoKnowledgeHit import com.correx.core.events.types.SessionId import com.correx.core.inference.Embedder import com.correx.core.kernel.orchestration.RepoKnowledgeRetriever -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query private const val RETRIEVAL_OVERSAMPLE_FACTOR = 4 diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/memory/ProjectMemoryService.kt b/apps/server/src/main/kotlin/com/correx/apps/server/memory/ProjectMemoryService.kt index b55494d1..be7b6515 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/memory/ProjectMemoryService.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/memory/ProjectMemoryService.kt @@ -13,9 +13,9 @@ import com.correx.core.events.types.SessionId import com.correx.core.inference.Embedder import com.correx.core.journal.DefaultDecisionJournalRepository import com.correx.core.journal.ProjectMemoryDistiller -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query import kotlinx.coroutines.CancellationException import kotlinx.datetime.Clock import org.slf4j.LoggerFactory diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/narration/NarrationSubscriber.kt b/apps/server/src/main/kotlin/com/correx/apps/server/narration/NarrationSubscriber.kt index 09658eee..46bb794f 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/narration/NarrationSubscriber.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/narration/NarrationSubscriber.kt @@ -13,8 +13,8 @@ import com.correx.core.events.events.WorkflowFailedEvent import com.correx.core.events.events.WorkflowStartedEvent import com.correx.core.events.stores.EventStore import com.correx.core.events.types.SessionId -import com.correx.core.router.RouterFacade -import com.correx.core.router.model.NarrationTrigger +import com.correx.core.talkie.TalkieFacade +import com.correx.core.talkie.model.NarrationTrigger import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel @@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap class NarrationSubscriber( private val eventStore: EventStore, - private val routerFacade: RouterFacade, + private val routerFacade: TalkieFacade, private val scope: CoroutineScope, private val maxPerRun: Int = DEFAULT_MAX_PER_RUN, ) { diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ClientMessage.kt b/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ClientMessage.kt index f53c04b4..de592440 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ClientMessage.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ClientMessage.kt @@ -8,7 +8,7 @@ import com.correx.core.events.types.ClarificationRequestId import com.correx.core.events.types.GrantId import com.correx.core.events.types.SessionId import com.correx.core.events.types.StageId -import com.correx.core.router.ChatMode +import com.correx.core.talkie.ChatMode import kotlinx.datetime.Instant import kotlinx.serialization.Serializable diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt b/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt index 32f08c91..7b3e3d85 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt @@ -562,7 +562,7 @@ sealed interface ServerMessage { @Serializable @SerialName("router.response") - data class RouterResponseMessage( + data class TalkieResponseMessage( val sessionId: SessionId, val content: String, val steeringEmitted: Boolean, diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt b/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt index 4695ce40..fb69db9f 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt @@ -35,7 +35,7 @@ import com.correx.core.kernel.orchestration.WorkspaceContext import com.correx.core.config.ProjectProfileLoader import com.correx.core.config.ProjectProfileWriter import com.correx.core.config.withConvention -import com.correx.core.router.IdeaReader +import com.correx.core.talkie.IdeaReader import com.correx.core.utils.TypeId import com.correx.infrastructure.inference.commons.ResourceProbe import com.correx.infrastructure.inference.commons.ResourceSnapshot @@ -253,7 +253,7 @@ class GlobalStreamHandler(private val module: ServerModule) { is ClientMessage.ChatInput -> { // The USER and ROUTER turns are emitted as ChatTurnEvents inside onUserInput // and reach the client as chat.turn frames via streamGlobal — no direct - // RouterResponseMessage (the conversation is event-derived). + // TalkieResponseMessage (the conversation is event-derived). withSessionContext(msg.sessionId) { runCatching { module.routerFacade.onUserInput(msg.sessionId, msg.text, msg.mode) diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/ws/SessionStreamHandler.kt b/apps/server/src/main/kotlin/com/correx/apps/server/ws/SessionStreamHandler.kt index 4c07d411..9167e972 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/ws/SessionStreamHandler.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/ws/SessionStreamHandler.kt @@ -6,7 +6,7 @@ import com.correx.apps.server.logging.withSessionContext import com.correx.apps.server.protocol.ClientMessage import com.correx.apps.server.protocol.ProtocolSerializer import com.correx.apps.server.protocol.ServerMessage -import com.correx.apps.server.protocol.ServerMessage.RouterResponseMessage +import com.correx.apps.server.protocol.ServerMessage.TalkieResponseMessage import com.correx.core.events.types.SessionId import io.ktor.server.websocket.DefaultWebSocketServerSession import io.ktor.websocket.Frame @@ -93,7 +93,7 @@ class SessionStreamHandler(private val module: ServerModule) { session.send( Frame.Text( ProtocolSerializer.encodeServerMessage( - RouterResponseMessage( + TalkieResponseMessage( sessionId = msg.sessionId, content = response.content, steeringEmitted = response.steeringEmitted, diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/ws/StreamQueries.kt b/apps/server/src/main/kotlin/com/correx/apps/server/ws/StreamQueries.kt index c84c554b..7eb8652b 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/ws/StreamQueries.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/ws/StreamQueries.kt @@ -14,7 +14,7 @@ import com.correx.core.approvals.GRANT_LEDGER_SESSION_ID import com.correx.core.approvals.GrantScope import com.correx.core.approvals.ProjectIdentity import com.correx.core.events.types.ProjectId -import com.correx.core.router.IdeaReader +import com.correx.core.talkie.IdeaReader import com.correx.core.config.EditableConfig import com.correx.core.events.events.ArtifactContentStoredEvent import com.correx.core.events.events.ArtifactCreatedEvent diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/ArchitectContradictionHookTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/ArchitectContradictionHookTest.kt index a07b3289..e31ab0ab 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/ArchitectContradictionHookTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/ArchitectContradictionHookTest.kt @@ -35,11 +35,11 @@ import com.correx.core.kernel.orchestration.OrchestratorEngines import com.correx.core.kernel.orchestration.OrchestratorRepositories import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.risk.DefaultRiskAssessor -import com.correx.core.router.l3.L3Hit -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query -import com.correx.core.router.model.RouterConfig +import com.correx.core.talkie.l3.L3Hit +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query +import com.correx.core.talkie.model.TalkieConfig import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.SessionProjector @@ -182,10 +182,10 @@ class ArchitectContradictionHookTest { tokenizer = provider.tokenizer, decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), ) - val routerFacade = InfrastructureModule.createRouterFacade( + val routerFacade = InfrastructureModule.createTalkieFacade( eventStore = eventStore, inferenceRouter = inferenceRouter, - config = RouterConfig(), + config = TalkieConfig(), tokenizer = provider.tokenizer, ) val noopProviderRegistry = object : ProviderRegistry { diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/lifecycle/LifecycleTestSupport.kt b/apps/server/src/test/kotlin/com/correx/apps/server/lifecycle/LifecycleTestSupport.kt index 9418f022..cbe3315f 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/lifecycle/LifecycleTestSupport.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/lifecycle/LifecycleTestSupport.kt @@ -24,7 +24,7 @@ import com.correx.core.kernel.orchestration.OrchestratorEngines import com.correx.core.kernel.orchestration.OrchestratorRepositories import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.risk.DefaultRiskAssessor -import com.correx.core.router.model.RouterConfig +import com.correx.core.talkie.model.TalkieConfig import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.SessionProjector @@ -116,10 +116,10 @@ fun buildTestServerModule( decisionJournalRepository = decisionJournalRepository, ) - val routerFacade = InfrastructureModule.createRouterFacade( + val routerFacade = InfrastructureModule.createTalkieFacade( eventStore = eventStore, inferenceRouter = modelSwapper, - config = RouterConfig(), + config = TalkieConfig(), tokenizer = firstProvider.tokenizer, ) diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ArchitectContradictionCheckerTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ArchitectContradictionCheckerTest.kt index 36188d50..18ec8df6 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ArchitectContradictionCheckerTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ArchitectContradictionCheckerTest.kt @@ -3,10 +3,10 @@ package com.correx.apps.server.memory import com.correx.core.events.types.SessionId import com.correx.core.events.types.StageId import com.correx.core.inference.Embedder -import com.correx.core.router.l3.L3Hit -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query +import com.correx.core.talkie.l3.L3Hit +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetrieverTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetrieverTest.kt index 8db9c7d5..6d82f7e7 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetrieverTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/memory/L3RepoKnowledgeRetrieverTest.kt @@ -2,8 +2,8 @@ package com.correx.apps.server.memory import com.correx.core.events.types.SessionId import com.correx.core.inference.Embedder -import com.correx.core.router.l3.InMemoryL3MemoryStore -import com.correx.core.router.l3.L3MemoryEntry +import com.correx.core.talkie.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.L3MemoryEntry import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceObservationTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceObservationTest.kt index 8f07aa8a..846457e0 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceObservationTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceObservationTest.kt @@ -10,7 +10,7 @@ import com.correx.core.inference.Embedder import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalRepository -import com.correx.core.router.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.InMemoryL3MemoryStore import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.infrastructure.persistence.InMemoryEventStore import kotlinx.coroutines.runBlocking diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceReuseTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceReuseTest.kt index ae3e6652..fd478d53 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceReuseTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceReuseTest.kt @@ -6,7 +6,7 @@ import com.correx.core.inference.Embedder import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalRepository -import com.correx.core.router.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.InMemoryL3MemoryStore import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.events.types.SessionId import com.correx.infrastructure.persistence.InMemoryEventStore diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceTest.kt index 29012032..6b12555c 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/memory/ProjectMemoryServiceTest.kt @@ -13,7 +13,7 @@ import com.correx.core.inference.Embedder import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalRepository -import com.correx.core.router.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.InMemoryL3MemoryStore import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.infrastructure.persistence.InMemoryEventStore import kotlinx.coroutines.runBlocking diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/narration/NarrationSubscriberTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/narration/NarrationSubscriberTest.kt index c5f198ab..b946dfdf 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/narration/NarrationSubscriberTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/narration/NarrationSubscriberTest.kt @@ -25,10 +25,10 @@ import com.correx.core.events.types.StageId import com.correx.core.events.types.TransitionId import com.correx.core.events.types.ValidationReportId import org.junit.jupiter.api.Assertions.assertTrue -import com.correx.core.router.ChatMode -import com.correx.core.router.RouterFacade -import com.correx.core.router.model.NarrationTrigger -import com.correx.core.router.model.RouterResponse +import com.correx.core.talkie.ChatMode +import com.correx.core.talkie.TalkieFacade +import com.correx.core.talkie.model.NarrationTrigger +import com.correx.core.talkie.model.TalkieResponse import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -72,10 +72,10 @@ class NarrationSubscriberTest { override suspend fun lastGlobalSequence(): Long = 0L } - private class RecordingRouterFacade : RouterFacade { + private class RecordingTalkieFacade : TalkieFacade { val calls = CopyOnWriteArrayList>() - override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = + override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse = error("unused in narration tests") override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { @@ -99,7 +99,7 @@ class NarrationSubscriberTest { @Test fun `triggers are narrated in order for matching events`(): Unit = runBlocking { - val facade = RecordingRouterFacade() + val facade = RecordingTalkieFacade() NarrationSubscriber(fakeStore, facade, scope).start() // Wait for subscription to be attached @@ -122,7 +122,7 @@ class NarrationSubscriberTest { @Test fun `pause narration is grounded with the pending approval tool and preview`(): Unit = runBlocking { - val facade = RecordingRouterFacade() + val facade = RecordingTalkieFacade() NarrationSubscriber(fakeStore, facade, scope).start() while (liveFlow.subscriptionCount.value == 0) yield() @@ -158,7 +158,7 @@ class NarrationSubscriberTest { @Test fun `maxPerRun=1 skips second trigger after first narration`(): Unit = runBlocking { - val facade = RecordingRouterFacade() + val facade = RecordingTalkieFacade() NarrationSubscriber(fakeStore, facade, scope, maxPerRun = 1).start() while (liveFlow.subscriptionCount.value == 0) yield() @@ -180,10 +180,10 @@ class NarrationSubscriberTest { @Test fun `a throwing narrate does not prevent subsequent narrations`(): Unit = runBlocking { var callCount = 0 - val facade = object : RouterFacade { + val facade = object : TalkieFacade { val calls = CopyOnWriteArrayList() - override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = + override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse = error("unused") override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { @@ -210,7 +210,7 @@ class NarrationSubscriberTest { @Test fun `execution plan rejection triggers narration with rejection reason`(): Unit = runBlocking { - val facade = RecordingRouterFacade() + val facade = RecordingTalkieFacade() NarrationSubscriber(fakeStore, facade, scope).start() while (liveFlow.subscriptionCount.value == 0) yield() @@ -244,7 +244,7 @@ class NarrationSubscriberTest { * letting the test enqueue further events (and resolve the pause) while the pause narration is * still pending in the channel. */ - private class GatingRouterFacade : RouterFacade { + private class GatingTalkieFacade : TalkieFacade { val calls = CopyOnWriteArrayList() val release = CompletableDeferred() // Signals that the worker has pulled the first (warmup) trigger and is now blocked, @@ -252,7 +252,7 @@ class NarrationSubscriberTest { val blocked = CompletableDeferred() private var first = true - override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = + override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse = error("unused in narration tests") override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { @@ -289,7 +289,7 @@ class NarrationSubscriberTest { @Test fun `resolved approval drops the still-queued pause narration`(): Unit = runBlocking { - val facade = GatingRouterFacade() + val facade = GatingTalkieFacade() NarrationSubscriber(fakeStore, facade, scope).start() while (liveFlow.subscriptionCount.value == 0) yield() @@ -320,7 +320,7 @@ class NarrationSubscriberTest { @Test fun `unrelated approval resolution leaves the pending pause intact`(): Unit = runBlocking { - val facade = GatingRouterFacade() + val facade = GatingTalkieFacade() NarrationSubscriber(fakeStore, facade, scope).start() while (liveFlow.subscriptionCount.value == 0) yield() @@ -342,7 +342,7 @@ class NarrationSubscriberTest { @Test fun `resume drops every queued pause narration for the session`(): Unit = runBlocking { - val facade = GatingRouterFacade() + val facade = GatingTalkieFacade() NarrationSubscriber(fakeStore, facade, scope).start() while (liveFlow.subscriptionCount.value == 0) yield() diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/routes/EventInspectRoutesTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/routes/EventInspectRoutesTest.kt index 57e3aebd..6c8c27a4 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/routes/EventInspectRoutesTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/routes/EventInspectRoutesTest.kt @@ -33,7 +33,7 @@ import com.correx.core.kernel.orchestration.OrchestratorRepositories import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.events.types.ProviderId import com.correx.core.risk.DefaultRiskAssessor -import com.correx.core.router.model.RouterConfig +import com.correx.core.talkie.model.TalkieConfig import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.SessionProjector @@ -165,10 +165,10 @@ class EventInspectRoutesTest { tokenizer = provider.tokenizer, decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), ) - val routerFacade = InfrastructureModule.createRouterFacade( + val routerFacade = InfrastructureModule.createTalkieFacade( eventStore = eventStore, inferenceRouter = inferenceRouter, - config = RouterConfig(), + config = TalkieConfig(), tokenizer = provider.tokenizer, ) val noopProviderRegistry = object : ProviderRegistry { diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/routes/TaskRoutesTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/routes/TaskRoutesTest.kt index 7f97789c..70f0b353 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/routes/TaskRoutesTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/routes/TaskRoutesTest.kt @@ -26,7 +26,7 @@ import com.correx.core.kernel.orchestration.OrchestratorRepositories import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.events.types.ProviderId import com.correx.core.risk.DefaultRiskAssessor -import com.correx.core.router.model.RouterConfig +import com.correx.core.talkie.model.TalkieConfig import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.SessionProjector @@ -444,10 +444,10 @@ class TaskRoutesTest { tokenizer = provider.tokenizer, decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), ) - val routerFacade = InfrastructureModule.createRouterFacade( + val routerFacade = InfrastructureModule.createTalkieFacade( eventStore = eventStore, inferenceRouter = inferenceRouter, - config = RouterConfig(), + config = TalkieConfig(), tokenizer = provider.tokenizer, ) val noopProviderRegistry = object : ProviderRegistry { diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/ws/WorkspaceHandshakeTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/ws/WorkspaceHandshakeTest.kt index 398341a5..08c11ab3 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/ws/WorkspaceHandshakeTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/ws/WorkspaceHandshakeTest.kt @@ -186,10 +186,10 @@ class WorkspaceHandshakeTest { decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), ) - val routerFacade = InfrastructureModule.createRouterFacade( + val routerFacade = InfrastructureModule.createTalkieFacade( eventStore = eventStore, inferenceRouter = inferenceRouter, - config = com.correx.core.router.model.RouterConfig(), + config = com.correx.core.talkie.model.TalkieConfig(), tokenizer = provider.tokenizer, ) diff --git a/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt b/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt index 66b8cfbe..41a8a674 100644 --- a/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt +++ b/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt @@ -418,14 +418,14 @@ object ConfigLoader { val toolsFileReadSection = sections["tools.file_read"] ?: emptyMap() val toolsFileWriteSection = sections["tools.file_write"] ?: emptyMap() val toolsFileEditSection = sections["tools.file_edit"] ?: emptyMap() - val routerSection = sections["router"] ?: emptyMap() + val routerSection = sections["talkie"] ?: sections["router"] ?: emptyMap() // Embedder + L3 are shared infra (the orchestrator uses them for workflow repo-knowledge, // not just the router chat layer). Prefer the top-level [embedder]/[l3] sections; fall back // to the legacy [router.embedder]/[router.l3] so existing configs keep working. val routerEmbedderSection = sections["embedder"] ?: sections["router.embedder"] ?: emptyMap() val routerL3Section = sections["l3"] ?: sections["router.l3"] ?: emptyMap() - val routerGenerationSection = sections["router.generation"] ?: emptyMap() - val routerNarrationSection = sections["router.narration"] ?: emptyMap() + val routerGenerationSection = sections["talkie.generation"] ?: sections["router.generation"] ?: emptyMap() + val routerNarrationSection = sections["talkie.narration"] ?: sections["router.narration"] ?: emptyMap() val modelsSection = sections["models"] ?: emptyMap() val server = ServerConfig( @@ -575,7 +575,7 @@ object ConfigLoader { modelId = asStringOrNull(routerNarrationSection["model_id"]), ) - val router = RouterConfig( + val router = TalkieConfig( embedder = embedder, l3 = l3, conversationKeepLast = asInt(routerSection["conversation_keep_last"], 6), @@ -655,7 +655,7 @@ object ConfigLoader { cli = cli, tools = tools, providers = providers, - router = router, + talkie = router, models = models, modelsSettings = modelsSettings, artifacts = artifacts, diff --git a/core/config/src/main/kotlin/com/correx/core/config/CorrexConfig.kt b/core/config/src/main/kotlin/com/correx/core/config/CorrexConfig.kt index aa33010e..04fe4060 100644 --- a/core/config/src/main/kotlin/com/correx/core/config/CorrexConfig.kt +++ b/core/config/src/main/kotlin/com/correx/core/config/CorrexConfig.kt @@ -9,7 +9,7 @@ data class CorrexConfig( val cli: CliConfig = CliConfig(), val tools: ToolsConfig = ToolsConfig(), val providers: List = emptyList(), - val router: RouterConfig = RouterConfig(), + val talkie: TalkieConfig = TalkieConfig(), val models: List = emptyList(), val modelsSettings: ModelsSettings = ModelsSettings(), val artifacts: List = emptyList(), @@ -189,7 +189,7 @@ data class ProviderConfig( ) @Serializable -data class RouterConfig( +data class TalkieConfig( val embedder: EmbedderConfig = EmbedderConfig(), val l3: L3Config = L3Config(), val conversationKeepLast: Int = 6, diff --git a/core/config/src/main/kotlin/com/correx/core/config/CorrexConfigWriter.kt b/core/config/src/main/kotlin/com/correx/core/config/CorrexConfigWriter.kt index bbfd5e68..0ffe77af 100644 --- a/core/config/src/main/kotlin/com/correx/core/config/CorrexConfigWriter.kt +++ b/core/config/src/main/kotlin/com/correx/core/config/CorrexConfigWriter.kt @@ -51,36 +51,36 @@ object CorrexConfigWriter { b.section("tools.file_edit") b.kv("enabled", cfg.tools.fileEditEnabled) - b.section("router") - b.kv("conversation_keep_last", cfg.router.conversationKeepLast) - b.kv("retrieval_k", cfg.router.retrievalK) - b.kv("token_budget", cfg.router.tokenBudget) + b.section("talkie") + b.kv("conversation_keep_last", cfg.talkie.conversationKeepLast) + b.kv("retrieval_k", cfg.talkie.retrievalK) + b.kv("token_budget", cfg.talkie.tokenBudget) b.section("embedder") - b.kv("backend", str(cfg.router.embedder.backend)) - b.kv("dimension", cfg.router.embedder.dimension) - cfg.router.embedder.url?.let { b.kv("url", str(it)) } - cfg.router.embedder.modelId?.let { b.kv("model_id", str(it)) } + b.kv("backend", str(cfg.talkie.embedder.backend)) + b.kv("dimension", cfg.talkie.embedder.dimension) + cfg.talkie.embedder.url?.let { b.kv("url", str(it)) } + cfg.talkie.embedder.modelId?.let { b.kv("model_id", str(it)) } b.section("l3") - b.kv("backend", str(cfg.router.l3.backend)) - cfg.router.l3.persistPath?.let { b.kv("persist_path", str(it)) } - b.kv("python_executable", str(cfg.router.l3.pythonExecutable)) - cfg.router.l3.scriptPath?.let { b.kv("script_path", str(it)) } - b.kv("dim", cfg.router.l3.dim) - b.kv("bit_width", cfg.router.l3.bitWidth) + b.kv("backend", str(cfg.talkie.l3.backend)) + cfg.talkie.l3.persistPath?.let { b.kv("persist_path", str(it)) } + b.kv("python_executable", str(cfg.talkie.l3.pythonExecutable)) + cfg.talkie.l3.scriptPath?.let { b.kv("script_path", str(it)) } + b.kv("dim", cfg.talkie.l3.dim) + b.kv("bit_width", cfg.talkie.l3.bitWidth) - b.section("router.generation") - b.kv("temperature", cfg.router.generation.temperature) - b.kv("top_p", cfg.router.generation.topP) - b.kv("max_tokens", cfg.router.generation.maxTokens) + b.section("talkie.generation") + b.kv("temperature", cfg.talkie.generation.temperature) + b.kv("top_p", cfg.talkie.generation.topP) + b.kv("max_tokens", cfg.talkie.generation.maxTokens) - b.section("router.narration") - b.kv("temperature", cfg.router.narration.temperature) - b.kv("top_p", cfg.router.narration.topP) - b.kv("max_tokens", cfg.router.narration.maxTokens) - b.kv("max_per_run", cfg.router.narration.maxPerRun) - cfg.router.narration.modelId?.let { b.kv("model_id", str(it)) } + b.section("talkie.narration") + b.kv("temperature", cfg.talkie.narration.temperature) + b.kv("top_p", cfg.talkie.narration.topP) + b.kv("max_tokens", cfg.talkie.narration.maxTokens) + b.kv("max_per_run", cfg.talkie.narration.maxPerRun) + cfg.talkie.narration.modelId?.let { b.kv("model_id", str(it)) } b.section("orchestration") b.kv("stage_timeout_ms", cfg.orchestration.stageTimeoutMs) diff --git a/core/config/src/main/kotlin/com/correx/core/config/EditableConfig.kt b/core/config/src/main/kotlin/com/correx/core/config/EditableConfig.kt index 979b3484..fee6b5b9 100644 --- a/core/config/src/main/kotlin/com/correx/core/config/EditableConfig.kt +++ b/core/config/src/main/kotlin/com/correx/core/config/EditableConfig.kt @@ -87,57 +87,57 @@ object EditableConfig { ), EditableField( "router.conversation_keep_last", ConfigFieldType.INT, - getString = { it.router.conversationKeepLast.toString() }, + getString = { it.talkie.conversationKeepLast.toString() }, withString = { c, v -> rtr(c) { it.copy(conversationKeepLast = int("router.conversation_keep_last", v)) } }, ), EditableField( "router.retrieval_k", ConfigFieldType.INT, - getString = { it.router.retrievalK.toString() }, + getString = { it.talkie.retrievalK.toString() }, withString = { c, v -> rtr(c) { it.copy(retrievalK = int("router.retrieval_k", v)) } }, ), EditableField( "router.token_budget", ConfigFieldType.INT, - getString = { it.router.tokenBudget.toString() }, + getString = { it.talkie.tokenBudget.toString() }, withString = { c, v -> rtr(c) { it.copy(tokenBudget = int("router.token_budget", v)) } }, ), EditableField( "router.generation.temperature", ConfigFieldType.DOUBLE, - getString = { it.router.generation.temperature.toString() }, + getString = { it.talkie.generation.temperature.toString() }, withString = { c, v -> gen(c) { it.copy(temperature = dbl("router.generation.temperature", v)) } }, ), EditableField( "router.generation.top_p", ConfigFieldType.DOUBLE, - getString = { it.router.generation.topP.toString() }, + getString = { it.talkie.generation.topP.toString() }, withString = { c, v -> gen(c) { it.copy(topP = dbl("router.generation.top_p", v)) } }, ), EditableField( "router.generation.max_tokens", ConfigFieldType.INT, - getString = { it.router.generation.maxTokens.toString() }, + getString = { it.talkie.generation.maxTokens.toString() }, withString = { c, v -> gen(c) { it.copy(maxTokens = int("router.generation.max_tokens", v)) } }, ), EditableField( "router.narration.temperature", ConfigFieldType.DOUBLE, - getString = { it.router.narration.temperature.toString() }, + getString = { it.talkie.narration.temperature.toString() }, withString = { c, v -> nar(c) { it.copy(temperature = dbl("router.narration.temperature", v)) } }, ), EditableField( "router.narration.top_p", ConfigFieldType.DOUBLE, - getString = { it.router.narration.topP.toString() }, + getString = { it.talkie.narration.topP.toString() }, withString = { c, v -> nar(c) { it.copy(topP = dbl("router.narration.top_p", v)) } }, ), EditableField( "router.narration.max_tokens", ConfigFieldType.INT, - getString = { it.router.narration.maxTokens.toString() }, + getString = { it.talkie.narration.maxTokens.toString() }, withString = { c, v -> nar(c) { it.copy(maxTokens = int("router.narration.max_tokens", v)) } }, ), EditableField( "router.narration.max_per_run", ConfigFieldType.INT, - getString = { it.router.narration.maxPerRun.toString() }, + getString = { it.talkie.narration.maxPerRun.toString() }, withString = { c, v -> nar(c) { it.copy(maxPerRun = int("router.narration.max_per_run", v)) } }, ), EditableField( "router.narration.model_id", ConfigFieldType.STRING, - getString = { it.router.narration.modelId.orEmpty() }, + getString = { it.talkie.narration.modelId.orEmpty() }, withString = { c, v -> nar(c) { it.copy(modelId = v.trim().ifBlank { null }) } }, ), EditableField( @@ -215,7 +215,7 @@ private fun srv(c: CorrexConfig, f: (ServerConfig) -> ServerConfig) = c.copy(ser private fun tui(c: CorrexConfig, f: (TuiConfig) -> TuiConfig) = c.copy(tui = f(c.tui)) private fun cli(c: CorrexConfig, f: (CliConfig) -> CliConfig) = c.copy(cli = f(c.cli)) private fun tls(c: CorrexConfig, f: (ToolsConfig) -> ToolsConfig) = c.copy(tools = f(c.tools)) -private fun rtr(c: CorrexConfig, f: (RouterConfig) -> RouterConfig) = c.copy(router = f(c.router)) +private fun rtr(c: CorrexConfig, f: (TalkieConfig) -> TalkieConfig) = c.copy(talkie = f(c.talkie)) private fun gen(c: CorrexConfig, f: (GenerationSettings) -> GenerationSettings) = rtr(c) { it.copy(generation = f(it.generation)) } private fun nar(c: CorrexConfig, f: (NarrationSettings) -> NarrationSettings) = diff --git a/core/config/src/test/kotlin/com/correx/core/config/ConfigLoaderTest.kt b/core/config/src/test/kotlin/com/correx/core/config/ConfigLoaderTest.kt index 47f13725..305c0724 100644 --- a/core/config/src/test/kotlin/com/correx/core/config/ConfigLoaderTest.kt +++ b/core/config/src/test/kotlin/com/correx/core/config/ConfigLoaderTest.kt @@ -307,16 +307,16 @@ class ConfigLoaderTest { parseTomlMethod.isAccessible = true val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig - assertEquals("llamacpp", result.router.embedder.backend) - assertEquals(1536, result.router.embedder.dimension) - assertEquals("http://localhost:11000", result.router.embedder.url) - assertEquals("nomic-embed-text", result.router.embedder.modelId) + assertEquals("llamacpp", result.talkie.embedder.backend) + assertEquals(1536, result.talkie.embedder.dimension) + assertEquals("http://localhost:11000", result.talkie.embedder.url) + assertEquals("nomic-embed-text", result.talkie.embedder.modelId) - assertEquals("turbovec", result.router.l3.backend) - assertEquals("/tmp/router/l3.tq", result.router.l3.persistPath) - assertEquals("python3", result.router.l3.pythonExecutable) - assertEquals(1536, result.router.l3.dim) - assertEquals(4, result.router.l3.bitWidth) + assertEquals("turbovec", result.talkie.l3.backend) + assertEquals("/tmp/router/l3.tq", result.talkie.l3.persistPath) + assertEquals("python3", result.talkie.l3.pythonExecutable) + assertEquals(1536, result.talkie.l3.dim) + assertEquals(4, result.talkie.l3.bitWidth) } @Test @@ -331,16 +331,16 @@ class ConfigLoaderTest { parseTomlMethod.isAccessible = true val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig - assertEquals("noop", result.router.embedder.backend) - assertEquals(1536, result.router.embedder.dimension) - assertEquals(null, result.router.embedder.url) - assertEquals(null, result.router.embedder.modelId) + assertEquals("noop", result.talkie.embedder.backend) + assertEquals(1536, result.talkie.embedder.dimension) + assertEquals(null, result.talkie.embedder.url) + assertEquals(null, result.talkie.embedder.modelId) - assertEquals("in_memory", result.router.l3.backend) - assertEquals(null, result.router.l3.persistPath) - assertEquals("python3", result.router.l3.pythonExecutable) - assertEquals(1536, result.router.l3.dim) - assertEquals(4, result.router.l3.bitWidth) + assertEquals("in_memory", result.talkie.l3.backend) + assertEquals(null, result.talkie.l3.persistPath) + assertEquals("python3", result.talkie.l3.pythonExecutable) + assertEquals(1536, result.talkie.l3.dim) + assertEquals(4, result.talkie.l3.bitWidth) } @Test @@ -477,15 +477,15 @@ class ConfigLoaderTest { parseTomlMethod.isAccessible = true val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig - assertEquals(8, result.router.conversationKeepLast) - assertEquals(3, result.router.retrievalK) - assertEquals(8192, result.router.tokenBudget) - assertEquals(0.4, result.router.generation.temperature) - assertEquals(0.8, result.router.generation.topP) - assertEquals(600, result.router.generation.maxTokens) - assertEquals(0.55, result.router.narration.temperature) - assertEquals(2048, result.router.narration.maxTokens) - assertEquals(25, result.router.narration.maxPerRun) + assertEquals(8, result.talkie.conversationKeepLast) + assertEquals(3, result.talkie.retrievalK) + assertEquals(8192, result.talkie.tokenBudget) + assertEquals(0.4, result.talkie.generation.temperature) + assertEquals(0.8, result.talkie.generation.topP) + assertEquals(600, result.talkie.generation.maxTokens) + assertEquals(0.55, result.talkie.narration.temperature) + assertEquals(2048, result.talkie.narration.maxTokens) + assertEquals(25, result.talkie.narration.maxPerRun) } @Test @@ -494,10 +494,10 @@ class ConfigLoaderTest { parseTomlMethod.isAccessible = true val result = parseTomlMethod.invoke(ConfigLoader, "[server]\nhost = \"x\"") as CorrexConfig - assertEquals(6, result.router.conversationKeepLast) - assertEquals(512, result.router.generation.maxTokens) - assertEquals(4096, result.router.narration.maxTokens) - assertEquals(100, result.router.narration.maxPerRun) + assertEquals(6, result.talkie.conversationKeepLast) + assertEquals(512, result.talkie.generation.maxTokens) + assertEquals(4096, result.talkie.narration.maxTokens) + assertEquals(100, result.talkie.narration.maxPerRun) } @Test @@ -530,7 +530,7 @@ class ConfigLoaderTest { val result = ConfigLoader.parseTomlForTest(toml) - assertEquals("llama-cpp:phi-3-mini", result.router.narration.modelId) + assertEquals("llama-cpp:phi-3-mini", result.talkie.narration.modelId) } @Test @@ -542,6 +542,6 @@ class ConfigLoaderTest { val result = ConfigLoader.parseTomlForTest(toml) - assertNull(result.router.narration.modelId) + assertNull(result.talkie.narration.modelId) } } diff --git a/core/config/src/test/kotlin/com/correx/core/config/CorrexConfigWriterTest.kt b/core/config/src/test/kotlin/com/correx/core/config/CorrexConfigWriterTest.kt index 5bd23570..c89d57e0 100644 --- a/core/config/src/test/kotlin/com/correx/core/config/CorrexConfigWriterTest.kt +++ b/core/config/src/test/kotlin/com/correx/core/config/CorrexConfigWriterTest.kt @@ -26,7 +26,7 @@ class CorrexConfigWriterTest { shellAllowedExecutables = listOf("git", "ls"), networkAllowedHosts = listOf("example.com"), ), - router = RouterConfig( + talkie = TalkieConfig( conversationKeepLast = 7, retrievalK = 4, tokenBudget = 5000, diff --git a/core/config/src/test/kotlin/com/correx/core/config/EditableConfigTest.kt b/core/config/src/test/kotlin/com/correx/core/config/EditableConfigTest.kt index 11e6f1e8..53643cdf 100644 --- a/core/config/src/test/kotlin/com/correx/core/config/EditableConfigTest.kt +++ b/core/config/src/test/kotlin/com/correx/core/config/EditableConfigTest.kt @@ -25,7 +25,7 @@ class EditableConfigTest { CorrexConfig(), mapOf("router.generation.temperature" to "0.25", "orchestration.stage_timeout_ms" to "90000"), ).getOrThrow() - assertEquals(0.25, out.router.generation.temperature) + assertEquals(0.25, out.talkie.generation.temperature) assertEquals(90_000L, out.orchestration.stageTimeoutMs) } diff --git a/core/events/src/main/kotlin/com/correx/core/events/events/RouterEvents.kt b/core/events/src/main/kotlin/com/correx/core/events/events/TalkieEvents.kt similarity index 97% rename from core/events/src/main/kotlin/com/correx/core/events/events/RouterEvents.kt rename to core/events/src/main/kotlin/com/correx/core/events/events/TalkieEvents.kt index c6b438ff..af8b276d 100644 --- a/core/events/src/main/kotlin/com/correx/core/events/events/RouterEvents.kt +++ b/core/events/src/main/kotlin/com/correx/core/events/events/TalkieEvents.kt @@ -53,7 +53,7 @@ data class ContextTruncatedEvent( @Serializable @SerialName("RouterNarration") -data class RouterNarrationEvent( +data class TalkieNarrationEvent( val sessionId: SessionId, val narrationId: String, val trigger: String, diff --git a/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt b/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt index 9f07ba22..3e9a8b89 100644 --- a/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt +++ b/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt @@ -20,7 +20,7 @@ import com.correx.core.events.events.ClarificationRequestedEvent import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.CritiqueFindingsRecordedEvent import com.correx.core.events.events.CritiqueOutcomeCorrelatedEvent -import com.correx.core.events.events.RouterNarrationEvent +import com.correx.core.events.events.TalkieNarrationEvent import com.correx.core.events.events.OperatorProfileBoundEvent import com.correx.core.events.events.ProjectProfileBoundEvent import com.correx.core.events.events.SessionWorkspaceBoundEvent @@ -147,7 +147,7 @@ val eventModule = SerializersModule { subclass(ChatSessionStartedEvent::class) subclass(SessionWorkingTaskEvent::class) subclass(ChatTurnEvent::class) - subclass(RouterNarrationEvent::class) + subclass(TalkieNarrationEvent::class) subclass(SessionWorkspaceBoundEvent::class) subclass(OperatorProfileBoundEvent::class) subclass(ProjectProfileBoundEvent::class) diff --git a/core/events/src/test/kotlin/com/correx/core/events/serialization/RouterNarrationEventSerializationTest.kt b/core/events/src/test/kotlin/com/correx/core/events/serialization/TalkieNarrationEventSerializationTest.kt similarity index 79% rename from core/events/src/test/kotlin/com/correx/core/events/serialization/RouterNarrationEventSerializationTest.kt rename to core/events/src/test/kotlin/com/correx/core/events/serialization/TalkieNarrationEventSerializationTest.kt index fbc55490..789652c2 100644 --- a/core/events/src/test/kotlin/com/correx/core/events/serialization/RouterNarrationEventSerializationTest.kt +++ b/core/events/src/test/kotlin/com/correx/core/events/serialization/TalkieNarrationEventSerializationTest.kt @@ -1,16 +1,16 @@ package com.correx.core.events.serialization import com.correx.core.events.events.EventPayload -import com.correx.core.events.events.RouterNarrationEvent +import com.correx.core.events.events.TalkieNarrationEvent import com.correx.core.events.types.SessionId import com.correx.core.inference.TokenUsage import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -class RouterNarrationEventSerializationTest { +class TalkieNarrationEventSerializationTest { - private val sample = RouterNarrationEvent( + private val sample = TalkieNarrationEvent( sessionId = SessionId("sess-1"), narrationId = "narr-42", trigger = "stage_start", @@ -24,6 +24,8 @@ class RouterNarrationEventSerializationTest { @Test fun `round-trips as polymorphic EventPayload`() { val encoded = eventJson.encodeToString(EventPayload.serializer(), sample) + // Wire discriminator is intentionally still "RouterNarration": the class was renamed + // Router->Talkie, but changing a persisted @SerialName would break replay of existing logs. assertTrue(encoded.contains("\"type\":\"RouterNarration\""), "SerialName must be present: $encoded") val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded) assertEquals(sample, decoded) @@ -31,7 +33,7 @@ class RouterNarrationEventSerializationTest { @Test fun `round-trips with optional fields absent`() { - val minimal = RouterNarrationEvent( + val minimal = TalkieNarrationEvent( sessionId = SessionId("sess-2"), narrationId = "narr-0", trigger = "manual", diff --git a/core/router/src/main/kotlin/com/correx/core/router/RouterProjector.kt b/core/router/src/main/kotlin/com/correx/core/router/RouterProjector.kt deleted file mode 100644 index 844d9676..00000000 --- a/core/router/src/main/kotlin/com/correx/core/router/RouterProjector.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.correx.core.router - -import com.correx.core.events.events.StoredEvent -import com.correx.core.router.model.RouterState -import com.correx.core.sessions.projections.Projection - -class RouterProjector( - private val reducer: RouterReducer, -) : Projection { - - override fun initial(): RouterState = reducer.initial - - override fun apply(state: RouterState, event: StoredEvent): RouterState = - reducer.reduce(state, event) -} diff --git a/core/router/src/main/kotlin/com/correx/core/router/RouterRepository.kt b/core/router/src/main/kotlin/com/correx/core/router/RouterRepository.kt deleted file mode 100644 index aa687e17..00000000 --- a/core/router/src/main/kotlin/com/correx/core/router/RouterRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.correx.core.router - -import com.correx.core.events.types.SessionId -import com.correx.core.router.model.RouterState -import com.correx.core.sessions.projections.replay.EventReplayer - -interface RouterRepository { - suspend fun getRouterState(sessionId: SessionId): RouterState -} - -class DefaultRouterRepository( - private val replayer: EventReplayer, -) : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = - replayer.rebuild(sessionId) -} diff --git a/core/router/AGENTS.md b/core/talkie/AGENTS.md similarity index 100% rename from core/router/AGENTS.md rename to core/talkie/AGENTS.md diff --git a/core/router/build.gradle b/core/talkie/build.gradle similarity index 100% rename from core/router/build.gradle rename to core/talkie/build.gradle diff --git a/core/router/src/main/kotlin/com/correx/core/router/IdeaReader.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/IdeaReader.kt similarity index 96% rename from core/router/src/main/kotlin/com/correx/core/router/IdeaReader.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/IdeaReader.kt index d724c88a..d24755c2 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/IdeaReader.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/IdeaReader.kt @@ -1,11 +1,11 @@ -package com.correx.core.router +package com.correx.core.talkie import com.correx.core.events.events.IdeaCapturedEvent import com.correx.core.events.events.IdeaDiscardedEvent import com.correx.core.events.events.IdeaPromotedEvent import com.correx.core.events.stores.EventStore import com.correx.core.events.types.SessionId -import com.correx.core.router.model.Idea +import com.correx.core.talkie.model.Idea /** * Rebuilds the operator's idea board from the event log. Cross-session by design: it folds over diff --git a/core/router/src/main/kotlin/com/correx/core/router/Ideas.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/Ideas.kt similarity index 98% rename from core/router/src/main/kotlin/com/correx/core/router/Ideas.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/Ideas.kt index fda44e0a..baaa22a9 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/Ideas.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/Ideas.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject diff --git a/core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieContextBuilder.kt similarity index 96% rename from core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieContextBuilder.kt index e35fef3e..dc76a70d 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieContextBuilder.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import com.correx.core.context.model.CompressionMetadata import com.correx.core.context.model.ContextEntry @@ -11,20 +11,20 @@ import com.correx.core.events.types.ContextPackId import com.correx.core.events.types.SessionId import com.correx.core.events.types.StageId import com.correx.core.inference.Tokenizer -import com.correx.core.router.model.Idea -import com.correx.core.router.model.NarrationTrigger -import com.correx.core.router.model.RouterConfig -import com.correx.core.router.model.RouterL2Entry -import com.correx.core.router.model.RouterState -import com.correx.core.router.model.RouterTurn -import com.correx.core.router.model.TurnRole -import com.correx.core.router.model.WorkflowSummary +import com.correx.core.talkie.model.Idea +import com.correx.core.talkie.model.NarrationTrigger +import com.correx.core.talkie.model.TalkieConfig +import com.correx.core.talkie.model.RouterL2Entry +import com.correx.core.talkie.model.TalkieState +import com.correx.core.talkie.model.TalkieTurn +import com.correx.core.talkie.model.TurnRole +import com.correx.core.talkie.model.WorkflowSummary import java.util.* import kotlinx.coroutines.CancellationException -interface RouterContextBuilder { +interface TalkieContextBuilder { suspend fun build( - state: RouterState, + state: TalkieState, budget: TokenBudget, availableWorkflows: List = emptyList(), projectProfileText: String? = null, @@ -38,18 +38,18 @@ interface RouterContextBuilder { * Default delegates to [build] so that anonymous test stubs that only override [build] compile * without needing a stub override. */ - suspend fun buildNarrationContext(state: RouterState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = + suspend fun buildNarrationContext(state: TalkieState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = build(state, budget, emptyList(), null) } -class DefaultRouterContextBuilder( - private val config: RouterConfig, +class DefaultTalkieContextBuilder( + private val config: TalkieConfig, private val tokenizer: Tokenizer? = null, // Cross-session ideas to surface to the router (router-only context). Read fresh each build so // newly-captured ideas appear on the next turn; injected via the constructor so the build() // interface — and its many test doubles — stay unchanged. private val ideaProvider: () -> List = { emptyList() }, -) : RouterContextBuilder { +) : TalkieContextBuilder { companion object { private val SYSTEM_PROMPT = @@ -106,7 +106,7 @@ class DefaultRouterContextBuilder( } override suspend fun build( - state: RouterState, + state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?, @@ -179,7 +179,7 @@ class DefaultRouterContextBuilder( // --- Conversation turns (L1) --- // The current (protected) user turn is the last element in conversationHistory, // regardless of conversationKeepLast — it must always be present. - val protectedUserTurn: RouterTurn? = state.conversationHistory.lastOrNull() + val protectedUserTurn: TalkieTurn? = state.conversationHistory.lastOrNull() // Apply conversationKeepLast cap. The protected turn may or may not fall within this window. val cappedTurns = state.conversationHistory.takeLast(config.conversationKeepLast) @@ -317,7 +317,7 @@ class DefaultRouterContextBuilder( ) } - private fun buildWorkflowStatusContent(state: RouterState, stageOverride: String? = null): String { + private fun buildWorkflowStatusContent(state: TalkieState, stageOverride: String? = null): String { val status = capitalizeFirst(state.workflowStatus.name.lowercase()) val stage = stageOverride ?: state.currentStageId?.value ?: "none" return "Status: $status, Stage: $stage" @@ -400,7 +400,7 @@ class DefaultRouterContextBuilder( * narration lines never enter the conversation flow. */ override suspend fun buildNarrationContext( - state: RouterState, + state: TalkieState, trigger: NarrationTrigger, budget: TokenBudget, ): ContextPack { diff --git a/core/router/src/main/kotlin/com/correx/core/router/RouterFacade.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieFacade.kt similarity index 93% rename from core/router/src/main/kotlin/com/correx/core/router/RouterFacade.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieFacade.kt index 8051e712..3b27af9f 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/RouterFacade.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieFacade.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.ChatTurnRole @@ -8,7 +8,7 @@ import com.correx.core.events.events.IdeaCapturedEvent import com.correx.core.events.events.L3MemoryRetrievedEvent import com.correx.core.events.events.L3RetrievedHit import com.correx.core.events.events.NewEvent -import com.correx.core.events.events.RouterNarrationEvent +import com.correx.core.events.events.TalkieNarrationEvent import com.correx.core.events.events.SteeringNoteAddedEvent import com.correx.core.events.events.WorkflowProposedEvent import com.correx.core.events.stores.EventStore @@ -23,53 +23,53 @@ import com.correx.core.inference.InferenceRouter import com.correx.core.inference.ModelCapability import com.correx.core.inference.ResponseFormat import com.correx.core.inference.TokenUsage -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query -import com.correx.core.router.model.NarrationTrigger -import com.correx.core.router.model.RouterConfig -import com.correx.core.router.model.RouterResponse -import com.correx.core.router.model.WorkflowSummary +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query +import com.correx.core.talkie.model.NarrationTrigger +import com.correx.core.talkie.model.TalkieConfig +import com.correx.core.talkie.model.TalkieResponse +import com.correx.core.talkie.model.WorkflowSummary import kotlinx.coroutines.CancellationException import kotlinx.datetime.Clock import org.slf4j.LoggerFactory import java.util.UUID -private val log = LoggerFactory.getLogger(DefaultRouterFacade::class.java) +private val log = LoggerFactory.getLogger(DefaultTalkieFacade::class.java) -interface RouterFacade { +interface TalkieFacade { suspend fun onUserInput( sessionId: SessionId, input: String, mode: ChatMode = ChatMode.CHAT, - ): RouterResponse + ): TalkieResponse suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) } -class DefaultRouterFacade( - private val routerRepository: RouterRepository, - private val routerContextBuilder: RouterContextBuilder, +class DefaultTalkieFacade( + private val routerRepository: TalkieRepository, + private val routerContextBuilder: TalkieContextBuilder, private val inferenceRouter: InferenceRouter, private val eventStore: EventStore, - private val config: RouterConfig, + private val config: TalkieConfig, private val validateSteering: (suspend (String) -> String?)? = null, private val embedder: Embedder, private val l3MemoryStore: L3MemoryStore, private val workflowSummaryProvider: () -> List = { emptyList() }, private val sessionProfileProvider: suspend (SessionId) -> String? = { null }, -) : RouterFacade { +) : TalkieFacade { override suspend fun onUserInput( sessionId: SessionId, input: String, mode: ChatMode, - ): RouterResponse { + ): TalkieResponse { // Emit USER turn event and get the turnId for retrieval dedup val userTurnId = emitChatTurn(sessionId, input, ChatTurnRole.USER) // Rebuild state with user turn appended - var state = routerRepository.getRouterState(sessionId) + var state = routerRepository.getTalkieState(sessionId) val effectiveStageId = state.currentStageId ?: StageId.NONE // L3 retrieval — non-fatal; results fed back into state via event. @@ -119,7 +119,7 @@ class DefaultRouterFacade( ), ) // Rebuild state so lastRetrievedMemory reflects this turn - state = routerRepository.getRouterState(sessionId) + state = routerRepository.getTalkieState(sessionId) val contextPack = routerContextBuilder.build(state, config.tokenBudget, workflowSummaryProvider(), sessionProfileProvider(sessionId)) @@ -199,11 +199,11 @@ class DefaultRouterFacade( } } - return RouterResponse(content = content, steeringEmitted = steeringEmitted) + return TalkieResponse(content = content, steeringEmitted = steeringEmitted) } override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { - val state = routerRepository.getRouterState(sessionId) + val state = routerRepository.getTalkieState(sessionId) val effectiveStageId = trigger.stageId?.let { StageId(it) } ?: state.currentStageId ?: StageId.NONE val contextPack = routerContextBuilder.buildNarrationContext(state, trigger, config.tokenBudget) @@ -234,7 +234,7 @@ class DefaultRouterFacade( causationId = null, correlationId = null, ), - payload = RouterNarrationEvent( + payload = TalkieNarrationEvent( sessionId = sessionId, narrationId = narrationId, trigger = trigger.kind, diff --git a/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieProjector.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieProjector.kt new file mode 100644 index 00000000..c0b213cb --- /dev/null +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieProjector.kt @@ -0,0 +1,15 @@ +package com.correx.core.talkie + +import com.correx.core.events.events.StoredEvent +import com.correx.core.talkie.model.TalkieState +import com.correx.core.sessions.projections.Projection + +class TalkieProjector( + private val reducer: TalkieReducer, +) : Projection { + + override fun initial(): TalkieState = reducer.initial + + override fun apply(state: TalkieState, event: StoredEvent): TalkieState = + reducer.reduce(state, event) +} diff --git a/core/router/src/main/kotlin/com/correx/core/router/RouterReducer.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieReducer.kt similarity index 75% rename from core/router/src/main/kotlin/com/correx/core/router/RouterReducer.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieReducer.kt index ec6282b9..ca924502 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/RouterReducer.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieReducer.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.L3MemoryRetrievedEvent @@ -11,22 +11,22 @@ import com.correx.core.events.events.StoredEvent 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.router.model.RouterL2Entry -import com.correx.core.router.model.RouterState -import com.correx.core.router.model.RouterTurn -import com.correx.core.router.model.StageOutcomeKind -import com.correx.core.router.model.TurnRole -import com.correx.core.router.model.WorkflowStatus +import com.correx.core.talkie.model.RouterL2Entry +import com.correx.core.talkie.model.TalkieState +import com.correx.core.talkie.model.TalkieTurn +import com.correx.core.talkie.model.StageOutcomeKind +import com.correx.core.talkie.model.TurnRole +import com.correx.core.talkie.model.WorkflowStatus import kotlinx.datetime.Instant -interface RouterReducer { - val initial: RouterState - fun reduce(state: RouterState, event: StoredEvent): RouterState +interface TalkieReducer { + val initial: TalkieState + fun reduce(state: TalkieState, event: StoredEvent): TalkieState } -class DefaultRouterReducer : RouterReducer { +class DefaultTalkieReducer : TalkieReducer { - override val initial: RouterState = RouterState( + override val initial: TalkieState = TalkieState( sessionId = null, workflowStatus = WorkflowStatus.IDLE, currentStageId = null, @@ -34,7 +34,7 @@ class DefaultRouterReducer : RouterReducer { conversationHistory = emptyList(), ) - override fun reduce(state: RouterState, event: StoredEvent): RouterState { + override fun reduce(state: TalkieState, event: StoredEvent): TalkieState { return when (val payload = event.payload) { is WorkflowStartedEvent -> handleWorkflowStarted(state, event) is WorkflowCompletedEvent -> handleWorkflowCompleted(state) @@ -50,7 +50,7 @@ class DefaultRouterReducer : RouterReducer { } } - private fun handleWorkflowStarted(state: RouterState, event: StoredEvent): RouterState { + private fun handleWorkflowStarted(state: TalkieState, event: StoredEvent): TalkieState { val payload = event.payload as WorkflowStartedEvent return state.copy( sessionId = payload.sessionId, @@ -59,33 +59,33 @@ class DefaultRouterReducer : RouterReducer { ) } - private fun handleWorkflowCompleted(state: RouterState): RouterState { + private fun handleWorkflowCompleted(state: TalkieState): TalkieState { return state.copy( workflowStatus = WorkflowStatus.COMPLETED, currentStageId = null ) } - private fun handleWorkflowFailed(state: RouterState): RouterState { + private fun handleWorkflowFailed(state: TalkieState): TalkieState { return state.copy( workflowStatus = WorkflowStatus.FAILED, currentStageId = null ) } - private fun handleOrchestrationPaused(state: RouterState): RouterState { + private fun handleOrchestrationPaused(state: TalkieState): TalkieState { return state.copy( workflowStatus = WorkflowStatus.PAUSED ) } - private fun handleOrchestrationResumed(state: RouterState): RouterState { + private fun handleOrchestrationResumed(state: TalkieState): TalkieState { return state.copy( workflowStatus = WorkflowStatus.RUNNING ) } - private fun handleStageCompleted(state: RouterState, event: StoredEvent): RouterState { + private fun handleStageCompleted(state: TalkieState, event: StoredEvent): TalkieState { val payload = event.payload as StageCompletedEvent val entry = RouterL2Entry( stageId = payload.stageId, @@ -100,7 +100,7 @@ class DefaultRouterReducer : RouterReducer { ) } - private fun handleStageFailed(state: RouterState, event: StoredEvent): RouterState { + private fun handleStageFailed(state: TalkieState, event: StoredEvent): TalkieState { val payload = event.payload as StageFailedEvent val entry = RouterL2Entry( stageId = payload.stageId, @@ -120,16 +120,16 @@ class DefaultRouterReducer : RouterReducer { // adr-0003 §4: harvest user steering into the current stage's decision point. // Accumulate here; handleStageCompleted/Failed attach and clear. Pure field // extraction from the event stream — no inference. - private fun handleSteeringNote(state: RouterState, payload: SteeringNoteAddedEvent): RouterState = + private fun handleSteeringNote(state: TalkieState, payload: SteeringNoteAddedEvent): TalkieState = state.copy(pendingSteeringNotes = state.pendingSteeringNotes + payload.content) - private fun handleChatTurn(state: RouterState, event: StoredEvent): RouterState { + private fun handleChatTurn(state: TalkieState, event: StoredEvent): TalkieState { val payload = event.payload as ChatTurnEvent val turnRole = when (payload.role) { com.correx.core.events.events.ChatTurnRole.USER -> TurnRole.USER com.correx.core.events.events.ChatTurnRole.ROUTER -> TurnRole.ROUTER } - val turn = RouterTurn( + val turn = TalkieTurn( role = turnRole, content = payload.content, timestamp = Instant.fromEpochMilliseconds(payload.timestampMs), diff --git a/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieRepository.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieRepository.kt new file mode 100644 index 00000000..4c8baa77 --- /dev/null +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/TalkieRepository.kt @@ -0,0 +1,16 @@ +package com.correx.core.talkie + +import com.correx.core.events.types.SessionId +import com.correx.core.talkie.model.TalkieState +import com.correx.core.sessions.projections.replay.EventReplayer + +interface TalkieRepository { + suspend fun getTalkieState(sessionId: SessionId): TalkieState +} + +class DefaultTalkieRepository( + private val replayer: EventReplayer, +) : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = + replayer.rebuild(sessionId) +} diff --git a/core/router/src/main/kotlin/com/correx/core/router/WorkflowProposals.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/WorkflowProposals.kt similarity index 98% rename from core/router/src/main/kotlin/com/correx/core/router/WorkflowProposals.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/WorkflowProposals.kt index 2927f99b..554f6843 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/WorkflowProposals.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/WorkflowProposals.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import com.correx.core.events.events.ProposedWorkflow import kotlinx.serialization.json.Json diff --git a/core/router/src/main/kotlin/com/correx/core/router/l3/InMemoryL3MemoryStore.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/InMemoryL3MemoryStore.kt similarity index 98% rename from core/router/src/main/kotlin/com/correx/core/router/l3/InMemoryL3MemoryStore.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/l3/InMemoryL3MemoryStore.kt index 4ed7df66..65a6243e 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/l3/InMemoryL3MemoryStore.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/InMemoryL3MemoryStore.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock diff --git a/core/router/src/main/kotlin/com/correx/core/router/l3/L3Hit.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3Hit.kt similarity index 79% rename from core/router/src/main/kotlin/com/correx/core/router/l3/L3Hit.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3Hit.kt index 77a7d2f5..3666c4de 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/l3/L3Hit.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3Hit.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 import kotlinx.serialization.Serializable diff --git a/core/router/src/main/kotlin/com/correx/core/router/l3/L3MemoryEntry.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MemoryEntry.kt similarity index 97% rename from core/router/src/main/kotlin/com/correx/core/router/l3/L3MemoryEntry.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MemoryEntry.kt index 5e658d45..fb53352f 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/l3/L3MemoryEntry.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MemoryEntry.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 import com.correx.core.events.types.SessionId import kotlinx.serialization.Serializable diff --git a/core/router/src/main/kotlin/com/correx/core/router/l3/L3MemoryStore.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MemoryStore.kt similarity index 95% rename from core/router/src/main/kotlin/com/correx/core/router/l3/L3MemoryStore.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MemoryStore.kt index b3777302..ced27ae6 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/l3/L3MemoryStore.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MemoryStore.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 /** * Cross-session memory store interface for vector-based retrieval. diff --git a/core/router/src/main/kotlin/com/correx/core/router/l3/L3MetadataRehydrator.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MetadataRehydrator.kt similarity index 97% rename from core/router/src/main/kotlin/com/correx/core/router/l3/L3MetadataRehydrator.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MetadataRehydrator.kt index b9e63939..d7b57869 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/l3/L3MetadataRehydrator.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3MetadataRehydrator.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.stores.EventStore diff --git a/core/router/src/main/kotlin/com/correx/core/router/l3/L3Query.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3Query.kt similarity index 96% rename from core/router/src/main/kotlin/com/correx/core/router/l3/L3Query.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3Query.kt index 3ac4d550..c249d8b7 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/l3/L3Query.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/L3Query.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 import com.correx.core.events.types.SessionId import kotlinx.serialization.Serializable diff --git a/core/router/src/main/kotlin/com/correx/core/router/l3/RehydratableL3MemoryStore.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/RehydratableL3MemoryStore.kt similarity index 96% rename from core/router/src/main/kotlin/com/correx/core/router/l3/RehydratableL3MemoryStore.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/l3/RehydratableL3MemoryStore.kt index 9597e5e3..1bd10ae2 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/l3/RehydratableL3MemoryStore.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/l3/RehydratableL3MemoryStore.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 /** * An [L3MemoryStore] whose vectors persist independently of the JVM (e.g. a diff --git a/core/router/src/main/kotlin/com/correx/core/router/model/Idea.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/Idea.kt similarity index 83% rename from core/router/src/main/kotlin/com/correx/core/router/model/Idea.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/model/Idea.kt index 8f2318e5..3ec9c6c4 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/model/Idea.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/Idea.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.model +package com.correx.core.talkie.model /** An active idea on the operator's cross-session scratchpad (a captured, not-yet-discarded note). */ data class Idea( diff --git a/core/router/src/main/kotlin/com/correx/core/router/model/NarrationTrigger.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/NarrationTrigger.kt similarity index 88% rename from core/router/src/main/kotlin/com/correx/core/router/model/NarrationTrigger.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/model/NarrationTrigger.kt index f4ab1b38..ccb65615 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/model/NarrationTrigger.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/NarrationTrigger.kt @@ -1,9 +1,9 @@ -package com.correx.core.router.model +package com.correx.core.talkie.model /** * Describes the event or condition that prompted a narration inference call. * - * The trigger is stored verbatim in [RouterNarrationEvent.trigger] so it can be + * The trigger is stored verbatim in [TalkieNarrationEvent.trigger] so it can be * replayed and displayed without re-querying the environment. */ data class NarrationTrigger( diff --git a/core/router/src/main/kotlin/com/correx/core/router/model/RouterConfig.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieConfig.kt similarity index 94% rename from core/router/src/main/kotlin/com/correx/core/router/model/RouterConfig.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieConfig.kt index c757aed6..c62d5cf1 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/model/RouterConfig.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieConfig.kt @@ -1,11 +1,11 @@ -package com.correx.core.router.model +package com.correx.core.talkie.model import com.correx.core.context.model.TokenBudget import com.correx.core.inference.GenerationConfig import kotlinx.serialization.Serializable @Serializable -data class RouterConfig( +data class TalkieConfig( val conversationKeepLast: Int = 6, val retrievalK: Int = 5, val tokenBudget: TokenBudget = TokenBudget(limit = 4096), diff --git a/core/router/src/main/kotlin/com/correx/core/router/model/RouterResponse.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieResponse.kt similarity index 66% rename from core/router/src/main/kotlin/com/correx/core/router/model/RouterResponse.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieResponse.kt index 96708f01..2c5573f3 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/model/RouterResponse.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieResponse.kt @@ -1,9 +1,9 @@ -package com.correx.core.router.model +package com.correx.core.talkie.model import kotlinx.serialization.Serializable @Serializable -data class RouterResponse( +data class TalkieResponse( val content: String, val steeringEmitted: Boolean = false, ) diff --git a/core/router/src/main/kotlin/com/correx/core/router/model/RouterState.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieState.kt similarity index 91% rename from core/router/src/main/kotlin/com/correx/core/router/model/RouterState.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieState.kt index 39382ea1..e2f26a70 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/model/RouterState.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/TalkieState.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.model +package com.correx.core.talkie.model import com.correx.core.events.events.L3RetrievedHit import com.correx.core.events.types.SessionId @@ -41,7 +41,7 @@ data class RouterL2Entry( ) @Serializable -data class RouterTurn( +data class TalkieTurn( val role: TurnRole, val content: String, val timestamp: Instant, @@ -49,12 +49,12 @@ data class RouterTurn( ) @Serializable -data class RouterState( +data class TalkieState( val sessionId: SessionId? = null, val workflowStatus: WorkflowStatus = WorkflowStatus.IDLE, val currentStageId: StageId? = null, val l2Memory: List = emptyList(), - val conversationHistory: List = emptyList(), + val conversationHistory: List = emptyList(), val lastRetrievedMemory: List = emptyList(), /** * Steering notes accumulated during the current stage, harvested into the diff --git a/core/router/src/main/kotlin/com/correx/core/router/model/WorkflowSummary.kt b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/WorkflowSummary.kt similarity index 75% rename from core/router/src/main/kotlin/com/correx/core/router/model/WorkflowSummary.kt rename to core/talkie/src/main/kotlin/com/correx/core/talkie/model/WorkflowSummary.kt index 711be304..2bf25034 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/model/WorkflowSummary.kt +++ b/core/talkie/src/main/kotlin/com/correx/core/talkie/model/WorkflowSummary.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.model +package com.correx.core.talkie.model data class WorkflowSummary( val id: String, diff --git a/core/router/src/test/kotlin/com/correx/core/router/IdeaReaderTest.kt b/core/talkie/src/test/kotlin/com/correx/core/talkie/IdeaReaderTest.kt similarity index 99% rename from core/router/src/test/kotlin/com/correx/core/router/IdeaReaderTest.kt rename to core/talkie/src/test/kotlin/com/correx/core/talkie/IdeaReaderTest.kt index 0a48ae10..03ed8dc9 100644 --- a/core/router/src/test/kotlin/com/correx/core/router/IdeaReaderTest.kt +++ b/core/talkie/src/test/kotlin/com/correx/core/talkie/IdeaReaderTest.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import com.correx.core.events.events.EventMetadata import com.correx.core.events.events.EventPayload diff --git a/core/router/src/test/kotlin/com/correx/core/router/IdeasTest.kt b/core/talkie/src/test/kotlin/com/correx/core/talkie/IdeasTest.kt similarity index 98% rename from core/router/src/test/kotlin/com/correx/core/router/IdeasTest.kt rename to core/talkie/src/test/kotlin/com/correx/core/talkie/IdeasTest.kt index 12f76ad6..2904920a 100644 --- a/core/router/src/test/kotlin/com/correx/core/router/IdeasTest.kt +++ b/core/talkie/src/test/kotlin/com/correx/core/talkie/IdeasTest.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue diff --git a/core/router/src/test/kotlin/com/correx/core/router/WorkflowProposalsTest.kt b/core/talkie/src/test/kotlin/com/correx/core/talkie/WorkflowProposalsTest.kt similarity index 98% rename from core/router/src/test/kotlin/com/correx/core/router/WorkflowProposalsTest.kt rename to core/talkie/src/test/kotlin/com/correx/core/talkie/WorkflowProposalsTest.kt index 9e16ed10..652b7302 100644 --- a/core/router/src/test/kotlin/com/correx/core/router/WorkflowProposalsTest.kt +++ b/core/talkie/src/test/kotlin/com/correx/core/talkie/WorkflowProposalsTest.kt @@ -1,4 +1,4 @@ -package com.correx.core.router +package com.correx.core.talkie import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue diff --git a/core/router/src/test/kotlin/com/correx/core/router/l3/InMemoryL3MemoryStoreTest.kt b/core/talkie/src/test/kotlin/com/correx/core/talkie/l3/InMemoryL3MemoryStoreTest.kt similarity index 99% rename from core/router/src/test/kotlin/com/correx/core/router/l3/InMemoryL3MemoryStoreTest.kt rename to core/talkie/src/test/kotlin/com/correx/core/talkie/l3/InMemoryL3MemoryStoreTest.kt index 2b074e1f..47a5527f 100644 --- a/core/router/src/test/kotlin/com/correx/core/router/l3/InMemoryL3MemoryStoreTest.kt +++ b/core/talkie/src/test/kotlin/com/correx/core/talkie/l3/InMemoryL3MemoryStoreTest.kt @@ -1,4 +1,4 @@ -package com.correx.core.router.l3 +package com.correx.core.talkie.l3 import com.correx.core.events.types.SessionId import com.correx.core.utils.TypeId diff --git a/infrastructure/build.gradle b/infrastructure/build.gradle index d8558551..98e87cb9 100644 --- a/infrastructure/build.gradle +++ b/infrastructure/build.gradle @@ -7,7 +7,7 @@ plugins { dependencies { implementation project(":core:tools") implementation project(":core:events") - implementation project(":core:router") + implementation project(":core:talkie") implementation project(":core:inference") implementation project(":core:approvals") implementation project(":core:sessions") diff --git a/infrastructure/router/build.gradle b/infrastructure/router/build.gradle index ba5b5485..f0275550 100644 --- a/infrastructure/router/build.gradle +++ b/infrastructure/router/build.gradle @@ -5,7 +5,7 @@ plugins { } dependencies { - implementation project(':core:router') + implementation project(':core:talkie') implementation project(':core:events') testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test" diff --git a/infrastructure/router/turbovec/build.gradle b/infrastructure/router/turbovec/build.gradle index 1eca6295..f77b6376 100644 --- a/infrastructure/router/turbovec/build.gradle +++ b/infrastructure/router/turbovec/build.gradle @@ -5,7 +5,7 @@ plugins { } dependencies { - implementation project(':core:router') + implementation project(':core:talkie') implementation project(':core:sessions') implementation project(':core:events') testImplementation "org.junit.jupiter:junit-jupiter" diff --git a/infrastructure/router/turbovec/src/main/kotlin/com/correx/infrastructure/router/turbovec/TurboVecL3MemoryStore.kt b/infrastructure/router/turbovec/src/main/kotlin/com/correx/infrastructure/router/turbovec/TurboVecL3MemoryStore.kt index eda5eeda..acf3d7d8 100644 --- a/infrastructure/router/turbovec/src/main/kotlin/com/correx/infrastructure/router/turbovec/TurboVecL3MemoryStore.kt +++ b/infrastructure/router/turbovec/src/main/kotlin/com/correx/infrastructure/router/turbovec/TurboVecL3MemoryStore.kt @@ -1,9 +1,9 @@ package com.correx.infrastructure.router.turbovec -import com.correx.core.router.l3.L3Hit -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3Query -import com.correx.core.router.l3.RehydratableL3MemoryStore +import com.correx.core.talkie.l3.L3Hit +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3Query +import com.correx.core.talkie.l3.RehydratableL3MemoryStore import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeout diff --git a/infrastructure/src/main/kotlin/com/correx/infrastructure/InfrastructureModule.kt b/infrastructure/src/main/kotlin/com/correx/infrastructure/InfrastructureModule.kt index 51725ed5..98ea2fb8 100644 --- a/infrastructure/src/main/kotlin/com/correx/infrastructure/InfrastructureModule.kt +++ b/infrastructure/src/main/kotlin/com/correx/infrastructure/InfrastructureModule.kt @@ -22,17 +22,17 @@ import com.correx.core.inference.InferenceRouter import com.correx.core.inference.ModelCapability import com.correx.core.inference.NoopEmbedder import com.correx.core.inference.Tokenizer -import com.correx.core.router.DefaultRouterContextBuilder -import com.correx.core.router.DefaultRouterFacade -import com.correx.core.router.IdeaReader -import com.correx.core.router.DefaultRouterReducer -import com.correx.core.router.DefaultRouterRepository -import com.correx.core.router.RouterFacade -import com.correx.core.router.RouterProjector -import com.correx.core.router.l3.InMemoryL3MemoryStore -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.model.RouterConfig -import com.correx.core.router.model.WorkflowSummary +import com.correx.core.talkie.DefaultTalkieContextBuilder +import com.correx.core.talkie.DefaultTalkieFacade +import com.correx.core.talkie.IdeaReader +import com.correx.core.talkie.DefaultTalkieReducer +import com.correx.core.talkie.DefaultTalkieRepository +import com.correx.core.talkie.TalkieFacade +import com.correx.core.talkie.TalkieProjector +import com.correx.core.talkie.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.model.TalkieConfig +import com.correx.core.talkie.model.WorkflowSummary import com.correx.core.events.types.SessionId import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.tools.contract.Tool @@ -306,22 +306,22 @@ object InfrastructureModule { } } - fun createRouterFacade( + fun createTalkieFacade( eventStore: EventStore, inferenceRouter: InferenceRouter, - config: RouterConfig = RouterConfig(), + config: TalkieConfig = TalkieConfig(), tokenizer: Tokenizer? = null, embedder: Embedder = createNoopEmbedder(), l3MemoryStore: L3MemoryStore = createInMemoryL3MemoryStore(), workflowSummaryProvider: () -> List = { emptyList() }, sessionProfileProvider: suspend (SessionId) -> String? = { null }, - ): RouterFacade { - val reducer = DefaultRouterReducer() - val projector = RouterProjector(reducer) + ): TalkieFacade { + val reducer = DefaultTalkieReducer() + val projector = TalkieProjector(reducer) val replayer = DefaultEventReplayer(eventStore, projector) - val repository = DefaultRouterRepository(replayer) + val repository = DefaultTalkieRepository(replayer) val ideaReader = IdeaReader(eventStore) - val contextBuilder = DefaultRouterContextBuilder(config, tokenizer, ideaProvider = { ideaReader.activeIdeas() }) - return DefaultRouterFacade(repository, contextBuilder, inferenceRouter, eventStore, config, null, embedder, l3MemoryStore, workflowSummaryProvider, sessionProfileProvider) + val contextBuilder = DefaultTalkieContextBuilder(config, tokenizer, ideaProvider = { ideaReader.activeIdeas() }) + return DefaultTalkieFacade(repository, contextBuilder, inferenceRouter, eventStore, config, null, embedder, l3MemoryStore, workflowSummaryProvider, sessionProfileProvider) } } diff --git a/settings.gradle b/settings.gradle index 73b6735f..40eeff5b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,7 +21,7 @@ include ':core:validation' include ':core:approvals' include ':core:tools' include ':core:toolintent' -include ':core:router' +include ':core:talkie' include ':core:sessions' include ':core:tasks' include ':core:config' diff --git a/testing/deterministic/build.gradle b/testing/deterministic/build.gradle index ea4dcb19..2dc645a4 100644 --- a/testing/deterministic/build.gradle +++ b/testing/deterministic/build.gradle @@ -6,7 +6,7 @@ plugins { dependencies { testImplementation(project(":core:events")) - testImplementation(project(":core:router")) + testImplementation(project(":core:talkie")) testImplementation(project(":core:sessions")) testImplementation(project(":core:transitions")) testImplementation(project(":core:validation")) diff --git a/testing/deterministic/src/test/kotlin/L3MetadataRehydratorTest.kt b/testing/deterministic/src/test/kotlin/L3MetadataRehydratorTest.kt index 4687fea0..6c52f317 100644 --- a/testing/deterministic/src/test/kotlin/L3MetadataRehydratorTest.kt +++ b/testing/deterministic/src/test/kotlin/L3MetadataRehydratorTest.kt @@ -8,12 +8,12 @@ import com.correx.core.events.events.StoredEvent import com.correx.core.events.stores.EventStore import com.correx.core.events.types.EventId import com.correx.core.events.types.SessionId -import com.correx.core.router.l3.InMemoryL3MemoryStore -import com.correx.core.router.l3.L3Hit -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3MetadataRehydrator -import com.correx.core.router.l3.L3Query -import com.correx.core.router.l3.RehydratableL3MemoryStore +import com.correx.core.talkie.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.L3Hit +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3MetadataRehydrator +import com.correx.core.talkie.l3.L3Query +import com.correx.core.talkie.l3.RehydratableL3MemoryStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.runBlocking import kotlinx.datetime.Instant diff --git a/testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt b/testing/deterministic/src/test/kotlin/TalkieContextBuilderTest.kt similarity index 87% rename from testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt rename to testing/deterministic/src/test/kotlin/TalkieContextBuilderTest.kt index 8b2426dc..83a23b8b 100644 --- a/testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt +++ b/testing/deterministic/src/test/kotlin/TalkieContextBuilderTest.kt @@ -5,15 +5,15 @@ import com.correx.core.context.model.TokenBudget import com.correx.core.events.events.L3RetrievedHit import com.correx.core.events.types.SessionId import com.correx.core.events.types.StageId -import com.correx.core.router.DefaultRouterContextBuilder -import com.correx.core.router.model.RouterConfig -import com.correx.core.router.model.WorkflowSummary -import com.correx.core.router.model.RouterL2Entry -import com.correx.core.router.model.RouterState -import com.correx.core.router.model.RouterTurn -import com.correx.core.router.model.StageOutcomeKind -import com.correx.core.router.model.TurnRole -import com.correx.core.router.model.WorkflowStatus +import com.correx.core.talkie.DefaultTalkieContextBuilder +import com.correx.core.talkie.model.TalkieConfig +import com.correx.core.talkie.model.WorkflowSummary +import com.correx.core.talkie.model.RouterL2Entry +import com.correx.core.talkie.model.TalkieState +import com.correx.core.talkie.model.TalkieTurn +import com.correx.core.talkie.model.StageOutcomeKind +import com.correx.core.talkie.model.TurnRole +import com.correx.core.talkie.model.WorkflowStatus import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import kotlinx.datetime.Instant @@ -24,12 +24,12 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -class RouterContextBuilderTest { +class TalkieContextBuilderTest { - private val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200)) - private val builder = DefaultRouterContextBuilder(config) + private val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200)) + private val builder = DefaultTalkieContextBuilder(config) - private fun buildPack(state: RouterState, budget: TokenBudget): ContextPack = runBlocking { + private fun buildPack(state: TalkieState, budget: TokenBudget): ContextPack = runBlocking { builder.build(state, budget) } @@ -43,12 +43,12 @@ class RouterContextBuilderTest { @Test fun `build fits within budget when all entries are small`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "hi", clock.now()), + TalkieTurn(TurnRole.USER, "hi", clock.now()), ), ) val pack = buildPack(state, TokenBudget(limit = 10000)) @@ -60,14 +60,14 @@ class RouterContextBuilderTest { fun `build drops entries when budget is exceeded`() { // Build a state where conversation + L2 entries together exceed the tight budget val longContent = "x".repeat(800) // ~400 tokens each - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, longContent, clock.now()), - RouterTurn(TurnRole.ROUTER, longContent, clock.now()), - RouterTurn(TurnRole.USER, longContent, clock.now()), + TalkieTurn(TurnRole.USER, longContent, clock.now()), + TalkieTurn(TurnRole.ROUTER, longContent, clock.now()), + TalkieTurn(TurnRole.USER, longContent, clock.now()), ), l2Memory = listOf( RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), @@ -82,10 +82,10 @@ class RouterContextBuilderTest { fun `build drops entries oldest-first for L2 memory when budget forces partial drop`() { // Each L2 entry: "Stage sN (SUCCESS): " + 200-char summary ≈ 55 tokens each. // Use a config with conversationKeepLast=0 so only L0 + L2 compete for budget. - val configNoConv = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) - val builderNoConv = DefaultRouterContextBuilder(configNoConv) + val configNoConv = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) + val builderNoConv = DefaultTalkieContextBuilder(configNoConv) val summary = "x".repeat(200) // ~50 tokens; with prefix "Stage sN (SUCCESS): " ~55 tokens total - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -115,18 +115,18 @@ class RouterContextBuilderTest { @Test fun `build drops conversation turns oldest-first when conversationKeepLast exceeded`() { - val configWide = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) - val builderWide = DefaultRouterContextBuilder(configWide) + val configWide = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) + val builderWide = DefaultTalkieContextBuilder(configWide) val longContent = "x".repeat(400) - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")), - RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-04T00:00:00Z")), + TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")), + TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-04T00:00:00Z")), ), ) // Budget allows L0 + 2 long conversation turns; oldest 2 turns are dropped. @@ -144,7 +144,7 @@ class RouterContextBuilderTest { @Test fun `L0 system prompt is always present`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.IDLE, currentStageId = null, @@ -156,7 +156,7 @@ class RouterContextBuilderTest { @Test fun `L0 workflow status is always present`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.FAILED, currentStageId = StageId("failed-stage"), @@ -171,7 +171,7 @@ class RouterContextBuilderTest { @Test fun `L0 entries survive zero budget`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.COMPLETED, currentStageId = null, @@ -185,12 +185,12 @@ class RouterContextBuilderTest { @Test fun `L0 entries survive budget that cannot cover L1 or L2`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "x".repeat(500), clock.now()), + TalkieTurn(TurnRole.USER, "x".repeat(500), clock.now()), ), l2Memory = listOf( RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()), @@ -215,10 +215,10 @@ class RouterContextBuilderTest { // Each L2 entry: "Stage sN (SUCCESS): " + 100-char summary ≈ 30 tokens each. // L0 ~30 tokens; budget 90 leaves ~60. Two entries (~30 each = ~60) fit; oldest dropped. val summary = "x".repeat(100) // ~25 tokens; with prefix ~30 tokens total - val configTight = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) - val builderTight = DefaultRouterContextBuilder(configTight) + val configTight = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) + val builderTight = DefaultTalkieContextBuilder(configTight) val baseTime = Instant.parse("2026-01-01T00:00:00Z") - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -251,9 +251,9 @@ class RouterContextBuilderTest { @Test fun `L2 entries fit within budget are all retained`() { - val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) - val builder = DefaultRouterContextBuilder(config) - val state = RouterState( + val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) + val builder = DefaultTalkieContextBuilder(config) + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -274,7 +274,7 @@ class RouterContextBuilderTest { @Test fun `system prompt is classified as L0`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.IDLE, ) @@ -286,7 +286,7 @@ class RouterContextBuilderTest { @Test fun `workflow status is classified as L0`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -298,13 +298,13 @@ class RouterContextBuilderTest { @Test fun `conversation turns are classified as L1`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "hello", clock.now()), - RouterTurn(TurnRole.ROUTER, "hi", clock.now()), + TalkieTurn(TurnRole.USER, "hello", clock.now()), + TalkieTurn(TurnRole.ROUTER, "hi", clock.now()), ), ) val pack = buildPack(state, TokenBudget(limit = 10000)) @@ -317,7 +317,7 @@ class RouterContextBuilderTest { @Test fun `l2 memory entries are classified as L2`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -335,12 +335,12 @@ class RouterContextBuilderTest { @Test fun `no entries other than L0 L1 L2 appear in pack`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "hello", clock.now()), + TalkieTurn(TurnRole.USER, "hello", clock.now()), ), l2Memory = listOf( RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), @@ -359,18 +359,18 @@ class RouterContextBuilderTest { @Test fun `conversationHistory respects conversationKeepLast from config`() { - val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) - val builder = DefaultRouterContextBuilder(config) - val state = RouterState( + val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) + val builder = DefaultTalkieContextBuilder(config) + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")), - RouterTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")), - RouterTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")), + TalkieTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")), + TalkieTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")), + TalkieTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")), ), ) val pack = runBlocking { builder.build(state, TokenBudget(limit = 10000)) } @@ -381,17 +381,17 @@ class RouterContextBuilderTest { @Test fun `conversationHistory takeLast preserves ordering`() { - val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000)) - val builder = DefaultRouterContextBuilder(config) - val state = RouterState( + val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000)) + val builder = DefaultTalkieContextBuilder(config) + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")), - RouterTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")), + TalkieTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")), + TalkieTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")), ), ) val pack = buildPack(state, TokenBudget(limit = 10000)) @@ -409,7 +409,7 @@ class RouterContextBuilderTest { @Test fun `build with empty state produces L0 only`() { - val state = RouterState() + val state = TalkieState() val pack = buildPack(state, TokenBudget(limit = 10000)) val l0Entries = pack.layers[ContextLayer.L0] assertNotNull(l0Entries) @@ -420,7 +420,7 @@ class RouterContextBuilderTest { @Test fun `pack contains correct metadata on empty state`() { - val state = RouterState() + val state = TalkieState() val pack = buildPack(state, TokenBudget(limit = 5000)) assertEquals(5000, pack.budgetLimit) assertEquals(0, pack.compressionMetadata.entriesDropped) @@ -430,7 +430,7 @@ class RouterContextBuilderTest { @Test fun `pack has correct context pack id and session info`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -447,7 +447,7 @@ class RouterContextBuilderTest { @Test fun `system prompt has expected content`() { - val state = RouterState() + val state = TalkieState() val pack = buildPack(state, TokenBudget(limit = 10000)) val systemEntry = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" } assertNotNull(systemEntry) @@ -461,7 +461,7 @@ class RouterContextBuilderTest { @Test fun `workflow status entry contains status and stage`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.COMPLETED, currentStageId = null, @@ -475,12 +475,12 @@ class RouterContextBuilderTest { @Test fun `conversation turn entry is formatted with role prefix`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "user message", clock.now()), + TalkieTurn(TurnRole.USER, "user message", clock.now()), ), ) val pack = buildPack(state, TokenBudget(limit = 10000)) @@ -492,7 +492,7 @@ class RouterContextBuilderTest { @Test fun `l2 entry is formatted with stage outcome and summary`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -516,12 +516,12 @@ class RouterContextBuilderTest { @Test fun `budgetUsed equals sum of tokenEstimate of all retained entries`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "hello world", clock.now()), + TalkieTurn(TurnRole.USER, "hello world", clock.now()), ), l2Memory = listOf( RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), @@ -535,14 +535,14 @@ class RouterContextBuilderTest { @Test fun `compressionMetadata entriesDropped reflects dropped count`() { - val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10)) - val builder = DefaultRouterContextBuilder(config) - val state = RouterState( + val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10)) + val builder = DefaultTalkieContextBuilder(config) + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "x".repeat(200), clock.now()), + TalkieTurn(TurnRole.USER, "x".repeat(200), clock.now()), ), l2Memory = listOf( RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()), @@ -561,17 +561,17 @@ class RouterContextBuilderTest { @Test fun `build with full state including all layers`() { - val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) - val builder = DefaultRouterContextBuilder(config) - val state = RouterState( + val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) + val builder = DefaultTalkieContextBuilder(config) + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")), - RouterTurn(TurnRole.USER, "second turn", Instant.parse("2026-01-03T00:00:00Z")), - RouterTurn(TurnRole.ROUTER, "second reply", Instant.parse("2026-01-04T00:00:00Z")), + TalkieTurn(TurnRole.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")), + TalkieTurn(TurnRole.USER, "second turn", Instant.parse("2026-01-03T00:00:00Z")), + TalkieTurn(TurnRole.ROUTER, "second reply", Instant.parse("2026-01-04T00:00:00Z")), ), l2Memory = listOf( RouterL2Entry( @@ -614,7 +614,7 @@ class RouterContextBuilderTest { @Test fun `build with null sessionId produces unknown pack id`() { - val state = RouterState( + val state = TalkieState( workflowStatus = WorkflowStatus.IDLE, ) val pack = buildPack(state, TokenBudget(limit = 1000)) @@ -627,7 +627,7 @@ class RouterContextBuilderTest { @Test fun `L3 hits injected into pack ordered by score descending`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -667,7 +667,7 @@ class RouterContextBuilderTest { @Test fun `L3 entries carry recalled memory label in content`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, lastRetrievedMemory = listOf( @@ -694,7 +694,7 @@ class RouterContextBuilderTest { fun `L3 hits dropped when budget exhausted and counted in entriesDropped`() { // Budget tight enough for L0 but not L3 entries with long text val longText = "x".repeat(800) // ~200 tokens - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, lastRetrievedMemory = listOf( @@ -721,14 +721,14 @@ class RouterContextBuilderTest { @Test fun `system prompt and current user turn survive tight budget`() { val longContent = "x".repeat(800) // ~200 tokens each - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z"), turnId = "old-turn"), - RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z"), turnId = "mid-turn"), - RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "current-turn"), + TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z"), turnId = "old-turn"), + TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z"), turnId = "mid-turn"), + TalkieTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "current-turn"), ), ) // Budget 60: L0 frame ~30 tokens -> remaining ~30; current user turn ~4 tokens -> protected. @@ -748,17 +748,17 @@ class RouterContextBuilderTest { @Test fun `oldest conversation turn is dropped before newest when budget is tight`() { - val config3 = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000)) - val builder3 = DefaultRouterContextBuilder(config3) + val config3 = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000)) + val builder3 = DefaultTalkieContextBuilder(config3) val longContent = "x".repeat(800) // ~200 tokens each - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "oldest turn $longContent", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"), - RouterTurn(TurnRole.ROUTER, "middle turn $longContent", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"), - RouterTurn(TurnRole.USER, "newest turn", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"), + TalkieTurn(TurnRole.USER, "oldest turn $longContent", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"), + TalkieTurn(TurnRole.ROUTER, "middle turn $longContent", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"), + TalkieTurn(TurnRole.USER, "newest turn", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"), ), ) // Budget 100: L0 frame ~30 tokens -> remaining ~70; newest turn ~3 tokens -> protected. @@ -776,12 +776,12 @@ class RouterContextBuilderTest { @Test fun `L3 and L2 dropped before recent conversation when budget is tight`() { val longContent = "x".repeat(200) // ~50 tokens each - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"), + TalkieTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"), ), lastRetrievedMemory = listOf( L3RetrievedHit( @@ -816,7 +816,7 @@ class RouterContextBuilderTest { @Test fun `available workflows entry is injected into L0 when non-empty`() { val workflows = listOf(WorkflowSummary("healthcheck", "Runs the health check pipeline", listOf("collect", "report"))) - val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), availableWorkflows = workflows) } + val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), availableWorkflows = workflows) } val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" } assertNotNull(entry) assertTrue(entry!!.content.contains("## Available workflows")) @@ -825,27 +825,27 @@ class RouterContextBuilderTest { @Test fun `available workflows entry is omitted when list is empty`() { - val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000)) } + val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000)) } assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" }) } @Test fun `project profile text is injected into L0 when non-null`() { val text = "## Project profile\nA Kotlin service.\n### Conventions\n- Use runCatching" - val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), projectProfileText = text) } + val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), projectProfileText = text) } val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" } assertEquals(text, entry!!.content) } @Test fun `project profile text is omitted when null or blank`() { - val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), projectProfileText = " ") } + val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), projectProfileText = " ") } assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" }) } @Test fun `system prompt contains triage directive not bare refusal`() { - val pack = buildPack(RouterState(), TokenBudget(limit = 10000)) + val pack = buildPack(TalkieState(), TokenBudget(limit = 10000)) val content = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" }!!.content assertFalse(content.contains("if something is not in context, say you do not have it")) assertTrue(content.contains("Available workflows", ignoreCase = true)) @@ -855,13 +855,13 @@ class RouterContextBuilderTest { @Test fun `truncatedLayers populated with affected layers when drops occur`() { val longText = "x".repeat(800) - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, longText, clock.now(), turnId = "old"), - RouterTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"), + TalkieTurn(TurnRole.USER, longText, clock.now(), turnId = "old"), + TalkieTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"), ), lastRetrievedMemory = listOf( L3RetrievedHit( @@ -889,12 +889,12 @@ class RouterContextBuilderTest { // The current user turn must still be included; budgetUsed must report the true total; // appliedStrategies must contain "BudgetExceeded". val longUserTurn = "x".repeat(200) // ~50 tokens - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, longUserTurn, clock.now(), turnId = "current"), + TalkieTurn(TurnRole.USER, longUserTurn, clock.now(), turnId = "current"), ), ) // Budget 1: system prompt + workflow status alone exceed this. @@ -928,16 +928,16 @@ class RouterContextBuilderTest { @Test fun `conversationKeepLast zero still includes the current user turn`() { - val configZero = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) - val builderZero = DefaultRouterContextBuilder(configZero) - val state = RouterState( + val configZero = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) + val builderZero = DefaultTalkieContextBuilder(configZero) + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "old turn 1", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"), - RouterTurn(TurnRole.ROUTER, "old reply", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"), - RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"), + TalkieTurn(TurnRole.USER, "old turn 1", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"), + TalkieTurn(TurnRole.ROUTER, "old reply", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"), + TalkieTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"), ), ) val pack = runBlocking { builderZero.build(state, TokenBudget(limit = 10000)) } @@ -955,7 +955,7 @@ class RouterContextBuilderTest { @Test fun `appliedStrategies includes L3Recall when lastRetrievedMemory is non-empty`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, @@ -978,7 +978,7 @@ class RouterContextBuilderTest { @Test fun `appliedStrategies does not include L3Recall when lastRetrievedMemory is empty`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, ) @@ -995,12 +995,12 @@ class RouterContextBuilderTest { @Test fun `layers map is sorted by ContextLayer ordinal`() { - val state = RouterState( + val state = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, conversationHistory = listOf( - RouterTurn(TurnRole.USER, "hello", clock.now()), + TalkieTurn(TurnRole.USER, "hello", clock.now()), ), l2Memory = listOf( RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), diff --git a/testing/deterministic/src/test/kotlin/RouterFacadeTest.kt b/testing/deterministic/src/test/kotlin/TalkieFacadeTest.kt similarity index 82% rename from testing/deterministic/src/test/kotlin/RouterFacadeTest.kt rename to testing/deterministic/src/test/kotlin/TalkieFacadeTest.kt index 89a038a8..d768a5a0 100644 --- a/testing/deterministic/src/test/kotlin/RouterFacadeTest.kt +++ b/testing/deterministic/src/test/kotlin/TalkieFacadeTest.kt @@ -29,26 +29,26 @@ import com.correx.core.inference.NoopEmbedder import com.correx.core.inference.ProviderHealth import com.correx.core.inference.ResponseFormat import com.correx.core.inference.TokenUsage -import com.correx.core.router.ChatMode -import com.correx.core.router.DefaultRouterContextBuilder -import com.correx.core.router.DefaultRouterFacade -import com.correx.core.router.DefaultRouterReducer -import com.correx.core.router.IdeaReader -import com.correx.core.router.RouterContextBuilder -import com.correx.core.router.RouterFacade -import com.correx.core.router.RouterProjector -import com.correx.core.router.RouterRepository -import com.correx.core.router.l3.InMemoryL3MemoryStore -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query -import com.correx.core.router.model.RouterConfig -import com.correx.core.router.model.RouterResponse -import com.correx.core.router.model.RouterState -import com.correx.core.router.model.RouterTurn -import com.correx.core.router.model.TurnRole -import com.correx.core.router.model.WorkflowSummary -import com.correx.core.router.model.WorkflowStatus +import com.correx.core.talkie.ChatMode +import com.correx.core.talkie.DefaultTalkieContextBuilder +import com.correx.core.talkie.DefaultTalkieFacade +import com.correx.core.talkie.DefaultTalkieReducer +import com.correx.core.talkie.IdeaReader +import com.correx.core.talkie.TalkieContextBuilder +import com.correx.core.talkie.TalkieFacade +import com.correx.core.talkie.TalkieProjector +import com.correx.core.talkie.TalkieRepository +import com.correx.core.talkie.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query +import com.correx.core.talkie.model.TalkieConfig +import com.correx.core.talkie.model.TalkieResponse +import com.correx.core.talkie.model.TalkieState +import com.correx.core.talkie.model.TalkieTurn +import com.correx.core.talkie.model.TurnRole +import com.correx.core.talkie.model.WorkflowSummary +import com.correx.core.talkie.model.WorkflowStatus import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.testing.fixtures.EventFixtures import com.correx.testing.fixtures.inference.MockTokenizer @@ -63,7 +63,7 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -class RouterFacadeTest { +class TalkieFacadeTest { // -------------------------------------------------------------------------- // CHAT mode @@ -135,21 +135,21 @@ class RouterFacadeTest { fun `STEERING mode appends SteeringNoteAddedEvent with current stageId`(): Unit = runBlocking { val mockStore = mockEventStore() val stageId = StageId("stage-A") - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = - RouterState( + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = + TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, ) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter("steering response"), eventStore = mockStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -166,26 +166,26 @@ class RouterFacadeTest { @Test fun `conversation history grows per call - user and router turns appended`(): Unit = runBlocking { - val capturedStates = mutableListOf() + val capturedStates = mutableListOf() val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { capturedStates.add(state) return emptyContextPack() } }, inferenceRouter = mockInferenceRouter("router reply"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -206,26 +206,26 @@ class RouterFacadeTest { @Test fun `conversation history is session-scoped - different sessions do not share history`(): Unit = runBlocking { - val capturedStates = mutableListOf() + val capturedStates = mutableListOf() val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { capturedStates.add(state) return emptyContextPack() } }, inferenceRouter = mockInferenceRouter("response"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -246,17 +246,17 @@ class RouterFacadeTest { @Test fun `state is passed through to context builder`(): Unit = runBlocking { - val capturedState = mutableListOf() - val mockContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { + val capturedState = mutableListOf() + val mockContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { capturedState.add(state) return emptyContextPack() } } - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = - RouterState( + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = + TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = StageId("s1"), @@ -265,7 +265,7 @@ class RouterFacadeTest { routerContextBuilder = mockContextBuilder, inferenceRouter = mockInferenceRouter("inference response"), eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -278,20 +278,20 @@ class RouterFacadeTest { @Test fun `budget is passed through to context builder`(): Unit = runBlocking { val capturedBudget = mutableListOf() - val mockContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { + val mockContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { capturedBudget.add(budget) return emptyContextPack() } } - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, routerContextBuilder = mockContextBuilder, inferenceRouter = mockInferenceRouter("response"), eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 4200)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 4200)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -313,20 +313,20 @@ class RouterFacadeTest { } } val stateStageId = StageId("state-stage") - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState( + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stateStageId, ) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter, eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -346,20 +346,20 @@ class RouterFacadeTest { return mockProvider("response") } } - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState( + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.IDLE, currentStageId = null, ) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter, eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -378,16 +378,16 @@ class RouterFacadeTest { return mockProviderWithCapture("response", capturedRequestIds) } } - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter, eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -408,16 +408,16 @@ class RouterFacadeTest { return mockProviderWithRequestCapture("response", capturedRequests) } } - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter, eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -431,12 +431,12 @@ class RouterFacadeTest { @Test fun `context pack is passed to inference provider`(): Unit = runBlocking { val capturedContextPacks = mutableListOf() - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { val pack = ContextPack( id = ContextPackId("test-pack"), sessionId = state.sessionId ?: SessionId("unknown"), @@ -478,7 +478,7 @@ class RouterFacadeTest { } }, eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -488,12 +488,12 @@ class RouterFacadeTest { @Test fun `responseFormat defaults to Text`(): Unit = runBlocking { val capturedRequests = mutableListOf() - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = object : InferenceRouter { override suspend fun route( @@ -504,7 +504,7 @@ class RouterFacadeTest { } }, eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -514,7 +514,7 @@ class RouterFacadeTest { } @Test - fun `onUserInput returns RouterResponse with content and mode flag`(): Unit = runBlocking { + fun `onUserInput returns TalkieResponse with content and mode flag`(): Unit = runBlocking { val mockStore = mockEventStore() val facade = facadeWithMocks(eventStore = mockStore, chatMode = ChatMode.STEERING) val response = facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!") @@ -576,19 +576,19 @@ class RouterFacadeTest { override fun capabilities(): Set = setOf(CapabilityScore(ModelCapability.General, 1.0)) } val eventStore = mockEventStore() - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = object : InferenceRouter { override suspend fun route(stageId: StageId, requiredCapabilities: Set): InferenceProvider = metricsProvider }, eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -717,16 +717,16 @@ class RouterFacadeTest { store: EventStore, reply: String, workflows: List, - ): RouterFacade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId) + ): TalkieFacade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter(reply), eventStore = store, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), workflowSummaryProvider = { workflows }, @@ -739,28 +739,28 @@ class RouterFacadeTest { chatMode: ChatMode = ChatMode.CHAT, embedder: Embedder = NoopEmbedder(dimension = 8), l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(), - ): RouterFacade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = - RouterState( + ): TalkieFacade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = + TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = StageId("s1"), ) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter("inference response"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = embedder, l3MemoryStore = l3MemoryStore, ).let { impl -> - object : RouterFacade { - override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = + object : TalkieFacade { + override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse = impl.onUserInput(sessionId = sessionId, input = input, mode = chatMode) - override suspend fun narrate(sessionId: SessionId, trigger: com.correx.core.router.model.NarrationTrigger) = + override suspend fun narrate(sessionId: SessionId, trigger: com.correx.core.talkie.model.NarrationTrigger) = impl.narrate(sessionId = sessionId, trigger = trigger) } } @@ -872,21 +872,21 @@ class RouterFacadeTest { ) val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter("router reply"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5), embedder = stubEmbedder, l3MemoryStore = l3Store, ) @@ -928,9 +928,9 @@ class RouterFacadeTest { ) val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) // Bootstrap session with a ChatTurnEvent whose turnId matches the L3 entry @@ -954,17 +954,17 @@ class RouterFacadeTest { ) ) - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter("router reply"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5), embedder = stubEmbedder, l3MemoryStore = l3Store, ) @@ -986,16 +986,16 @@ class RouterFacadeTest { throw RuntimeException("embedding service unavailable") } val eventStore = mockEventStore() - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter("response"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = throwingEmbedder, l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -1014,9 +1014,9 @@ class RouterFacadeTest { } @Test - fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in RouterState`(): Unit = runBlocking { - val reducer = DefaultRouterReducer() - val projector = RouterProjector(reducer) + fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in TalkieState`(): Unit = runBlocking { + val reducer = DefaultTalkieReducer() + val projector = TalkieProjector(reducer) val sessionId = SessionId("replay-session") val hits = listOf( L3RetrievedHit( @@ -1048,28 +1048,28 @@ class RouterFacadeTest { @Test fun `ContextTruncatedEvent emitted when context builder drops entries`(): Unit = runBlocking { - // Use the real DefaultRouterContextBuilder with a tight budget and large conversation + // Use the real DefaultTalkieContextBuilder with a tight budget and large conversation val longContent = "x".repeat(800) // ~200 tokens each val sessionId = SessionId("truncation-session") val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = DefaultRouterContextBuilder( - config = RouterConfig( + routerContextBuilder = DefaultTalkieContextBuilder( + config = TalkieConfig( conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80), ), ), inferenceRouter = mockInferenceRouter("response"), eventStore = eventStore, - config = RouterConfig( + config = TalkieConfig( conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80), ), @@ -1112,16 +1112,16 @@ class RouterFacadeTest { @Test fun `no ContextTruncatedEvent when budget is generous`(): Unit = runBlocking { val eventStore = mockEventStore() - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = DefaultRouterContextBuilder( - config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), + routerContextBuilder = DefaultTalkieContextBuilder( + config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)), ), inferenceRouter = mockInferenceRouter("response"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -1136,9 +1136,9 @@ class RouterFacadeTest { val longContent = "x".repeat(800) val sessionId = SessionId("turnid-session") val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) // Pre-seed with a large turn so the budget overflows on the next call @@ -1162,17 +1162,17 @@ class RouterFacadeTest { ) ) - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = DefaultRouterContextBuilder( - config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)), + routerContextBuilder = DefaultTalkieContextBuilder( + config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)), ), inferenceRouter = mockInferenceRouter("response"), eventStore = eventStore, - config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)), + config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -1192,28 +1192,28 @@ class RouterFacadeTest { // Each turn must emit L3MemoryRetrievedEvent (with empty hits), so that // lastRetrievedMemory is always current-turn-scoped and never stale. val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) - val capturedStates = mutableListOf() - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val capturedStates = mutableListOf() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = object : RouterContextBuilder { - private val real = DefaultRouterContextBuilder( - config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), + routerContextBuilder = object : TalkieContextBuilder { + private val real = DefaultTalkieContextBuilder( + config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)), ) - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack { capturedStates.add(state) return real.build(state, budget) } }, inferenceRouter = mockInferenceRouter("reply"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -1265,25 +1265,25 @@ class RouterFacadeTest { val capturedPacks = mutableListOf() val eventStore = mockEventStore() - val replayer = DefaultEventReplayer( + val replayer = DefaultEventReplayer( store = eventStore, - projection = RouterProjector(DefaultRouterReducer()), + projection = TalkieProjector(DefaultTalkieReducer()), ) - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = replayer.rebuild(sessionId) }, - routerContextBuilder = object : RouterContextBuilder { - private val real = DefaultRouterContextBuilder( - config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), + routerContextBuilder = object : TalkieContextBuilder { + private val real = DefaultTalkieContextBuilder( + config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)), ) - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = real.build(state, budget).also { capturedPacks.add(it) } }, inferenceRouter = mockInferenceRouter("router reply"), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5), embedder = stubEmbedder, l3MemoryStore = l3Store, ) @@ -1321,16 +1321,16 @@ class RouterFacadeTest { return mockProvider("narration text") } } - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter, eventStore = mockEventStore(), - config = RouterConfig( + config = TalkieConfig( tokenBudget = TokenBudget(limit = 5000), narrationModelId = "llama-cpp:phi-3-mini", ), @@ -1339,7 +1339,7 @@ class RouterFacadeTest { ) facade.narrate( sessionId = SessionId("test-session"), - trigger = com.correx.core.router.model.NarrationTrigger( + trigger = com.correx.core.talkie.model.NarrationTrigger( kind = "test", instruction = "describe what happened", ), @@ -1366,16 +1366,16 @@ class RouterFacadeTest { return mockProvider("response") } } - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState() }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter, eventStore = mockEventStore(), - config = RouterConfig( + config = TalkieConfig( tokenBudget = TokenBudget(limit = 5000), narrationModelId = "llama-cpp:phi-3-mini", ), @@ -1393,9 +1393,9 @@ class RouterFacadeTest { @Test fun `workflowSummaryProvider is called and forwarded to context builder on each turn`(): Unit = runBlocking { val captured = mutableListOf>() - val capturingBuilder = object : RouterContextBuilder { + val capturingBuilder = object : TalkieContextBuilder { override suspend fun build( - state: RouterState, + state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?, @@ -1413,9 +1413,9 @@ class RouterFacadeTest { @Test fun `sessionProfileProvider is called and forwarded to context builder`(): Unit = runBlocking { val capturedProfiles = mutableListOf() - val capturingBuilder = object : RouterContextBuilder { + val capturingBuilder = object : TalkieContextBuilder { override suspend fun build( - state: RouterState, + state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?, @@ -1433,9 +1433,9 @@ class RouterFacadeTest { } private fun facadeWith( - contextBuilder: RouterContextBuilder = object : RouterContextBuilder { + contextBuilder: TalkieContextBuilder = object : TalkieContextBuilder { override suspend fun build( - state: RouterState, + state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?, @@ -1443,14 +1443,14 @@ class RouterFacadeTest { }, workflowSummaryProvider: () -> List = { emptyList() }, sessionProfileProvider: suspend (SessionId) -> String? = { null }, - ): RouterFacade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId) + ): TalkieFacade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId) }, routerContextBuilder = contextBuilder, inferenceRouter = mockInferenceRouter("inference response"), eventStore = mockEventStore(), - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), workflowSummaryProvider = workflowSummaryProvider, diff --git a/testing/deterministic/src/test/kotlin/RouterNarrationTest.kt b/testing/deterministic/src/test/kotlin/TalkieNarrationTest.kt similarity index 83% rename from testing/deterministic/src/test/kotlin/RouterNarrationTest.kt rename to testing/deterministic/src/test/kotlin/TalkieNarrationTest.kt index 0f791eb0..598eea54 100644 --- a/testing/deterministic/src/test/kotlin/RouterNarrationTest.kt +++ b/testing/deterministic/src/test/kotlin/TalkieNarrationTest.kt @@ -4,7 +4,7 @@ import com.correx.core.context.model.TokenBudget import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.EventMetadata import com.correx.core.events.events.NewEvent -import com.correx.core.events.events.RouterNarrationEvent +import com.correx.core.events.events.TalkieNarrationEvent import com.correx.core.events.events.StoredEvent import com.correx.core.events.stores.EventStore import com.correx.core.events.types.ContextPackId @@ -25,19 +25,19 @@ import com.correx.core.inference.NoopEmbedder import com.correx.core.inference.ProviderHealth import com.correx.core.inference.ResponseFormat import com.correx.core.inference.TokenUsage -import com.correx.core.router.DefaultRouterContextBuilder -import com.correx.core.router.DefaultRouterFacade -import com.correx.core.router.RouterContextBuilder -import com.correx.core.router.RouterRepository -import com.correx.core.router.l3.InMemoryL3MemoryStore -import com.correx.core.router.l3.L3MemoryEntry -import com.correx.core.router.l3.L3MemoryStore -import com.correx.core.router.l3.L3Query -import com.correx.core.router.model.NarrationTrigger -import com.correx.core.router.model.RouterConfig -import com.correx.core.router.model.RouterState -import com.correx.core.router.model.WorkflowStatus -import com.correx.core.router.model.WorkflowSummary +import com.correx.core.talkie.DefaultTalkieContextBuilder +import com.correx.core.talkie.DefaultTalkieFacade +import com.correx.core.talkie.TalkieContextBuilder +import com.correx.core.talkie.TalkieRepository +import com.correx.core.talkie.l3.InMemoryL3MemoryStore +import com.correx.core.talkie.l3.L3MemoryEntry +import com.correx.core.talkie.l3.L3MemoryStore +import com.correx.core.talkie.l3.L3Query +import com.correx.core.talkie.model.NarrationTrigger +import com.correx.core.talkie.model.TalkieConfig +import com.correx.core.talkie.model.TalkieState +import com.correx.core.talkie.model.WorkflowStatus +import com.correx.core.talkie.model.WorkflowSummary import com.correx.testing.fixtures.inference.MockTokenizer import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.runBlocking @@ -47,14 +47,14 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -class RouterNarrationTest { +class TalkieNarrationTest { // -------------------------------------------------------------------------- // narrate() — event emission // -------------------------------------------------------------------------- @Test - fun `narrate emits RouterNarrationEvent with inference content and metrics`(): Unit = runBlocking { + fun `narrate emits TalkieNarrationEvent with inference content and metrics`(): Unit = runBlocking { val knownLatencyMs = 55L val knownUsage = TokenUsage(promptTokens = 8, completionTokens = 3) val eventStore = MapBackedEventStore() @@ -68,7 +68,7 @@ class RouterNarrationTest { facade.narrate(SessionId("sess-1"), trigger) - val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() + val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() assertEquals(1, narrationEvents.size) val event = narrationEvents[0] assertEquals("narration line", event.content) @@ -112,8 +112,8 @@ class RouterNarrationTest { facade.narrate(SessionId("sess-4"), trigger) - val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() - assertTrue(narrationEvents.isEmpty(), "blank inference must not emit RouterNarrationEvent") + val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() + assertTrue(narrationEvents.isEmpty(), "blank inference must not emit TalkieNarrationEvent") } @Test @@ -124,30 +124,30 @@ class RouterNarrationTest { facade.narrate(SessionId("sess-5"), trigger) - val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() - assertTrue(narrationEvents.isEmpty(), "empty inference must not emit RouterNarrationEvent") + val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() + assertTrue(narrationEvents.isEmpty(), "empty inference must not emit TalkieNarrationEvent") } @Test - fun `narrate RouterNarrationEvent carries sessionId and stageId from state`(): Unit = runBlocking { + fun `narrate TalkieNarrationEvent carries sessionId and stageId from state`(): Unit = runBlocking { val stageId = StageId("my-stage") val eventStore = MapBackedEventStore() - val facade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = - RouterState( + val facade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = + TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = stageId, ) }, - routerContextBuilder = object : RouterContextBuilder { - override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() - override suspend fun buildNarrationContext(state: RouterState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack() + routerContextBuilder = object : TalkieContextBuilder { + override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List, projectProfileText: String?): ContextPack = emptyContextPack() + override suspend fun buildNarrationContext(state: TalkieState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack() }, inferenceRouter = mockInferenceRouter("stage narration", latencyMs = 10L, tokensUsed = TokenUsage(1, 1)), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = InMemoryL3MemoryStore(), ) @@ -155,7 +155,7 @@ class RouterNarrationTest { facade.narrate(SessionId("session-x"), trigger) - val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() + val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance() assertEquals(1, narrationEvents.size) assertEquals(SessionId("session-x"), narrationEvents[0].sessionId) assertEquals("my-stage", narrationEvents[0].stageId) @@ -167,8 +167,8 @@ class RouterNarrationTest { @Test fun `buildNarrationContext includes system entry in L0`(): Unit = runBlocking { - val builder = DefaultRouterContextBuilder(RouterConfig()) - val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) + val builder = DefaultTalkieContextBuilder(TalkieConfig()) + val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) val trigger = NarrationTrigger(kind = "test", instruction = "Do something.") val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000)) @@ -179,8 +179,8 @@ class RouterNarrationTest { @Test fun `buildNarrationContext includes workflow status entry`(): Unit = runBlocking { - val builder = DefaultRouterContextBuilder(RouterConfig()) - val state = RouterState( + val builder = DefaultTalkieContextBuilder(TalkieConfig()) + val state = TalkieState( sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING, currentStageId = StageId("my-stage"), @@ -196,8 +196,8 @@ class RouterNarrationTest { @Test fun `buildNarrationContext includes trigger instruction as final entry`(): Unit = runBlocking { - val builder = DefaultRouterContextBuilder(RouterConfig()) - val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) + val builder = DefaultTalkieContextBuilder(TalkieConfig()) + val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) val instruction = "Narrate the completed action now." val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction) val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000)) @@ -214,8 +214,8 @@ class RouterNarrationTest { // Regression: the trigger instruction must render as a user turn. When it was filed // under L0 it folded into the system message, leaving zero user messages and the chat // template rejected the request ("No user query found in messages"). - val builder = DefaultRouterContextBuilder(RouterConfig()) - val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING) + val builder = DefaultTalkieContextBuilder(TalkieConfig()) + val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING) val instruction = "Tell the user the stage just completed." val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction) val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000)) @@ -231,8 +231,8 @@ class RouterNarrationTest { @Test fun `buildNarrationContext stays within budget`(): Unit = runBlocking { - val builder = DefaultRouterContextBuilder(RouterConfig()) - val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) + val builder = DefaultTalkieContextBuilder(TalkieConfig()) + val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) val trigger = NarrationTrigger(kind = "test", instruction = "Short.") val budget = TokenBudget(limit = 5000) val pack = builder.buildNarrationContext(state, trigger, budget) @@ -242,13 +242,13 @@ class RouterNarrationTest { @Test fun `buildNarrationContext does not include conversationHistory turns`(): Unit = runBlocking { - val builder = DefaultRouterContextBuilder(RouterConfig()) - val state = RouterState( + val builder = DefaultTalkieContextBuilder(TalkieConfig()) + val state = TalkieState( sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE, conversationHistory = listOf( - com.correx.core.router.model.RouterTurn( - role = com.correx.core.router.model.TurnRole.USER, + com.correx.core.talkie.model.TalkieTurn( + role = com.correx.core.talkie.model.TurnRole.USER, content = "secret conversation turn", timestamp = kotlinx.datetime.Clock.System.now(), ), @@ -274,19 +274,19 @@ class RouterNarrationTest { latencyMs: Long = 10L, tokensUsed: TokenUsage = TokenUsage(5, 5), l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(), - ): DefaultRouterFacade = DefaultRouterFacade( - routerRepository = object : RouterRepository { - override suspend fun getRouterState(sessionId: SessionId): RouterState = - RouterState( + ): DefaultTalkieFacade = DefaultTalkieFacade( + routerRepository = object : TalkieRepository { + override suspend fun getTalkieState(sessionId: SessionId): TalkieState = + TalkieState( sessionId = sessionId, workflowStatus = WorkflowStatus.RUNNING, currentStageId = StageId("s1"), ) }, - routerContextBuilder = DefaultRouterContextBuilder(RouterConfig()), + routerContextBuilder = DefaultTalkieContextBuilder(TalkieConfig()), inferenceRouter = mockInferenceRouter(responseText, latencyMs, tokensUsed), eventStore = eventStore, - config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), + config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)), embedder = NoopEmbedder(dimension = 8), l3MemoryStore = l3MemoryStore, ) @@ -372,7 +372,7 @@ class RouterNarrationTest { storedEntries.add(entry) } - override suspend fun query(query: L3Query): List = emptyList() + override suspend fun query(query: L3Query): List = emptyList() override suspend fun existsByTurnIdPrefix(prefix: String): Boolean = false diff --git a/testing/deterministic/src/test/kotlin/RouterProjectorTest.kt b/testing/deterministic/src/test/kotlin/TalkieProjectorTest.kt similarity index 94% rename from testing/deterministic/src/test/kotlin/RouterProjectorTest.kt rename to testing/deterministic/src/test/kotlin/TalkieProjectorTest.kt index 1a91020f..56f6c2fa 100644 --- a/testing/deterministic/src/test/kotlin/RouterProjectorTest.kt +++ b/testing/deterministic/src/test/kotlin/TalkieProjectorTest.kt @@ -9,27 +9,27 @@ 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.StageId -import com.correx.core.router.DefaultRouterReducer -import com.correx.core.router.RouterProjector -import com.correx.core.router.model.RouterState -import com.correx.core.router.model.StageOutcomeKind -import com.correx.core.router.model.WorkflowStatus +import com.correx.core.talkie.DefaultTalkieReducer +import com.correx.core.talkie.TalkieProjector +import com.correx.core.talkie.model.TalkieState +import com.correx.core.talkie.model.StageOutcomeKind +import com.correx.core.talkie.model.WorkflowStatus import com.correx.testing.fixtures.EventFixtures.stored import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -class RouterProjectorTest { +class TalkieProjectorTest { - private val reducer = DefaultRouterReducer() - private val projector = RouterProjector(reducer) + private val reducer = DefaultTalkieReducer() + private val projector = TalkieProjector(reducer) private val sessionId = SessionId("s1") private val stageId = StageId("stage-1") private val workflowId = "workflow-test" @Test - fun `initial() returns RouterState with IDLE status and empty collections`() { + fun `initial() returns TalkieState with IDLE status and empty collections`() { val state = projector.initial() assertEquals(WorkflowStatus.IDLE, state.workflowStatus) assertNull(state.sessionId) @@ -190,7 +190,7 @@ class RouterProjectorTest { @Test fun `full lifecycle through projector mirrors reducer pipeline`() { - var state: RouterState = projector.initial() + var state: TalkieState = projector.initial() state = projector.apply( state, diff --git a/testing/deterministic/src/test/kotlin/RouterReducerTest.kt b/testing/deterministic/src/test/kotlin/TalkieReducerTest.kt similarity index 97% rename from testing/deterministic/src/test/kotlin/RouterReducerTest.kt rename to testing/deterministic/src/test/kotlin/TalkieReducerTest.kt index 2e2287c2..1dfbbebf 100644 --- a/testing/deterministic/src/test/kotlin/RouterReducerTest.kt +++ b/testing/deterministic/src/test/kotlin/TalkieReducerTest.kt @@ -9,9 +9,9 @@ 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.StageId -import com.correx.core.router.DefaultRouterReducer -import com.correx.core.router.model.StageOutcomeKind -import com.correx.core.router.model.WorkflowStatus +import com.correx.core.talkie.DefaultTalkieReducer +import com.correx.core.talkie.model.StageOutcomeKind +import com.correx.core.talkie.model.WorkflowStatus import com.correx.testing.fixtures.EventFixtures.stored import kotlinx.datetime.Instant import org.junit.jupiter.api.Assertions.assertEquals @@ -20,9 +20,9 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -class RouterReducerTest { +class TalkieReducerTest { - private val reducer = DefaultRouterReducer() + private val reducer = DefaultTalkieReducer() private val sessionId = SessionId("s1") private val stageId = StageId("stage-1") private val workflowId = "workflow-test" diff --git a/testing/integration/build.gradle b/testing/integration/build.gradle index 0de0ca40..6b8b8e9e 100644 --- a/testing/integration/build.gradle +++ b/testing/integration/build.gradle @@ -29,6 +29,6 @@ dependencies { testImplementation(project(":infrastructure:tools")) testImplementation(project(":infrastructure:tools:filesystem")) testImplementation(project(":apps:server")) - testImplementation(project(":core:router")) + testImplementation(project(":core:talkie")) testImplementation(project(":core:config")) } \ No newline at end of file diff --git a/testing/integration/logs/archive/correx-2026-06-28-1.log.gz b/testing/integration/logs/archive/correx-2026-06-28-1.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..f8efbbb99244a26dd562e00a7b353df6510f6514 GIT binary patch literal 67120 zcmZU4V{~Ru@Mg?6m{=3Ov2EMtBoo`l#CG1;n%K4{wr$&*c<1-uJ-Z+FeyZE2`gV79 z-FoUgT}>1P2lhYD*G1QQ!ahd|gZH*#{)vyFyIj7!KNk<_FLwm!H z2Y+IvqGpz4GHP2*Ik^G80lr+F*TuAN>nP|SQQi9dnS3LP4Q&%WS>8R#S9&B#Q~Fpv zpWbiBHFxzi(>X4yUq`DF-`|dj8huw0y@y&`js6c}|2-uxI9F{~)qW%NRg` z67|A3ugh3{b#!(-YUNdO3Ht+bZs4;p=-!{Xgy})+qr|&(_z-dE7J>$f~nD4@qy4V*}`e^b;vEm@vu~H05 zY)WQK9@&nm@sNEgkG=hvc~~=DO6Ot!yKmVWERjO!-HbwN=1bS&0>l0N;FPpe&CyWj zys2UF#|r=KfqAou993>UXub`F@>E zp670Ldw=XLp8L6-!iLRVn|;KV>72WFz+;uvk#VvZMjL^YA;Ha*AW7I@6Rbw->ExBR zXxu6Vm%Cj(A4R48zdC$A?k#;yzbvLjs=L3q)QKq1mp`8yM&AwJmRr_H&sPsRBtP~W zMzfn19UQi#RnkPD$@J%}#GD$h*gKJR0ri1Vf z@KnYy>dsTT!;{Ds3Vlil!tW7s0P&Jz%n&+R)RNa8w^H0$#RT++IMt-Cv(&?IW_Wyu zI6sF;RAOfv&sYyp13VX$eSQ@YbwUijmSn!{f1Dv`APf=5Qm1`T?bJI33(vKxnC!(YM`myqzwZ_YN8y*Yn$<(&cf~Rwm{YgB1k>-Mah3&5>G!Wb2vzleq=xnD$7L85S0UYJZD$s{=&drn!?%*MVbfSw? z{mR7gm}Xo*%LX0RUW%~Pq^RIz{67Sdm{K96a*9$u5poLIhSP6;YEMx$;fzjHFOXIz z|B|AY@H*Km!pcVgWat!vvt~Q0MzJk&)4%{!rERA&nc+N0S^Vn)xk?5;^$)eC(*J3~ zTyi!^B~FzM!bv$6WAPggk~KNtIW;aAaee7JSTP62!~C1s^1qoIv1NP7(L+eQGZHu9 z$no|p1fECZOhS#o@28wuLD$Qv=1E&&EW#fK{xq^;j!oAq6W67Ej@OC4pArApt+@@5 zs{@6A&)8%TO(7u*gHsH*qialzb0k!ZW6qtJcJqLy5|z_QMBOCR=xoKzr;=4B{%<3t z>2*U17@Ke=1XHtgpyZPDrL34+Lm>;Xj87Mgt-h5e4CjF6cV;@tfYn;;ydnxA_bVu9 znmo_GAd%Lk4&hM9vS&LGJ7LE)bRW1}R_k-%iX<)MjRDcfYlwbHl*sa=8R*;>%8TVz zBG})SI1`1+9-Lcg--2kwb>cg*W0y)7q{;d3NyxQCR=oMFdQ`mxd;+0}wcR4>ws^Ht z;5-3SIa@F3IG7?gbb@tclXTOc!+Bgcupd_>Y&tUNRuVl)MZ zzkM9G_^Rn#st~ae`4e61R^_gp-=v?&js0{c%wNxrCZkdXH@n}j7RRskw*)dbCY||z zloXx&1)T30zKO5fzq&k5K7M%`Z?kNBZ!Wgjf8E`kV>7(}48>Hc#r)an^*mYOe*H7n zKHLnslU2z5Qx?KZEG_XXBcz$pH^cI#kLL;mC-`wx5|-ZCvpv7N_R*5Ba%I!~@pM)A zSE~+NXqKK01rx;vYX_9j{xA&HoxoPcO1J|O-) zmQX~eNlhHin$INxI0}nC#YjA!r#?Z1CD+*m3my8HZ#{zo=DPL4r8(YRkSOgpMXVW4 z&W3ZskL<2~MqC@kasM z3#sjGcy%HqPY4~V_8_|yxep?i$LbhA?zAK0_aNoQR!OL+G;w|4`Gw zT{!f7GA1SuzW#ZH46F5fL|yF5`t{f3t_EK1pu%4bBqfVQe`qx%u{d=|Hm^nE1(wTY z(F0ZXa9R!HMX(S}q|hKO)kPt5-6%0(Vl>OnNUuj)vlnyi&p4nv=a z<#>P3F+u5?4htFOeU2v<)p!HRRc%%dbor8t#A*YC6)+Zg<-)p*O-7@S>Os5S^W9ed zq-sc7LT$x|2@7iMN@94E#L!xYLr3pzFf7LKLS-gg{UgK!XB99iqjdT35TPP(@RkXX z($EkDDrrSZh|*dPi+O8j<{Ip9Z7G$RKlE*u+kSLqP5Z8Ty0^SyX4-hjC}rS)Sh6tQ zp6S0PJ&;=sIDnSvg>4Ko>&VC*$_Ypt`rTT*=8vtNUKuhx{u94JmyYtks&FsyuL-N4 zj((w>&a0A8$r>iSf1ElxdAPdVzuE9SfYr*z<e1&!EmVl~VGJo+vAa(;l4qe?>%JN&BVZ!jALeOLi#6ukr22=&m58X@x~J z=yl8tX0p}xnNHq3U#@N1Yj~+5IbXX0ux!omE;Ze*X9=xU&t?f}7LL1zM{>&PxU*ZU z++cwytxx$Pzvpen$rVcIXw|g**w~wm^8*N&jaTcI!0^YwmQ(0NtHg%TtI@T1oZm5g zk85e`Ij>XiXepua?r2EM=fH9+t)Q}6f7Eu74w7X%r8#z*&@i0H=#IlT9p%WI)X_+# zsB#fGu@e+#zOk2}>OEUmFK1^Q4}Z^?Xd&u{426d**uNJB-o?gF^NJ5E8SaZqTCoMn zA?CT{V4MH=W{@fbmw+LfT)$8qF=RjaOTf(KV*E{D_SVyH))6X;tH8KNs%_u|2ixUe zTbm*@X|>kpe}W4JcvVdbsC#kfb1~cJHhFSo`FrirO{qXx`e((b%Ok=2>Cvi5``b9F zZKL+VJSBaN3M?JWLap<=Uw7;mr{t}mYWJ<6*6`Ks*AR)a&<{r7=eA44SKptZs4u2z zj4(?GzyN|lJPmPLGPYBBQ4$9_jaL42xi72H`}O3T!0lIY%U?klg{<2i{X$)X_c#Dg z?q5G=oUeJ|fxEab&Y!n})h;DnVd%SKU18WAL!$ai*7`8+ZiEWCdHYlSQBLc9($Wyf zQNgu!m!QcDG63PVUw7`8*LKk}DxLnrmZ)@*G9GHYkA`gB7;@#Mh#1!RWCM-qnOHLf z$5S(u=Dv>x-!S|<#*8kGf4T~>C=575@Wh3zl94W^NZgDE@<+HqMYECaqQQxiMFI9D z9ys&Wud3H!xn)*@;e(%!yPBh2<-*GwIjAH^{%$v(9skmTwoQu5X~c+URIjlE>@rvp zje9+Ux3}%*ph@ya%G_0bpXPKH{W!@VEutC|_(o#ku94$EZ0^n;HSHw+0O?Iz3{9Ptz7~+yz=--tw8swPB*_603 zz*G|;)As=mV;5-YhL#4Vnp<_Gv15fL#EG^!Wm;S9FX2qq& ze$|PYL17sQHk$!4p$=1zVQj!Ans!K~EG>`e8`a-HMe`66kkKuV3&-zj>w)G3Cnwq1 z`n>%VlRAFB8q7P=VXg|3C0S(1b@CTE;J1X!g`|*u4vf&{-lK30Mee3I#|u?z;_zFM zu6(Ll`Tgi?O?Tsf9ruUBbp}FHiUV)Wo7zd)Jzt3kXDpo+t}QzORA5Y18w;wB)T4ux z4ewTch+g!!*%e<4>9}%OF;6rWJ6wsl_UN$Car`*KK7u>I5jQB9)ZcK(9|I>qj(U=1 zjEStxy%pb)t5Fc(F;}o9Au1^>JNa~wbL$lGK*jLDcN#yeWnnX_BQBqtKSNUY9gQ7Q zQpETz-*myH$#f_xW3!!=WIxZbfTCc+K^iJg1}MyD=zoMQjU@w)Fb9XETIw4;tGZu`0D8vqmTThI2j-@{d^klxTgy9B9~FGGOTH&sb-|LOdhX$ z1khF+lmIG(%N0zlxoX+t>BXG$xu?P9?jNP7VK*$`P}y*&07gvo-yILcjg@@aU9fjJ>??5Qw%2S zp{f!$$~UIh%60(DSP=A(nc8vhtpA6swVOu^$|36 zd9--?A=pR*+Uw5{89WbSPtI&omwA-Q+4HK5;A4LAAG7P`C7rZ1pB;Xb5tpG`Rf0WY2kt9 zC5mZ1q43bv3oy;nwtJCkxof=|W%5$a-PzZfoWm&}y3Q#B2(MR>NJ};NoUR0<-?Z@< z?bc2o!odw^2jt;3BG`h17Jx82P%6ZA5@52_KsCG)9^0RELwp>+Icqo_=ZEe|X(BM6 zc8ke&`bXv8M5Q$CA|(%s|BLh=-U{JDBtgo01-Z+!sPW_#^cQ^vf41cNsf+sRA5~X^ zNA#eSqRlR5|Nm_ zn=kaMgMn#l6CN{cj?Y_59{r6yARJ`{n7iHBC=Y8N2r! zZn;inA!{{37ce_0KacrTE#sR7mDD#hu#)HifC{)%CCjAal*!N($LYoREQvJbkY0hy zNUhyPVFRq%Uop$D+o7nre1X#>mp$R&h(s4;m^B9FSMI;_uC_^T-+B46bo!6y9F-UN zQ)6i*8QWY&N{tq%=+UdyNV!HV>IhXU{l(~@;U~!1RLs%#8YYWGt5zf#)MBN`5G5wsl}z%NrZ&xSCIF=ghCXP z+;HqWslHe4vovSmPy4?MKu(V%qDn=^WY$RvjRu000fi*m1I$1)^OTf z&a!302qrwO&I*FK<3?tl21W-H1Fena1ZWGtCPIaaT&wTl*J$f?a}g_2Wu}&Xe0{|2 z$%!gxTsAh9y#l8YbcpOiIn3?%Ky${ORD1WtUFItHcX+x$xZ!K6~SiN{?B;F zDJzubVAvo&2Mzj=Xu5Ig5I)R8#4rL>K6!#UgYw1^75|V?@M2xa^jfF`mR&%(9BUPd z=O2=_sQ|I3qg%9yN{3&m<5}KX2RJJZ^&{OgvyY#};1-3nCXz5Z5fU1`73q=^SfzoI z8sUe+K(cXH86e7ll{*J75>>xz^r`}$=ifpXzVvKpAlise{A7YFsaW3}A8snN0|^H* zY~)3x74pDYo1zIWO)CoGE?@snLBy33m9jzu00<@Rk4_pep@Qr*fR=4sr1cZo;)2gY z!G4N&9IArFTqp`5D-u^TM1z_?WfK z^996$S@dK7uxGqkau5d3IB^r!B8g;gtnWehjg9pA{Mn=5uY{A)O*Z1D;;pjBSJ<8h zo=4f5{q{mUJQu_mf$y^;hB6{9d(1%wMYm6yV)-(S5NSs^1_jKhV?g3bl>!%vLZ%`} zNJIzv@e|y*@rT0$mQ?Cr?KSC10lPch7frB2x;u0-7b_}8Y^GwKg`40dL~3xVgH~gt z{LGol_x1-eJ}!}qo(QcXl?Bv0Wbw`WHP82>dL+u=SCIS&b`73yCeKfEg%$A)MNz*) z@LJR-stKQ~Ji^ybf3d7}Xsm=>g1C{Meb8iUb@;X__&s&LPUb;y$>!Ua+J2@Z!9%Z* zGVhi(;x#@Itpbh{W;O(B+gWgyT}{qIuU13b8rM-RqdfMNgxJscLF=4!#a=H)%zhrV zRE!cGZhjw4?}gao6-3<%zTVAd1{#wcVfK-FtOUxD%8sEx=X61}^CQQkgv$<82||ZU z8QE#JI*9<(sidg@xo~hUGTa?8$2^HJxM8L0JHXVjvLn{JPNoKlB` z;hzqU0T;|H1F;fHW4%_zaq&fES%TE#2gH(N?u(0u2!Z))6_H1%7!Bntm zvPiXbX2`80!Z}9i@DU(61M1ja)_e{h?iM-QTE>b~K5v$PejEJHZy6l^2E!7ZsRvNM zDB*}tQG~>oAZGun(Iq8jcr)H}Ykz<7((Y{Hu_N$sH{5=`y1i0h5Lm@#3Ke6u0$bpQ zPGg2{r8+Csrb+AdBt^5zw&<~Udz>7{uIc_bS{>l2o_W8lGG)B>dwtp*{}F~BI_vZ0 z_*7+oSP%x!pgSWy0pA5S{mcUm!3pd^${n^kS#VQgSxg27BsPE5 zK`>6X5Q22R#YTJqsmvH@q)OO;loAMVvn2f8l;!j*p`kzyNg>c*Cd;nv4s$k z57%!HKJ1l}df>9>txa$Hdi^2-6V4h8#yeP;(IbIs>P!xqd*c59B@(;UXHx_!`mj;4 zYLgt1K&1*5JQcBMMz3loyBzah{7g@RRipK|u!6oV*AR0PcWnyfbogv#QTx{@)ntU@ z%{FyoP{k5Y4Td=lwv?swQwB~7O|(g9K}au0=Tfx{bhqB}I2&F;9S&m&M>hUl!U&5>A%K$s2b+c>j{s?$&YjtE-C#RchP zreDPk3ttdAwb{F1i(LK*$1s+pLJ0W~X@@XU@v^=8rw9t(a3-&B^-&cB&zJ6mXrkEZI zV2~8yyLkCP#rysY`d~P+(mbCE=_>6iTj&8exa4r)F}EZP(Sl8n&(~2&X~++2u`%t) zoooHUF?dSrm_BgtL58_FI|scx1}0!h}k{bUsD1!(-d&s>99YSQ zQZ|zzi4!;!{YIg=)Qm)5#M{$JPP|h96g|sH>`)kO{*u;AB)!@phGUV%Teu2LVcI0NIs6bKGN_8oNZT^~* zh7B&w3L1jN5>jv+g;B6GnWqGNFJD8Uwk3e&Hwy?CPMe{Tg_Jgg*9B{m2VAs%=4pv{ zgK|Chwz+Jl<8;&b+u40YoxZgyN-B~FLLTP%=IbeowR{EBRf1iE&4^9$rs&T))0j`D z&`9hMJo#no*`yAoq9}bKxJnbHixc&Sn(jQlwCC9LQ#D zl-vK)EgTgl6Jv&rHkL>jPO2P9iEWx%p-~!1a6HUW>s(zhx-Mq<$uJu6rkiMJYB)u0 z#LL?zq9%!O`5Q7+RcM3sn3N;+;+~UmfXG-mJN96>f~WC66p_FJ_bdp{=47eqWUi+z zzn4(X<&;RtC*_F*op1pJ@a`oBOmlVtsM$Z7#iMA$RQ2A|>UGJf0;JpIXr{<`o{l!0 z!1exOO?DuAA|pYXck%s$5&IF#>CYX*Dij`(CH%bbgJ7*MiH(mktD$M1~FLL?LNY5=AQIT9&t&`yD) zWaLPCeij%4E16@d4=LZJ%6C((~31m=tz+g%$S%P)~JrAiN`9ffDV5BLy z{z#;DG~CEhdFW5kt2(R*LK9)ZC?F8x!=*~~?HiTrG6vh8g*ZZ&m1FMD(U@jmU zu6ym~^)2c#25+8+knpd!uVdyEyIntqMau6I4v?$WQ?5&FpxPyuZJ};G57?LpvfIKi zW@$`Y?Tx8+ET0IaDFDp5h}mZeq@X8QgM?@<{c4kNca$iEifN3cSO%M{e_j8pED`p6 zz3a4&o6UdAydoF+eyWI9FIi6}kws9dnDkdyQOR~-_AlsPKJXuDRwuIHHQC@|OcCXE z@Zn9`LPJM+Cy=X^qSC3gK%J7EGGsvaBA2QzNVpEMd8AnFll*`y8da&5d;WnlU2oh~ zLZRqngW;b={RTXyMwPDiAEBmTng<%cZY;lT@evtzNrr!FYTdjAM+^sE*YAB*{{5=b zdb0w1K`5Sj8X3v{OqH0lWONN-{u({%xzoJk75XLk5#we7n zTWul9z4{-$gH5-mzJ3wxCb>5}E(;aSkM_2U%utuN!Tf#>eIli&DwP_w4_C{T{j$8> zr(0?$O`c|7;b>Y7=pU7dW$2a!$QJU2@y=GI#ekjY^>>tF2r1=_^+A>t3h7vtNLiaa zyma%v?{9yE(|MNy^T!XHX2OJH{c$LR2*6nNYU)GC?nYW~XrHI7|D7Kx#L@jw=k3z? zQ-b_<{=(^0B*0U2p#Y&+#77a(gm%S1ouPPz03HRqFC=Rmz_}VQQfB6B0{tv_XTD}?4LjPBm)+Y4GvYRiGZ`5yMl!GT>f%hm!_eaN{YIqEJ02m72^N}R6hupJr8v>EI&Wf>M zV=z!36~RSaqol!Mdnd?MdRuR-3;j!xprdIsDWSz|sfn)@z-bE~^86`FX2gp9`}vzL zsSOST*z2f?kx!KNGNKTgI@eKi`rEg!2EB5y>E;quIkvHT@1Jy%RyVI61x9sErqvSL zAUPLRT1WnJ=zhZvzp&U5rN6)3Mt{qoi$>)YC%H3B{ zjf714KjFA-N8a@bSQ^}`UC~0GWAoEMl9;~qI|sLjxYPg$ER@RueAIK+ro;vXdLiaC z+;jPvXS9%xA>4;jmwQ)M&3uYXTZ3Fug}Pjh|s9rz*#thXq*u=)7c?53bte%Qb%vGog=W$6iKjVo`o834So56 z8-d1}Mj4y9s|H!IrYOr1mYr@cX<>BF6}L2 z^u4=WnmuE;yhvrSG@5b^$1N$Sx7Tb%>SM0Qn7fV}i4F!t0 zr-%B6A4k{rRGg37NBX!o3FdCRkwfXotx@elwCt2hCX~?lm--mEWCoPvyyjV2`^^#`J%)C9WObP=N8FU9$*ux#$#8vJM~P2* zjnCfYMNSUP$`Eq-Ih-CIi=M*SIDt6J7yQbx$7co7Ru%2{{Dfpc^)&?TcXCq+Dz|~h z#EunZHyxiYvT8ZxYISNQxJ^7{O?emE;e=udM9}QUQ7s><5HQ0(sTUxEAgwSX{muum z$IV>bed93JS}r_`e~JgrqAGtWe$6MWckC8eaIwy&b z?KRaz1D;V|)g@vf?T`215L=bMcU+S(63V~b(Emsk)54>go|^js-<~SwMg!1?gKrLy zBsmxrZaRz`Nyw_NG-ifqUGyKZ+|!VvDr%Yln@k5wI{<_epb4Q>vG%h~s#_}F5?YxL zlzQF!L?~y2{6|7DXi}yw{aqn}=^Sfp+yH`vf7si|i2%4$L?to9k_>w?dN8}D3Tc$8vNZR%u>xN- z?eh88(L|-H3JOR8-5B-6Af@dt4Kx;_{q87F-m~AAM2*!c!7>ZJMkT%#(F`dWedpwk zA1W=f#SSVygTMnDsN5f`3efO|lKFE(nd66e!huo@TXzF>XB5lN=HMc zTI66R2L;I;;Szh20z(I{N<%-Maj=FfDR4*97co^cFv9kX^QilkD1%8Mrj5RQOlwcf z-}n)64bP5tvDdxtG#HN?^xa~&Ow%>QYm7toygk`g@62agss12bH2h>cmykaFIB`*uUpWqOoJ?cz zwO$9~nYmhjGf6v+;O}K0N^Z@AJ?%6~S*W~fd@_6%h1~mQ`X~T4Vvfh`_QvD54HU!J z?k-3Stad$z`|D#o9^3nVJo74O6-q%FEIQV8_LW~v$T5tRz8R6ZkCq@ROiy7Fn5Rb{ z1Hl(I5-I|dK2oH7*f2EBK8)0KvU=D`CN#iryviuWz&soi)lxoL97c720yTg+(lz~{ zM3?(hNVhHPha6(vnmc6ZuMuzeR67WtV_z_ow&6cMw$D1PHW~!H+s#D1qEmuc!yHp) z0U!QNX#LNSfmJ)}C@WlL%|xi$Sg@Kj@MyisFawI{nlep==|BK+D0%PTs*f%{GTj^7 zQ&AFeQg(13{mxW4?c>pxYPA}Su)eM(Ae^gBWtzB6sG2SB*ZUhd*Ce_TkhU3#Iup|z zV+3ICKN4J&FqZVIjoKllUXfbl4IHwgDxYYkq{#aXe7eWi`CikU(w~=mwKnu&RU6+( zzUF(=`SY1~f=>n?y?$aC`!75mnh#BL^0uQ12`oq^H9P}xHc&8v5C$--55vjJi>2?Y zHsR}MWuZApmy{NM)l@5e5-JUrqpVES`2wb zm|~D}tJ#6FF(Scqk0-)wJIQNg+zCcdW4Zk>HUKxD-y-bvMPH`9dYxPoe>}`={mm6V zmrp>#XUTQLb_=vsQh+8W*3^*6Vu1=~hM0*)OZzs})JsX?5Y+7aTiVC-D~TeRcm`h< z51d#bWr0zQAs&cy{QYZAK1X>}1rSiL0ImhXfQx7C{e~85p8gFp^*+mky4GpF0JSe9 z(=hvp{Rz#W+D?_xSD)+eg6w^@W{y)1@hYEkQhm52P>{*IPlu&JE)`RW37@4h*BZ_EutudXfRYt%oA+VR4M4a-zTk zb?XQg6Pna6qsaUD4SWX^Z2hl)(!!^`0x?qy?d3a1e4(G6>e9a28B;K7JQrj{px3lL zXhoiNMt_E>)R~k>)HEfu{w%9ZCTK0~)Z&S%2D$AF(pC>x{~DpDh# z15unFZ_lFsbn1Jv=YHKDtEc{nfIC@nyD{z`kj*V#m+!pj{#3ucKQ5>8+UV}|y`8^L z)$eNC)cJRvW2(DEYke!1AknUW7`97CL4cR-!#u^&JF7e{QSp9wRPrV2Cs9%NO#R9L zhu)P~&iirC*RTmccKzqTnslR<#OzeOS~0cObrSxh=nvNs~ZmupHNPK$Vf9<8$czND+WKxppK`+qjtVp zP=*3vW|6r9mMAG)FVKloCBMVE$VaJ~p@)2v=SEFe0mGLAmu!RKBZF6?QB)=Q!5U{o zZRa{+<0s-m5-Wb-bT?#}z0!;Si%mBwNY(DAoq|#+h4={{-Kl$^y+40N^onKl>M zB3auRI`Nli^&Wv(>Y18sG*}1$L>4X_Pyu&@+SJW)nk`BC%e76QC|o}k5!WJes^*m~ zJJH}#dx_@LQ)SVr(BagRYy5hq2rSi=&t>!+f7;<)t`LMF8R!|`s-%e`wHGRCiXKs* zWj>f+2Zo!5alGn?U*LI5B$mVyNLw_WGw^u$QYWnAC#(J_TKgy3)a~w>`~`k;37}EZXK7g!R?nJmD`jtN zeoyE1mhY7yY7^952hIbH$g6#GQ+63Liy!h{nL<&9uyOE>WRh(qGnITyP#*&3_-cp^ zl(Nu0P}aVug7rkAc7A;9Pnc~a+$LxsHSFMwBI13iKwZ^%+90Q?yZ$^6K<`NzBLs4`)AuDA>!yQF`Wu*0k*!r|+5MK`3496KxJ5Zby4eA9uv*q3KF_Zo7UD!V1 zz2XwK;-zbF{;WK_wo29(nn9!)u5nmgw5-aWb0NB~tIPK*HFwMV?Q^pNd&$J!?(6mH z@-b$aPIq^DbSc1^mY%3^PG5ni{jcTD<=grC^57=m(kjTB-)^hZ*YD%(k&{usN00`| zEzz=$zoO@S+uj;dTbl3q3zl6a@^W8m&^DyY<|7FM2?bu0URscOE5%9*=Kb*WNC1T)xlb>0F#g$y(~EYm9u zRSYGbB>4$?uwhZC>BW*=}hj*mxW3 zw(h!LU?@sFEKt#~Fdn2w9?tzMP}lSJ9n65V=f?L-@MB(3p5?3M>tz}p#3xJf1x4xi zd4K(R>hSor`NiqUpSy9cZ@#pP-j;Fl=FP%+&fly~7I8k>*O1igq&4>}+p%fV=G&q#9AN2V@{pWCi9Es1sW|69JC2Y~^Y3kzFM~4Pl-D+gAY!TA z6OGQ`Eup)?(@A8N_dl%&#WMkTr?)Sm=8H_IsQZQH^>@GL?$JaT(H1$;@9F1pUu?7+$kXN_1@OcyxMM^Rx<4k$nM_^=B{%i!0gUO^ghJ|XUD#4Hg+27L< zI4ZEyc|t_>`K$nGT%HT4LTy3_?0Io(7b0O0=)b|ySs1T^`u&|n1L|~F9AwCMEgNbe zGD-Onb}z!D;Oh*iDFO>QFiS}LF#JgHbx3%zb=^;c0094?%kwVWu4yc(s%?dKqNeMs zuG@fV1L_+)??RZf;1yGz0=Ltl6(-_VGk;NW=Xf|oOg$^_Lims@?RQ`VR9Ym42`LGf zbDE4*(#J{T@(`>74*JrJG9D3^K~iOZ!>Ol^@G)iMxgoTG{)*&u!4WsVo%=#plP+=1 zuVZD~cT2svnOz*k-nC+h$yC=Xb1jzPOFu1FL1RAAR;O9;+`CsSiQa-sBBVEK-JLP# zHzOR6V8AafIX2T3-9n0jNC@k*Jq|ah05J+!(uQ3v8Y@3aMv8vqbR2)!AIG16Bz`Al{olHFNTBdX=9y@ z7Huh7Mp`*l+DA)??5ZDQI~$bJC+}R503KQyDn)=UWH}mu37vyAbi<#3den7_4ZC(# zGXN=qFvP)rW`t{xfK&ty$Yn^~&-OxkZzIKby#Rtse~R4#oy?weU5%FBub;X_x4S=Y zgB&gga8Tj5kl<~hIO)i+pl9|-`XC$>SpAhl(Xx(l(a&m0grjAk$5g70 zX=9r<9Qoc?J*+Fn^DR&wYJMCQKni(RJK zBtuH>e7bxX)W|sqOllB~x<3#k6;QOKr6(7a9z{u5FhA z{W8bk{DznBQY!jXRPlwzR1XsI68_rxi`XabC={VNh`rGJx`-gwvIM176qFN~GSy3xZW%d}8$t;OB>6KZpDA_S zHwc*N(D1yzb*=U+SAC0N;M*jc{Y&}r3>^3yesd*FOk!xr0#?U@g~Sbs_QT;Iw1jD# zMHtO(B@s0^aSzo_$k4ISpSW@b7!R2vl=g#)eBfat5w*;fzDU^%4Y(t;gFIcTsrdUo zajlAM_t~|}Cf>qBP4P*Zt?SDIs^|1l()2n}TySVAf)%aJmr=Z*)BQ>xH5^q@`fNqX zQR2ywyb-ec+vE3`DMZonv^9OzpjH{KJ`O@2O(Ikb`8F>*lbn)p$g-%0Uv*+aEP-Ig z+C!~ac&c8wZC>_NIOLi{kfc+(U49^4gK7m44jFZfSd>iMVJd4oQ?Zod-^q@2iIQYw z_5X{qdybAI@b&~AJDJ$FZQJhH=ERz0l8J5GHYT=hI}yZg@m*QZbQ zse7uctLj$W@28bZqocCrt2eOw5}KGA`8KeB!fm&Oi-<~UF1%6>3mv#FH$hxh!qq~cA%V&rQ%;j`)MR-`|`OG*umtp$gN@W^hg!paEk4Z3o#iGK^d$$P|b!8 z-L%5+rOBv?(cwzW#;M0Md@$A*Pn2LRbxnVE;1AF!!jixSq$~sC$=~6-)NVz%E^D8; zeX|Y|<&_%N^ggQY7`vkVN#+(ePG%4bHw({B_e8Fg9+D1=zU{ww3~Z*#>UD@i4c7Zg zNiOiyz0$&^qZ>t&|0y^2BhOL|R!w8pJ{zS@i|mv>Sujb3k%9RmV-GT!s3scN>ie@y)BuS^p4K;$)_0C+8`c$TPjYz{pY}Sac`dy_qR0ti;{g0XU-|}n-mxu-bPga zo2K6yD&CsaNic2M&DpANv5nWhbVRAqgI5qf8)`TI2g0C$+KT5M9wGemb*pNJrMgP; z+B8&B87*GUC=G?v{Qw!Jga#Q_{&!JYq=HH&wDU(>CYZthjW6&}dENSasBRwh;(WPM z`Fj5DY{|gkQ);PG?cUTBtAqJ|^{rHWJ33#CQ4Nmy9*99`cAe}@JAVj_XaKMIkNK!N zy&4{voFB_-mpR>Z=nt^p{iLonn24^|1%EPPfF03>DDeagMW;H$BpgvDqt?lz_;JJ& zFRX%$Vawq7%cg$I;$BG-`@^QhgM5s^k5h1zM5fi0_MdzK-68g@SZbDYlyONuAO^)1 zpZE}4sZtOS6URn@3uS!{CZT_4PKJ#*8I1dr$!%}R-S{=`x)BzDSt~(iLAPxLxiu^} ziNeVDWFjstfx-1?lH`3d9}b&B>qRw8%PLDm+2CvQ#vJ@Rqk}n~!nstOYR^?BOskh{ z14&u|76pI|H-y2Ajq4X~BrFUnk^(c?iVC~jNPAlq?;&`qTqdGhELMtab~EHYrb%7SRK-{cpg9jA!$&e{Gi zhTweG1xFFc5Wp|janEx>!%oxmN>UO}Fhk3b0;)0Q=;J@X?X9`$NwWT~+(%s*7r562 z2L5qh8SFHo{V+HFmP_Q--KfGRQiC@M69d%1Cq1FeoPH9QK&3~Si_`kRmP;sY$0q6! zPxF>yf-aEO10r+k_m<3^u$?;>O_;y*oNZV-<3kh~pjI6H7ES2?5%`A!SVt`eKu=EI z$Llzjiz{M)qQc@av4(*taX?BQs-9#S=QLAGWGmRPB>X$`AE$$0S(~F88Mk}c2Z&TQ zCM&^A5Gzy%3uBRSlEDM*#|DjC%sPU=QTbjf@U~dMbUh-&Fkb`={e!7)mP_<#-AFTJ zh2S$ZVNVNTW$FC&D_@$B{+|TFs~kfJz&Z(7?d7{Fd_HVQREZxd06otY$t=SKB!0PE z6nmgbg67OyHPUJFLY84qr!97o^v~??J81?m3~WvyC9tkg9z5{D2i!sft^e9Wi8`$aJO(x_X}*j+P1xJgVx^dYw%o!t zEGdctC7AxSo)@q@h)7d>qCXamuQCWoO8Y%8pu_31e+o&H=$&s7=l8z^F8<^i#f{y1 z3JCL?4F2CBf}!2e8kd!Qn0vFmXc*89VNN3DZ7}CNNg=A*r`_Z;0%Bb(hRRs|opBUl zSEa);X_s+PR~dUziTe>bfHRpkWuq~&kvMbj;L%Q21VWnHjks}e9*yM|pC7{$GA={$ z>BPd}zaauU-;!62bidvW6trpf5-hbJ}gb*P<8%g1(A$upx|+2rz>10}=1N zaWxU|X3{1~0aByE)~yBC-*p;|-=;ICRHnG@hD2w~^uKd1ylwtcoeOUiVmM;+al1Pj zn#i>`{PlEp_%!6dI+XqWx0_D;CswNV^3U`Pt1DA%0U_`<($}R`m4;)GhVzmxu$u_x_ z=Yz!@&#nsIATU=$thGtXD5rDAie9@|q*zX0e zBiss9S^T~xaB|V(hbtLpQqRAdXuwhKB6eamT2wfp5ii3fy_%$jMGEyF3c*x0ui*RI z>PfwCfaV>Y^pv8NalYWaG-5xBYjHOi%vojY|3VN>Z3Y`aG@sR-s_B|fViM~V5B~`pHM? zf~gFX7s>)ifWiQfYKT=uFT%D284w3I7D7=@V~vq6L8jd~ z%J!iVWFwr{y)6{T+AC3`v033kaK^otndSl3HkxzAkMzZt3NS{5F@x% zGzaN9cl0FZ{(3Cx5-((W?g~)EIrD=I%N;xf!D#RM3Zv@{W#-!|wl4F#Plx+cX7aY$ z0DcF7X;?Gc%OhRKlT~aJ&4bl9j5E6(Q*R~plq|)~qis+e!+&7`sr0tO>>S0~u)%On z>0pw&p+?e>n$>wmzw6JtrmtJr^a~=|yP>8LbCChKc@8hukA)OInK!Js-5>CAm zTrta9y+`^+x)k36pD8^8z8!VNS+u&%5>2}bF5wV;I#dr4`t<166pi@GN5;8Z>CuZ* zDCOPdi1?#$=E<}7pND#xEX6q%qkWTu6KZ7g5DK0+98(a0yq|n4XxQpb?_iz0XN~8~ zNK{Nd6k>SH%v)p(*`Glif;WkA3dxbyygT4~KD5YAL$|RpGhGFy!kKv#MGj5%zHXL= z-sWtNZqQXc{1r+zC0`Xn`U23t?Vg=*c7xwZRWQ!4K> zNgnUtmg;}L_uz<=UO)RT(0qP_jvu~QRaUss#Ng#I-<@8g4Nt%9>>pdgVp$I-|H*{U(2vX} zHcgI`j14sIqNg<^K?kh%Fq)Q)q7P4!8$wZ~^moz+;Zd9JNK@ahw_kN_MV|+3Zr(6<`61J=dR=g6Jl>|6vi=Md1EDB~}dkADAcI5m0P@LM|{}>H|*hE=G ziDnxw{1Xf4Cz%@g@34R_9aV5rAQlkfZb$m|4=kY3-b?piSb&&Ll~ZAvs<@*VtTf~x zNMu!OW|VXucS=HK6ug2?RahaXlV~+Az62g0&sO0HOqkP-<%*C6BTK|^aIOdX!`Ug& zOEE`(u)P?>nA=+Ak-ge*^9cEg(AB{=VxDHtK!`fuA{xz**7!Sc^pi zgNrMUFSJI%!JxKCi(-XqYV>c+WCX+o`bESys{eEXqLm?k4hJeH6EAOOgwv}c#?rzb ziR3~B)%&8DfKLR%eL7tlYkE98gUU&S1=~LPm_a{7iAWuiG_V_p)B*bZgXHxG5d**$ z&}GvC#Bo5GjiJoaC;bb=O{nt}%SKSSEV4;f)AR$4?((dTiMhc9H}NFA#6~Hg`dW=_ z;R3h_DPBfNUm!(laYfC>HaUWtjAG_R3^mU4UF$qeaA~1xep5?J({cZQ5dj9knWf_5 z@d%`5Fm8-~i)dCz9?LY=1W}5h4!EA7U2Sv>(N_;Q?6T91B>!2>6-uq&X^j;goq9$y zl&tqQUYzE=o@%(Fyoi^1`+N`)xO4SHpk1E0AWe}g&|Mkc?ssbkg?fsF*Qw4N`WfZZ zROdaf^Qaw%_XAxnv`NaTcUryw)d;v4n`6**F(-bFJKK$0b?-EX@1Q0ti2s;PAmo7( zCxc%KA#}t2Mga;9kSdXa9SX7#1x@QkHzHw29(Bx1lH{3W9U6byT@2}92^9;p!Q{4jCK~V(H>Ak9zrxg`$~*`fW|Z zxlIRmd@&D&o_c|+u@3lNS-7=1JrKNMGm4u8RYV1asE-F^kfS9yhilR7(1Ty&zjAhY zc8S;1>hG8%h5T@&!=2wg5Bo;d0>m%$kBvru6w$7-aTHacnoKcdjFPI?mCN?=TdA3i zDO#(^)Uo<{b?+BJ2^3@R4c;{X~A+QHKq76i+D*io^eZmjFjbWq7w7mooR&tAZp|y<* z&Y=j$31jaqtGBd_9~+H$Ox|P5`a8Yz>xJqK5jeT@$vF1iy>`Q3$8e|ninBts55*nd zjngn$?aSF&Xvf-NM?KBvHuvk3DDiM+AvCnVoo@{OXRUF4wHlF9&`Xvix^a@EP<^Z;WKgUq%BB#Ysl$wap-70?o8i4hG4|kGehdD1 zXAKi^(Bg(SVTQ8F8Z<)Q)FWoWo|XBc2=i}Rkw5{byADPR9F079K?O(0&^FZXd40_T z;SLL7@_nNwN_Bsz)^s_ESk|<){E5|=5*l1VT*+U_8fj|`OY>Vb*PU@amDJTt?p6g1KZ(fZQ z4lV4GRl5>OTM2DRl@LGHM&}n3O6%5brS&@r5^$ezi)heTL{H?iKj5Z$r^kDMLwUclJ>?VuF7(tzhM5UH?B`)JH!e50%6 zefN}v`lq=W4b7dZ(fvNx~W1D z7(WeTgsdkRxg0Dr55qL{Xj5F7CvdEDHEg?cdr$#yCbEoNV>K`ns4Y-&T>^iRZ8U_j znYVEzHg6}#E~4UQ@$UuyZzsTWl#=6${$lFrE_5JaTNLjNEAK~JcLEmu%ycgvY&+I1 zr?0Oo>S-dXkH!^gM^OTS-3LJTcO?A?4HGT=TsR71NXqbKg@!DJD!6z*d#}7Tad^Bi z?HjGEYsB4r{wCj#Y_aqjE~(VA+4zfFGrLW{2O^6Rkjb!kJB^U_py+s)2k1O=o`h)$NQ(N z@oPsL#2i;iNtNRwK#(hZhzxHau!>?ddG@6=5Q#fny)_+CpZK1vXOT=D=icma_TISd>?-I2*H&94_Mbdhueqf-S;|C*yXfvhMWHH&q zoD?Ggy-6cAB1WNPB7&nRc`x-;Py&%t{`9TtSuaGv^GWo!x`BA=9Liy@lms4Oy6zGB`7pCfYF{86@`^fXC z1{n9I^?_KzfcwXTY0I)QqGec0=GZX`ytAviFe<35Pz$^VWxR##6Dseb6%zozEm^4? zPhd>_l(>IztQZ>HE=uo}T$az}v~LktFjX)y-I(PhO}$0$_4{yD)X`>9I;f7L@o!4m z2vY~_S?LnmDcGs|wa4P0F8?+H*l=(*OVw z!K9~nNHdipV-C6=p6RVN$%iE;0&TTRw$iLY{NbyfR+pM{^Y-htFM26VTVc_{a>-dD zH=UJFE6#dW7mcVzMv%))dOFju0x7{^zNi1YnhO72`NFBryVfTMOv7(M7SZ8jBwkl?S{_U zOkawAs}_fZ?=1b7osdHoQC4*bRFWSYTNLhx3Ct`_aRPQe1jE+8M_989aeS9&r#LCy z=Jp{g6%l4Qm<^wP{jQ)zBbBRbSC2Ia?kU__->>rgc#OX`NAdBH6@TI}OgxdtMxC=! z$H41#vZn+O3YVnGzU3y_Q~ef0S5_Cliu|5Zg+~IHd^FZd?+0HW&#l}`a*R<^b|hTo z|27DWX}#4EPovT1UYpN@Q(cIxP7v2W#tEo)MfktP;PMuD)nP&`m zpCY2@pH9^=ml<<9`TucYH*cDfq>GXmR!g6&s`1+G-P-<}MW|pcZ z7HFS8S4xJei{F%fzh>xx^aD=@g_0%J zP~&~}(xz6q%bI!OLtI1Gc}#sc_8lsphBnNyxin%U-=h02@h|~h-t}oh{8~v^h58*m zpE|$hBM+Cp8Bz%e>asCc9&HV94b|YpP!ENP*qW!lh&9<0O3|!!qZ#8*vnf=m(0;cT zCKV2V0N0A#8)Pa2nHzVsK^4<#qwKjm;BmF`x_g)-mR;NJx*0}M-X)Li`m=|+W~vuP z5S^Fge<(tr$q zjilE~)-l3-xRrW{#hY-Ea@FhCBGg+$#`FLc_&~3Wl?nJRSg4Ytu!YY5P6Ut}Im8Q5 z+Lun`)}{{WR_+h`dtg0QUlU$m9qa+-pgD(y&Bqw2il|dF0^V-Xd$W)eZGw^;a>%N>A2Ph<;y;iI5cAVD& zL;1H4k(#w%o@xv=EifHh{6iR;pE4F@`3S<{sDA3A!YM`gg&T3hil># zdIB<4Q)m-iipN_>-=|T->t)0B;pO!^^qU=*Z^z5)p%Rd_{Wv>v5c0V@+v@126T(FL zZ99?OJ>&Mv2TbtoyeY@Ir1sZy{u$XX`p59{OZWN!Ms4W#=g?P-zeH~#x4*-O7WcMf z&x1b(F6Am;ydQP8PcLJEgHRJ{Upgy(NW*h;3HOy*HSzb2oCNeTmqu(+oo^JbJh>mL z2BKU&ReT2~e@6Zeabk6D!F71zdf=GcFc+iC6cWd2$>EunL-8-c=J9YuS)v2Va+>Z3 zeupQokm@_6mZUN*m+1*G*5?08_l)aVcAP$aNwPDo$6K4YnOY-?| z8i4#y<7yMT66GQ25*%TNjz}j^3mAU`ssP}}v2Q8>Bkz{{bdM zP^h*05ZQiAk(hFX7No9DhAa9Yt%*OTnvRwnbb7g?Y35XvC(;@}%3GFyK{gukfkw}z z_}LWjvm4WJl0s@V;2TM*5<~0>Q$6;C!45jB60%d1)$yKPPO=a~!mZkBM*0XbLT%D* zbQ|7Ag0!b;Ub?ti&}Rr|VL`b=z6-h|$J0)|8m|ziWc8n{X!sbwT)Vi^(K-kJTkXIA z!p@=?8*m#MyR+hJ5931zpR?j_SCu{&rR#(B*-}HYM!`j_ZeNFZI=D@- z#%<4YCWjUm^M_*=BG2c@52n-!O|I#cN`t@SmjlkaH|;O=F^^k;`huIDPwo#6#ueV} zv+9R-0)NjB-J8AJ9lzNI|LpoDXkxM!2r-1ShYm^JI#HoB!h?B|g>L>UfReRFfmVLD zQ8Nk!QiP%os!lZ9Tl*B}Ald&|c(y^`D+1yYn>+joK`APUjod0&q-8~L6Rzo( zNM)T$wf5Zek3=P-yB4Sv-|X0urM?=1*&O5&;db|@P_wN99c$IZFB;3AT@H-BhQF+;cU+B- zcd3&RDp#^z@yHbm;c5GdXYDkrMFz5`>;12?!-Ks_a4cfE)Fd>Ir{RQGONKshmfH?H zR=dT3v$5@#K*1o`jzIwD_Y@r~4xN1Q9R&C#B*Nx`SYDnvh?gz7SdKD;k~8bkL2)sq zsr4~pc2t{SnhR@6Q`56q7M5cCSQ$*9G#N}B7L>9qLR?eHV4mx!EEFOd6%UR}t55lu z*1D${m1)hlqpYa!?@bg8%~yuH2{z{aEsLN5oic)V9;? zrV10)CAZ6DkE2Ox9O&^Q9y?Xsj-!L^tkwwBm#?_t1-+PT0FrO787h0!P-?8;QO+2> zFlZ`muYXk79#p6r_!KcVOZ;k7CGO@Fa#*DVv5^9wA?E{iZ&0!Z;46HKy$rN1` zH#Rmt$sCrDJTJ^G)}V?FAb7I8uh#o*RB-nhW#O9nM7G#c&V7!k3_~hRgDGrG;g6~8 zNJ~qBkK^Mz%x|jHCKT9MP80rQIms5W{l=C?1x9k{H}{~Ms0PSGK+8+eAUUIx&${jw z8bAq+9!A%Kp%W(IBAFZx^+TJkctsG>-2dzJ`DBS-<#%{JL)}F_bUi~HXUA3Kv$fIj^;ywA6->xnNFpEfEao6>36@!OUSXA~ zPNOuBc^J7aH&K)oEsxo7<-g?t8%2Mtyx?+NS{=9%3eY>*k*DJWjzbFMn8d;F`};#0 zPlK((4765rFuRZu7vcZ0vAsxeYq5@~bM$NJ_}woJ-iUONf~k;G^(F{?bpHzfz?OgW z|69&y0Y6fH&HRI)(Ov_voHPAfl5aT5g!>M(yd|(!bY5*VRDM3B3OTe&nYYMKA}LOm zhfMFTtw7))8{#mX_O_l)vSau9XDUc+u@FuyM9h$M61YNmLeeqpdiD=~!6M9BCdO@p zr1b&~TZ242&Gu!oS_e!owxGA*ZuiJu)ib7L-l31yViqhpnl1Co1yi!wYTpBiGh)E{ zEZjGn`G-#!7ak4m0zLIjSU(iRwWxSpY{Ji==msS9q}?v~HYKO7T-sa6<)2Y6I|Bhi z6!NH;?`c?HZF^tfwa?f`n;rb;5xEXFZ5X+%_5_dUYU0$$M@?WEJTPG93tGB1S;AD^ zdrDX@sC0sMzVh83Lqc%U1$3WD%re?HWCr}p5r)vmSsL&2fCDp)_Zuk%BU&GXEw?zjN&fL z0}6UPYkZzn{{4>IjE_|*fbR%>u)}foAST-g`|byWtZ`hMDV~4Y^X2J+sl^4L`6LWz z%D!hb-6_sKq%6p7ENduUH~y{f|9qIz^s?TOs>W9W`&PY4<)4jdPejwM?OldgMKwH4 zT?x`TltO+PpDSy)HG=)e`*(9F=;L(n{S8rFPcpbkBI&*|+2s#Fi#dO5?kDJ`7>G-C zb`r=QIT*~}BJ{fN{E0M1Pr*9HQ?iAAJY3PVLD|*J_HtKIDkeRriG81WpN@7=o&0+8xlbl2dSE z)iP5$Aj?XlaJq|&(##5poH54RR@ z{&>d4wVrqJr|VxIreAw$!Ct~v#foVPR@oqdmzzNn4=xKU;2U>(sQ-NV23+pKhtmAJ z+_|;g&qPr2L;obSbIT#>eW<|Qx9y27pKmiJD)Ze>tkyepwptT7GXx!b-wNdqn|FC^ z-)E1nqE3A=AV=!bor02w4JBSkCfVz9l9k4L&mv*Rg==JVnH-|9YY{G@3i1*7k<3)h ziKUuky~)kF_D5F+BjaFIWD_G%3}4t6&{{f7{`x%bm?$Q?y}d57aP4`2 z8_Hxd^!?fz%Jirua{w{Tnsa6&b*u#&%uIcrIod&q))5PTco;CSh zDRwX!x)O_f|5CaIIwy641-?#Vyv-y`>9z}mnx?cGj8+su9vrO^zlE@dh6@x2x+q~4 zOvf`{zN;xKIerNqgJT-)AvcuPo)nCQ-vmwx0eNUTLZphT6LMnheD*K(#{Qi z5mn@Q=HXs*N@(iCn`P=s=4ZVnQ;x!6H`zrWkE->Wz^Y0O1_phQhP5!_A;~p5GIn$v z99_+DMAj5x*7@?nW?(Ex$_XJqwBEGSH^#_L$szWTmQVZqevKwI1t9jTrU;Mcp~bV+ z!eAhfJ?=Arc5U@=?-+PlohMl{JH%cZnCdox@5cIt66EQ9=d#)vN=vYo!hwG!WJXI6 z-8aePjvfQhyRZU2BEP2o4hXVs!c7usu~ICZW7d0+C5*DgpSJ-?H#7daC$y3$Kz@+8 zkyZ(tBdOm8--BhT`94#^R^zTLFVP+Yn%?@C<(t)XoW~S)iR)$IUN@?|oRQ;43aT-f zbR`{#xu80MaDQgk{_5N$toU4*c(7%qGi zJ2o?8c;UQCY^BS3nhURxl9%Z;f!0{GC*^F`{;(dVZ)kut-`Oiie1FIgd%XJ{s#$#h zql8&}iDzJPJPws{25UplO$?TlbAl(pvlw>(vMMKK zwdkm+U;ANfmMUQU?%>Vhz_C(KcOiaZq?-LvgNA@pN`I_Qp`o`T`OXh-vdk%YwrGy- z9ij-N^w^8-WBKw)a#0_p1Jx*!@z7||Z*^x{ptT_!5TGgAw z7DTs=SER>c%8J)O5#=fNNNs#ERW7=1<2IMw@_Flym%Wrb;%#t#{n(?&Xt>2~IP?2Y z@W(2CuSdQ&$ZG3IL)@9t(5@yfDul^?TtIT|518bUFK znJ@_jXsX`NNZNf2J|7Kc^$Q>?5Cak$(~?XDG0M{r(4b{&MGFlhr>r`ju~1NikDXkQ zo78lsEab^<(`v)Ib8x1v;NJW?rvp2oa=q`s;-I7N{p_D_BhxG~z~|l`JO1odp03M8 zgP&RhV0DY*#YpUL7SD9?Ko~QUV%sZ?CD0b7sDyxj%R!`JaYLpahd|EKF5`C{uSk)c z!1$vWMx=oG)ts`ak1RhF7XunkEtY{zZV6OG5R}@TN$V*W>H`13-zOK+VN_L!AeW!;%V%-rl?R5Ij$`waVQfscV(~(%Jh_G~61$&+~et9$)Kn zMOw$~u(AFO652UP#QFsCi=}pXE!q}csE)Ads<_Cz5P4d-dGU=PfDn#bbie>n>b98~ zM4_1UJJf5fkl~BRi{sZj%f|Pr^SrjQ*(Q#%pWV**=D@ryZa@=#R-hqM8!QhMM z5~>w&=f@@|4F5(Yd@YpL7Z%O0k&jt&1d@n^`>zY-naq8QPBLxo5sDz* z=MyS(GTwOg@=9+#_%$?(x2g`r;n%Pe{d#Rhb*pej(iN7@TTuG}m&A!l9TQ}y_i%p?u1ybBO82 z9!|#U6UcG_XTj zcF4qNI_64cK;ehcU(TaTzklDPzgWhszi31;4)VTM=sXq zbN(^!{pWOFW|M{*V$V;M=yQe|jJ+AoXs9}1ojLGZZH+cS zg7XL)7_mwUJA?RiJoyw>HfVL$vQbDGW9j>nmr{uscvgJ zmzA|0)BYAm!#Tw%xaGCisnEkXx}>UX&f){>k_9s?26w+KQHRwe3JHU19xDb^QV_-t z%(TDBxYoBWm_IL67|)?4U<;xLMDcLCfnH7W#w z>R5Uc8&`uui=4VTYJqQl+`5MKaH%gGBi%8QV%WVdlw#Q6l{JzY@soOQsDlMf=#SR% z`N3|(ZfXUkhat%lI*+WZUI&&G454Lm6>unNU7PG^KoLa!1YEd!bxFql;r8Qh#^&kQ z{&vxZ% z@MCFfAlVlKN4JZ$ao3Upd@IyfXIr4^o|1sHp!Vv+_tQ&R6B&!8?odrKB#_~>`vYN+ z!VU7{kQl^QAD>7K0BI$8#!Zy+T2|;hKl{q-KC11bvqFR18wjF0seu5<7ZCD1<5oY2 zl^}=(iKTYSLM678COF=LL?uFUkKo&fEDJmIqZRcp=LTH{kTaLW^`X1(Y@(hO|Cmke zTfkun;a=v`OyW8^u^IB*4~oHabQ-T*Imbg^mx<>`RH95~ba`Jp8r+^YUx#k~INR4? zYzx3Q$x>^ZM7obUuo0AhOrR_F97=+c_d=u=$Hk_p?43tpo3yg8=JRys_(xeaz*8D` zTLv<)teT@Oh56CsL*5_=*cb>CCc?tz)GiHkQ+`ks)FO$0;vA>7Dt@=LR z`G~%0H!Ez!ZfUV}e^l{ANy4vNkOSQF#_-hzZkmg`gx%cp%cCL_buW0xWiN;MJ9ZYSKxI%ru=^=ywPRN zH?`Ko!=ZvBKujwzKQBb0LFAL9rl8W7&hd7t z+K}pHhO1|FTMwF_A$NuE0=wMKLhoMM4iK7OhsXXXvTjKkR8$MH%oPE9k34Q#tGJj|`ggV_(&zKl z<&$|XuX4dJGOwrdK(8s#f7~3tw^{5yG>&)-LrzO1L^5x-$!;Qv=`{9V2096-kHh9@$(*;`UOUMd2Wm>zL{Jceb`IBoIE`$K?I%*vuPrGFQr7NVKZ@mDtB$!EVgst%KkUVtIVetUf&pb&G zN?K~*o^pqlbcCSRo)<(n&c2AOwhWCqQ&y8m3&G5_J@!}1*wdt!6|%UTAskNj=*WU5 zO;ps08rv+2rjO>U>eDMWM9p{Nk%_FasEuTF*H`c~c-`5SurCsBAL^Z9li zI$}ws1pVOGbFcJ@p_K%FDhx4_xeq;TNYIWJF+DqmnU-yg#{<^5fJcC)Fx)XH#UwaI z{}Y)m&3PKL`eEs!VE`JBgfNFcI zZyhJLKBUX}#0^$C+hd{<2nhU#_U+Tx5m%3P+P%Zq$2AtnFF33ok;t8|&%~XJn$O(# z+@Te&NZYSFb(m*SCalkO2K$O7oQ>v`LG|N5GCCXU^y#npriKj&3-B%lG|Fi)u}KHC5BnU4gs{4r&m z$Ei>6o)zLUzrY0Q>=W#!P1o!{c5d7fM^Gm9S!wX&mlMVSKX@xCf)#j*)S=xE*eYH? z7T@ec)CO_k{g)MkE>*Pi@)Cq~$;+j-MV4DJY@nS=rAkQZ~lLezelH%tiI%MPN z$XcwV#bya|uxpm>rI-!TeUU7|ap@?tPYGwP2DuGT^8~GduToDfN(OJj@pv&ugQCut zV$_4|`!Enf#=_N4!lwP8p%JQ~1ouUAKXrOYtXtW8RS!UhHo?fm`soo5$7#Mdx+X3Q z#-XdBs+BzYvw0~=O->iBOAX@_Xg*56_0o(J>Z7^!9L&9RDI|B^={}*~jtq4PXYZ#` zx?^&+3);CFgNuz}pv3nNv-qIM$vH`0pY*P6>raiQ?SVfIokn1DQx2K^{PFqyr=;?| z`3AC0L3Dqw7}4WiglQg5l5jb5eb=%9n>XXyqnR|fYO@azi}-!Xn#G zMi+XHf=xi57fimu)fy=;i%dU*N%C|Eb~ji&K=_Y50i8D2!4G*57G(e_d-*!R6wtuU zl8dI32cAg zl%$P&x4O5rMo#*eKCjVP816`9D5!oNyT-bZTZEPYryohnoI8)+NM@|=3SKt5x?dmBU~ZE#A)X5iFu>~*bRf`Cs0)Rrp`3cqY?R2 z;I92eas4B>t9eYzOlp`^(}%*w}C~@p{^|ZHpwm*Aq z#)qf-&jOOuCO;Ah(@rA)?52lz=caYDl9>D$<>6#O_%@ad8$w~4CX)wQ>}ONx{}=^E zUW|q9{u!z@5!YCL_*J@>qkX~g#*^30&+eZ}H-cK%a#;o3IDs8AYOcP2PsSnrm6d5^ z4zz~GxT#Wxrdv5(S6C#OUXb*Di z^jMJntnNpBleOb1=|;ouo>!ym&0wb#7NfM{Z`@Nxy>|se14Md#v&@CRS39m2rv-$4 zn?sTPDB)O48oQ_ThH+V;lt>EUvxWM6x3H=;W2Ycz3`W?2C?QRP`P_NS&#w=3pjl{f z%ontiqkY>glAEV0Mdfjh(($;!qa`^3DCG_5FoH0TR0g|Y6jTyRSe2E!abEJgpT;1_ zSQMxlEEC3@GT$0e<)-MctE9LbPG;&6HS9~Hnyl!cvh*QNYVOO=N(Y~vn}{c_Z<_YT z@BMmPt3+LRfY~%0R3FIb(|*S!E_F09{t4ZR@Me59LsVb;lokNEUTp0C{aa;J!#4Fd zm_yL10#V^}+JkdNmm*pM?-M*Jacq%sE$A?~-qPeDVUl6L-5hAtNSb}uc~JxsRb-0s zO+yK1os7_0`VWUv3wp-WnG(vW6qcD{iw+mVz1h&2V#0=@@Zq#aQJWi*>q_3}e99AE zU^{ur3v4HovVrYnQZ}%iY|4JY^k=uTWXqK*=R^O(yhEs`{bfGKBab;UIVph2;s1b?5WabIS~6GFbW z3ZOgAnw^4_C8!>XO=WgSjvU7$3>(s`HVh^ehN6gUcK(^@UesiHu9TN4HdWq56Tt|N z!ct1{>|>)C`RV61n6>1{nsOQcC(MQi&`%~YVC}Z6LY~FTO1n?qUpI>IV_MC4ITVjPIK`8; z|H0TjMpyQ2i=vOsitS{@wr#6o+qP{xRk3Z`PQ|uUNh(Rjy6eC9KJT7;-fgeFZ}Y=y zZOoC3HP@JZ^#1Ee>&FKVF>`$ZFRw9eSa=pC0ERht{x3SExTlQAkAuURp@W{!iMns` ziB0W^&Y^!zE=#^%>b~@W52s4*v$y~DCVAP~y5TW!zEku0yh)&Z@-Q;8855K&H?@YF zJv~cISd!v)BX6fCM(p)+yr~&rnI-`<-29-mh-(9Ialj348RL5&{Ic8rse$R}S2avy z@G%wi_0`EGP)d5ck8=gqzFJQWD+!+vF_>d`b^bUH#7f>HfAQ~z%+Va4k6p}3ERJm%$-!q}Y%BgYHBr3EgAf@UMxVN4}GYQt*7K-dV{(8QDW z28!$b8-F5mULTXIcrQ!UtA*S*>6AL1vtVJOfWl(4x3TUBk(%)Ig4F~u*>}6CireJD zBI8;LRF`h=@1d-7Bn*yR(DklGaHo6uIq{<)ydtHgpZ3$ zhoBz*(hfZZ6cJtfL~Vn393wKVIJSd3`*bJ{V1mKeI515pnct zR^7DQs-NaPBAo)9qos+vT?2U#gKA;GfCquF4$8+>BxueLKC58%LxEpCFRyhB+xLG& z73q#%JgQn-msdaYUccX-lrj1Wt(3(G@;~?acRr)=xxLi%-MM%0x4rCKX77G<$_v#I z&|4I_#5wi0_kOMK%HLS@yyp5b)(L)SZW4EU{e4h;y%+$-p4qM&*4=|gm%9EuArpnq z_N<|>Xsu6EOU8Gqa2yL^FvpT1=32lgReYBxA@~zCWFv-QocTk1GU;-mLKTV5EaxB+ zO(mWzpCa2?E}|uJwGMn&tGYpqe>gQZHLw!+1OlIV+0$Wg$^=S7yOIB}=vm|$cy>N6NPaIX|!ReC=|;|$jQxwn zRAZtVlGV(jxb8eF#7F5Jzql$!-W9TWTQdDZS&K2pa2G=>Mr=iSWUQGt&lXhRuPwu( zn)}t{KriH;(GZI@;bSR^!8FtO#+FEnZ0v)V3ac~9r0C7>SJf)SPe4xOftT(M%V08= zxk~-D3Sdf5L6u_af*tfN9;0uQxuVudPWGm+OTqoOz>oJwYtWaD$cR{@x*grB#H=5< z%>KV`MqDE13#S;fDUPAR+KA#{K~(7F!s$60{Hg{Dqim8ilq=!_Up3~iom;yFaB)nn z5_2fm84R2nbHmLFKpv*35RpRiLtRiN9KY)nKW@(5BgVpW8re`huKyQYlZN@@l0xRkM2>pU7G8Tjw3p z6GxAb%poWbBDTte#6|ADIqm#X$!G+zNiN|3{-B!QINGe7&!6%wQc){MJ92Kcc%Yc- zG`ifV>0JGzRyUK&fhZlclO)u`<5R}tYG(Pk1-`E$9sXkT&7t=FUeiIaE)VZ@F=utR zr}x?Qc7ODxN?p7270vVZeDN~EpyxSYSHHRzzE1G^g{bePZPmb^aNs7(6V$?I|DJQ# z|KViQ!GLHN^#kAYGVu02!@OB967W@X?fZN%MvLqZ6SPS&nDBM?0gO`i`WFuUICXHT z*?RT82cBEpe)i-3es;87%u8dy>(eOpMVSr-Ut}Yz;bFT>qkIsCIu#54!MO>oK%x<9G4@7%(IO@pdV2P#DZ`u@d?o*~Rd|(Dj|AIKMh5EqYJv@IvZ` zPiy?ATxYc%eU@uN7;F^#j8t{Rz`=IW%jHvx%CifGdqNpbceht4rWg1byxgL$K@Y&w4E4--VV>VGAx6~ zzq<{whwI=O#hcl+XI0M>GcekcVvyZ6ib2NUK|vVMlYp4mhg^{+zds0d8|@l4?%V8c zJ}zzcv>}6)(O~pHK+s7bQaQn-4l0&cP`>x^`FnVM9iE)GAOi3VHhs8O#UXH-FB z2r}4Lj(oNGpIp&!1t^F0zrXdpTaZ_CGip?+ErXm^LUm{;l;G|aRY92@W;7BWpf3kn z;6w1SRcW&Kzf=m>jvT9jS7V5iu9dMb1a?kFyw_EN{DRO#Vu_~RZ1}yWs)ATLD4bzk zj~srk7D8r7S&2Ixl-;C;cmlr=RS6YQz)zqUr-`@$l2eNN2cf`ki|Jl1k^^lL6A_I1SjZU9+y@OG(H4_mXR27sb<#N7lDpbRf{jy>28iFRGBj~c>C`E-L zI}EG<`I?C#{VB<51Ulf1pnrpV$PtpgX}c1%coM#jz)_7_2xqJx05RlH&V(sJ${^Ni zJ%9axri$qNlL8NGWsdW5E3B`tKsBBl_HH9h|q%`r-H@}4u*DAhGMSqretHX zVP+o!Ah{Vq&1oSM0?&Fbi`l!vV6}y>pJl8e?}@3-CsQH?6G}0su}axm3}@Jf9CWQF zC4V8jV&1;+V=5y)`mv+*TU`vi4_zrCH%jSy*K2Jrz|f?7QSf9mb~fJ$a>&x=iA3eir`rjk3r8LbMq;YPqxX{%dGMss_*wCI zM0?-WTm<@4~)(!tW?%*rdo`6#Ja|G zF0+uK8(L8&*2DX zIPLHJ6pUsoo1H0>>v&ahK{F3u?(J(&h}>ZZ;T&}8({|o;NWIDHXBOcgfUU+W@WRSt z8sLW*`Zug@P|**@z!#0>Q5~{6z^9q$D(x%e>-nAHc~vuyfuT)Mt1ONr#`j(RX-*me z^FGe;@8_3u%U9pV=2x8j&X>y=m;YA)dyM$R+gFwH+m|oOIlTzW38DmokStq%I!Qh) zx`rEo0u7yY&T^yj^CI)@OYN89`j>%x+ZPci)otNnu@v{%hhR~P;nC-pwZ|7#Q)XK4 zSD(zr*Oa!$cONdMS}mVvUmefPNuMFJKbFV)j!;*cgaQuf1cj;OMLQu%4(kKDVblnO zwE^9|FE8G5S0tOg2Q3k!iSa6d0Q@F}=w$>$4-C>Grd4B&T#J?swrJa9ZQXcVSUv7& ze5h$BIKT>q(lEARaoV6tX{?QW!PC6ww}(jJ!+NrPV}R*oVU&Es4Psc#Y84?9$0y>D zxkFRyxy8B8eV!tfiiM#N8z#@;+P9}n*Eabvqh&-9#e4ebMhZ+MopaOg_gsL#wXHou zz%AYTVv}3OOC>)~C;@B~cNxS4O6vFKql1}MgKjTkRbik+C;pMQP{Vpl2X3jl{Yn zNF*5)f!-q&r&5XC(1h3e#fk=>Jf&aFwZi4a*#0#fkM3~zB8sY zt=L)n<77*`sfwcmd}d4Bq$H=&9C1^8Y}w&NRBo3+CY6IUkdH1LAKaP^6qig}umxsF z`y4jiw?Eo*iF8(tOKL~lhnD-$;};n*WoOK6V3y04_|zby7nXeGd1{N@9z(Xq8numa zcYRaF7IM|~G$&$874I7h5THw#JbxYNq4?Ry@7qqGx<0{C^6O0NW<%kHlLzlXOm|vl$i(*f0HP5pd{~@w$-dNtI zQY~@z=52Z;WnRK^-9iwP0BFiaBBB0ZzGC3REtrg#11qo=s_tV`T`NW+TxOyR5}1KG zbVW5roFae>Fz{KsKn~i2E@XE_vl{$R6`d)O(BFt+7_8rQjZpNosI;+M$O==)aVz0l%1&BI}g^7gvxXjY{bfMzhtD}5LHDo z!ET(e;0S+2{TUHSQABP0eB^RaNOHRrHngIWW1Wmvx{7ja3TMR@PPDGbd=Wx7c!7z? zRV7EriIL~0wvYioM_~BKC3K%IpU;Est^O_}VeQSs-Cb|zU;BpFid{%!kARmUw`N+k zkxr42m{E@~LMX-tT33Q`?I~=YK`r7noopL0<}kInh~a`Odb~9ZD^z;?h@iiK1>#C) z?ScEVU*(VUwmwt8$e@(N=z{{iO+l9D!6)b;7etRLOhOIQySE9NxLsKg$QO#vwM7$x z(gFP0_>8Ag36mm>NrWZMVVJG9^R-z^=saYtF77j1=}KvfKXZC)MkiU!;&2;n;;Uwl zHgxP)kAis>2%vF|5dh?HmZA86a#>9;FZadAtOk12|CEboS1={E+bFEUc_TCrGd^VE zGM={Eq=)Gr)MhO}X|FX3cbRL0mQ@G&$>->3@Oz{A_8;PI1^;InP1HH<(!`P%Mgd;VOoW;Vvm!p6tRcxU4)# zZ9PeJY{_B?wwj455-3#{T-4!)3o7F|%E2%Z@?i+d*b*vXKdk?CJP)x0yJQD3U18}V z-Xq+gEh<=w9RA=^^-dDkFPW+%ACcJL$MoB>xRPxRx406+xQ8NpfwHB(LSyLFO3i8t z$x3ago-)9@<5*GE%kEi&@K|vMr%5qapCd}Om_ofjHys3%LZR*E;oC6SQYdq64-w<;@&39`q+0qGII9G5CdjF9u_WCtSi<=Z+ilTD|=jx+Z`FHdu1p>wJ}?`=oC(Q!Dun>tDegiuD_KY&EUlx3G{zjAe+y|Ge?0)EGJ8PuYe_u6 zW<0&_3smff*uRdV4Ji`;J`@)Hn`K(*m9B z1P6hE5r%*^ZCflBp-5&i?tU^)%Ag>RE`jBg5R~v-m61Yngb>pnHh@WcARt`b7pb#W zd`)_tVgxI`HJI*(>2%vqdL653D}{=cz;IBQn;k7xAj_M!`Jl;I_jO_ymx{iUYO{l0 zU7=NNhqqLGRAD>(Ii64DM7F>&QUPdNl}1 zg!NSA+fy)Z{;UaE#f-(eTM_K@Ks!GqE>*jRizrSbusc|+SjT8R zOvGZ3!ybw}5tSt(Q5jbmqJaYZ#mFCqGvre3mzwp13@guGC9~UigJan?4YF4%=&?-b z_#`Xo^n#EQ31G|ENU{G&r4SS7k&Q6ljG4tB=@jG5$(b4l3xdc5LQ!x1h zcf3u1_pw&rv`qz@{%gw%`Mg?Rqi5gOw}LHrWhpKC&v54}ADkKlpd9mwn27W=x1Lz4VA@NBD z0;io@_wz;=Svj6_y3tf=9v9p;s@)QU-~hj09kOG!1V0_V-=8l0GmLBoa2Q>0;4m^n zE4;#H+jfMp#T&|o_{<>i!xaP3-k}N{89!;}xjWd%OgzqCm{Bvr|Iaa|OV)v7Oq95E zZdlBfI5{)$Z`Pm8mAFJ`&}1;!jFETzwFR7QmXs8Yl3EAdsewkr)YM6w8YydNn~bql z^0dqpoho0n_)OrcoEbk739;Q)L2U`Mmic7gkr7q8Esg}!mh#pV2 z>>Nk+i_3W<05|V{&){mIw^3KyCMYBgV(ASIFdp0?;$16x-Ik<8qu{-u*8E_T`M8I%(p&C~6f_w&m>ST5H|1v7Ce z9tq&O+$Oixt)E(JP&H4pU6B5`;~y9L-vb;TmM_i%Ue$B&N2=Z^XEC+&Zo)}C9FlW+ zrPcpVYq_~RHTAl5jm>fxTnIR)+s$$?RssZ;Pes&*99K0gE|g5a1M zsUqPV1reA?9mQszU*#yJ^Jpj&|5ypM9t`@61TI~So;LbZ%|Ci2se z-mUKXm05P1fBbm7AN?%~e-m}rNhZ5jP;TqDe9dgj`_Uh1eh$rSk2!p7D`psdL?c2T z_Jw=7_<#$XZHD#2EzS5LO#Ot7o3zG-fNNf|AIy3$}4 zn51E8P{%pK8OsB!)gf^_y*|Nf znmSC}9N7|rqma&-#x>Tj&k70`XR{F=yhfNzXK#6q3?6T3kW6P(I5r@M1HnuMmCi-hONR0DoA17i_$JTOR zfHdOXG$$i8L7;O;e1yJ=4+ZnQz5o^hec+oXQ7gA)q{ad}2b&Wvn?>UuhgG~(38!3K zF(>N~@uaN`ILWc~qOAJB40BxFg2p~F?e12; zVb^{qp>O2Lkdd5smZ0&zXk)8i;i*lk%Z4GIZ{#O;x}Jib2O?T;kG>AQ6tD1cx9ERR(DzVUox9b7 ztK<)G&S$cq^x&5o4Fd>S;Wb`85zynq++x88I>1tmp_`e&`X^z{~fpO?R}A9 zym~9wD(VW|s92QQ z%%IFcnY0t@7RVEWMq0$_Y{;ac$okpns~M1U9X)!tf9-ws=MSPs#^>jKSI*{U;9X~n z&D)~EMZ)Lx=ZisaXuyZ-NdF*{;FiC7UUpKZi%cSdetf&2&(g;g0C6{g1b8xAn!wI7 z+HqV7CI(#g%Gp#+sorj;)@8|COiZwI=K|Kh zI9E>dpB_90Eh`ei>nv^rEHqOKD*6jln(GuucsrHkrB*LZPGisLN|>oO~Ax9 z@$*!^kZ)#7o-QC{OUiQQ6<3%5*RjH#C2}9FjVSzLffl(1rtjZdyfKuP#!8Ud;JBsG zuM{xaz_7su?Gf7T>hpQD0uEKkHfcLcnkt-aUlvw`!q8G+{crt?nCTfAeda=2i|XIy zLifAlA08958PkTH=b7#8-CwsOejhbgZ=(vXH-l~dZ-4efdf%{niDMrm&ft`ETnxxy zVPGmiTrh-#f5|B02V)D|T)b%32?pGc)d}c@_Airtosa^<#k*g(o2yt!#M7*gWd>Ni z4|pHIF6b>X4+l9+p@KcncW(~*0Us|pbzhpc=s43fe&~!GFkRp04$`T<%JXxjk?|uS zlz?Zk!kLh8+QTK}YeU&idf{=GdN5kg>KXRO$?yR_E}CCZF`0|aEd3hw{#;tXe6f;z zfA0z5(0o@%d;xd=lb4ajynyB5nU7=SAov}@uc@k6nur^QjGK@Q{V(T^_vH)wh(A_! zpC8K^b$wqaR~ZiaA8$_vJui=&JTw6{;Fv4v$e$ZtPX{2r`+1s&OPau?^11Lx`>CCW z)|7K-%&rFq`g~c}rP<%dkC7^<2O+oZ*Y)?4;eGE!b}A>Qs_p^0@8v&1=`HoZ+0kD+ z$$y)AbxF>h>^8KL!w1X>ee%-h-*T$*vSrU+iFg@1GCgqpdnX_nIYw(O4mm`t=d3jF zHTkAtr!F+dnS+FAiv#mjNXED`T;0Z2>d*0hBT7Bxl$<}D(yB516O=WN=2i7Q9*a(io53Aq_sT4#O|eFx9y~Q^_jO7+N=TEOpFG*vNn%$TZ6xR(xkCrZ%Z4JV^jC=!nGbT9k8p!aCUAn zHph1DHq*z1u_)I_-4Vo16-O$d0$dh?) z|1PcOx6`At8|7pE;q&zs0%~g^AoQR@D8rlz1x`T=RnCqAFymHAbiSar(EY9F@k`9` zt+vf=EEXM5bXA20>eB2r7e=GROI=p+KW5-YjRs&Cd$QTe8Mthii6|^t2Vq1`;!7LYe z8V_b&4uducpN|th?lVkx-<3^Y`dtQ(l2~#QTssL4AVyH1>?PNJ8+~ov3d|gTd}|!6 z`38YDxs>9llY*E}l+I~LmPJ+7{_O-{H$*l6s$}r|*&|*Ha76y)eOlAky*-=uPjTMS zKI4BC=hb-+(6%8@*^OMDjPY!EAiKXfo^Ee0_k~s5or`FuwLSFLWo)kBoe%O?V_ZMH zzC=cuA%80lMOQ&j++(P+COG|`&pMkur#OE%LoYec<(_SA1KNxZO2ZEd=K@ZnK+J;4 z4DkyGg$>r_X%dax@+E{udcIU*@&&bdA%D|`$UaQFE?<44e1+jaWASyQW}&r(9gR~C zPdg{aWt8GC`pwn5NsQY!bNpm-^4frX=8;Zc8$!QW)?xl*U?oTdgLr;9JO_wrJU)RP>{~6lNA2S>Br)AC~yu*5Z+s4;xvP8adB+ zM@q|wE%^m88CxZALabE4DVyxSrH)AFrT0;@dm1tawUT>@q*P9GWC02XwQVmGBRp+) z$+RZk*^q>th;sRoG*r0nH$#mImrG;i-f5DfE@qG!GdluqxLt?9?<(9GjXE3A<`Fk1 z!hwS#7S!MiyI1l$N(3S)_#+y=bqEGx!s{`Azn>ZR& zyc#MMvO7~oL@0F$3HHnoa%?A$d2vnkdXCu6T4v(|hc&M7=@VfhzfDeEh}a4QaS>Yz z-5HhahhZWvb=X<2pr{B#*ew#angPqq<*e{5+a40FaF6*s{#IU6c*1?3;GrQToqvGD zEd{lI0F)L&i2U$Q?uN^lD6%C!%6J{X-TztIPz*KnQkKm%8an-;FBs#vC@*k}~ zzkI~&ZYxQDuC_fzP-t+g=RrP#P|w016N*ycT_W+I!U+H;i;gd^IB*&!{I0MwD8wZR zr|fW24I`cK9%un{Bsx374SPwfY&6 zF1impOtIQ1xr@n%LI}0!PwaaUzdq=6!Hb{|U6Zq%+ER93{VSfi2z%C7=z>1RdoFhf z)A9%=zZWwz*EKL~dSN~qwf4zA*Mang95APrl<|G=fRDySLrbN7*_?yT&WXUiVx(p}SY>YzE~T>ve-3Otzx$`z88QQMyF#A|rZ} zM_*U9pSLYU=R-ob6oIJ>p7y@5e8-MOj<$VI2H4#zxkqN_Ecm1=B>4!2W3ThL3N6WP zS?`V_<_p**|ETX#Z~#K07}h(rQF8s>SoiZB##bKgr&skecbu4+rf(bq$CeOrNtHtu z6S}1th0+3Z47NBh3w+Ooht|KAw@mle>doX1mi7}>=$qbcS*Y}6G|}zBt%k3wIj#RX zwKFNLz$BNoGgtXX5#>lkiZi9zsMf9v6NNyCWQXB6EN1Q^p!9(BrnC{+{>Hb)EW9r0 z2dUep;L}v^daD4G?Z+)nqr>JO+zSWWP_!L5ZJzWi5l0)tF0A23^OpJ%h8&5c(cIQ# zBcaP4qEw$V{GopJR{zLUDE?2eZ zv_5KA@99{-Nlbzy$@DOTCiflK&QE7=qs@)U^BR^GO79h^WUkSSG{3_=Q^xBGWGA^WD0zOKejmw*{Yash062pEZN~8 zUzY`dJfZaK+E7shaz%jv!CFWO{0cevQ{ zgYMUm&>OgjG*tCY&EMqy^j^J-*<)uRK=+`dC@O*`1ZF-vm7FuX#G}{Ky#oyoLJ`oZ zjdUr{TO~NasA@kZEgE2NfSxUk8VHHFb3xkn)R%HWPM9N z9|claI!hjkTYDxzlJA5jD;}C=yKZ{6wx9l-|Mp=_dF+Yx|A9HAJ?nYBh7o{Ganh=L z8Gc%QE1K8ETKc7qjz45B;T8Ko(2kEuq1OM2cF33o9+hiEB^3V-uTaGy#-aM56U%)h zMW4>+RNQ%p_}$M9!`}&4eUhD%K*UcC)}{ud8G@?@B0ue@A4V>aEv=lbRBdr|KOX92 z1FfIHV|qNZR75C*xn;nxGU7NMG#Ox&jZ10qT)!22D;@CCRvF5sAZtj_63B8I81w)h z82-H$J9U0Am5YBTL|sq+Sr7*NdwBr}D5uLR# zVq)GyanSm=z%(YK{O04pGa1zocA!Y!N9yPYy3z%THOZa}M@Wh<%|PGUQp z&yH0yk>GkL!5x7dQ$^BQkK!mp*fONZ6g!q;8BE7;Y&^EZLt=hl|yiNFYXRylU+dH`c-1t1dq z@c>y0gJ&4&-k&YweO;a`^WV`9Pvj>ANA5BHcDg%Uj7997*RC))on;mFxb<`;ZhG4- zqhr_IR?i1lnkPiX?xN+zA;i|UVB9W|jAQ&U<82nnAJYL4MufZX_#8I_GLp4sh!Jnd zSu&C)Op++PlcUrO?rb3|H6BMxp2&95jx`z~&Sx66%jfMEMLJLse#s~ZG@%6f8c~Tu z+6d^SgQTZD=y!%+Ti^%O%(WGN9LL)?gqd;9dB98yi*10U2H8zEqulvMM8hzgBJ6x( zgd@&yV4*fhF;2B-KE@i+h1#v7*(8z6_mrBzX}nQ%$l}n=6LjIv4rUAcX)!}!a#Ha% zSSiE1!80vrVJWxww}+ynAM$`-FDpCs@*}a@lz?=j>)A#272mtCNy(QnRa= zaZ>sFa9Xks2_(g(uC%0&IFDwoK^LBr!<(d4RaUv)w}E7-R(>5t2RdxVDBB38J`@$M zf0G?N#|eY!_>I+mtSs%zvE%1{^uJUtr7`X_NyOJE!3#JrY8?{Nlc`bHnjN+XKOkDr zjMY;919o5}@Idh$)BzM=6Io3aS)r)(R^~(D$Y>!U(zr~AyTK%UAZgIQ*S1Up7jQ@_ zwYi*yXl>}gZ8eEcD%0bM8i_+o=h>`IfLB-C?8TgkK;47Kn*l>coqcZIsn)+3zfDtL z48Bl-m*P+4Pt%XxWwM?B=MBLlBHs@v`n9CH)Mn(gFY8?)Um_ho;QNPem?*y8mpS%| zzEkHWnndm8UA8_~YVRU4sh^#iLf#KFCtxUDbDNZc$(dkaegxu)mw>h=2d{&W1Z|x+ z1y>jckZU+Xc#3Ha6oYU}YA9jaAj%(u7=6gV&7Je|vz3WUb2HHh3sT!I7x>>Ovg zEKXZ~sy}6a)h587a|w2TR6zeh-e&#%{t+vBAs8@6K+zctLaGhvn;3{J3n2c91_e7( z)flk);=`DFfKk#(-Qc^2|SX?m>vPKgaoU^q&N; zGrM%0a4CqxNgN2V%)f!*ePQgv*u<{Sl!dP1^Z~lwDM_fYNF=v-XMLZ($GgWvANOOn zO%1isn;I%NU4#?Ks&eQhz`QQ)jgFDK9)q=ru70xghu6mLs$COZKR5cOYsfzdya6xE z3tx?L?-W<{=V)5osj1L0MOo9niI&wSpvcHX->@1FbAIY0kFTvtXh0+jI2nrXos8*z z5K#%G$c84#EPT0m|2Ua*a;c%uI5Z|yGU|qyCNrq31;d_VGR)P9uw6qS;EBNZsC*`( z|CiT+HS^COd)jI0pawW1!{j#_P&35nJl1V?+7e8qmD)4Z#f|Fy<-iw8t&8)82MNJm z!o)=k+<;aev)qo#x*tTk$m3of;#Y5Lx@Ifj8X;UF{KTv96%T=KiJh0AW_D4ICE+C8 zx>f@3f1M~uE<#oJ5@oa@0c;SK2gHu8!SO7E5Io&UX5j6r2QsQ7Ux~Erz-6EbTn2@@ zR>q0BWSZHGeCH1!VdNAGFo9AZqsihZ*jUELOhsFks!j^u22l2rQ&m_w@nk(0ySnh{(aB7R89TOd>M~YzxseIDsfJ*Q<+8vh z4z=?`Yk=YjMgBgPb}Aw*iF8x>Nk#iIW=w$wtm4v&8uywlr%)A80zw^^nvQUI|AIOw z09;}JhB}~VxzpZ%jyV$%(eY*R!7G}&ftHnj!Dk2e_I_a zo(r-6SRDe6ZsZreftCIaLAdY;tC)te)Z}Td-N!At>>CP^l=7G#qS;G<{c3dQ*dS4j z#a1NF@#I#$XT`Fd<1EpktPh8L5K4>?iN%#5p=#>b);2o!zTA;2rOvDcs2zhd)55aW zJ`;|s?X?-r$hkehvYw?XpX_?h2!HRwS|nu^l8q4 zh2i8GC_#PN3Cllb^a;fdy1o|vP^+)k*C(e`4aJ|@!5Yan^D9GmXrne1iYAtk6q`nJ zm1(JQGF2LlXodC-#nto?2|T^Ci562qRQNV^ zp+Pf*;d=EgH~xz4IdKGLe;?f>dI#dQ2X`eCv|HU$2jl-SO$cwp_x{W3;In2kuv}12 zMgbRRJMA}~0WGNpi;h5nRjhGTqA5`piDWTLrvATD9p4mKXj;h*))sGBN-kRo7&j9F zRKGqnTQT_=^!uLQ-+&=dPoIWcV9f>czRyL=)%F1UxzXG60OJkToV(r^9aH@OQD9Z8 z(s$n}FzoKt$L@72`1Pt zC=lT1bo+R)3QfxSo>ZzzIKsTfG9|db0+Ybp`CW9$XG( zxItCQ+Wl_HsdAhshKCT8@D}fw5@P&CSu*Qa9}9t7vmC1UWr}}1q`N^MpNXBe1&awLMK1b_gtLnr1ZI)7x1PNW zZSqL`rip1C*V}{{_`vxSgQL9U zwtA#Gbhc^GoxuI)RVI7;gY78n%BJ_q+P37=&x&04AJ6i#ZJMteCiMi@CSU7wPq0@O z>l}81?j|G8k4+~`xb&7eNpkB!8eAMT1eQKQ7Z)O`Owb?H9(c`o_hRYR{;(SF&QeqU zz1!!W7bfySiY}25V3Gax)Sa)E>Fh9u5`g_!>Tq0WsYlSYUS`5)GY&X*0!mzT8nDJhY@V;@_RZsxO_DWH=5kD#;)7(|F|B>0XAj(c$ccHC{Ct)O(0y14 zL(vUrbNlru+vbrW2-)|!XM*SfJLe2RJcjb~Hhe@Vl&a4$P+FY28J&0(CV#6V@sAHA zQ&O7n=%30;cLOdZ?EOj&i0|rx%LuU0**u_Z98zRANv^J^VIhLOK5u@%ERcfHq%&h< z1^Hc+7`cQ5=^O&}<2Ks(WKd(IM$=lzR&pf=p5Y0UI7S-Llw&phUrY4~e#T{S&kZ`a zTzGYN>-W5(l)lf*?aGSa|hlbQ^-=>ni=!Aqg1n29XgfoG*>&NP9cp+w1t zur{=Qq$ntiB>_Y_==yge_ot*JQx1uV$^hjS8tQZGz$k+}ZYH;P9azkd({9BcCD89QJ4xNTSTjjSh~Imxfl@B^JS6Ey!U_sfGz?ITNmuG({pN&R*8uI8y?EJOz#k z#wZ?wj;udFw%TFXRQ_UZ>PO>Y=i@&n$LhaKjxmKHw!5#$}6!9N7E@9TrAZ+#m~;6*S7=;y9}o>rmUhv_rCx9DuCRk60((MMIy4xqKP zwdk?DsE!Gk<51$5swuTzRq-j`3@;e+5lvK`PI+9OvkVL}xw`z@`Vp$t#a@-^)J&D};`4yuR&9yZ>85o+Ju9+Hugj& zV^!_i2UeKtZ=T0S zu1GXvMoN*8Cu5^QKxV`Sl=@VXZlZK!tA5ME0r9J8S>*Pcw3f2iLb@RSvCHoT36XM0 zM$!Rh3|ZP6jPXY<{P(Ib+KPi7D1?<%7_cwN#x`6ev79U%MPMmI2or9i7&erGC|0tW zcxVk}O`zc*0JTsK62gKFG9fd^$^$btuYjzMPU^LvI+}>!-nH-duZJ4>sP0SNr{UW% zw|_7WPbhax4jutpS7~roQZD88o?<6HNimmSFXmnzh-44}+YeF|2BNiz}ieaH9W^0mx(%mY|;7YU1Lkuw;5=jRr zR|kzz zT~|K0cqbdiH|_J}te*`3U;s8k<{*>Y3neecsrrdb8rD(7;F(%5Ks2NNu5nSn5g|KJ7O zwcPAuzeS^%OQ9B4^Am`}u$IFpuT*=Y4#omRra5PlV^Ao|V1%7#(GDz83+;tKI0cdD zgiZ099%#j@S@|ELb)$j7j{}(5?Ba5QS_);^Uyt!U|J57eD#mnlSZrE!8f`X(u90Co zO+8A^6f8^2Y;!-qv#O~QY4#=X{s%mWHC@vzwUBROhVf-k>n!+K{qfe@B<+qx#dP72 zDe3T&Q0Q9THxVb8tt26Q)!{vhoKP*r{r#T4?rwjcE$^*xnB*o%H?D8qa40F~0Si|s zJ(PSqXusalCuC8{WzN~@-cPtQxApiK+q#e>i8T|Nizp9Nud+# z5>V?QRyTJc46_|$AfILxyO@LXUy;noqf<$xW;N3GUDztnnY~NnX~l~Dw#he1y-P2C z?L_vTB5z#35eklb6Vbm?xXb1yj&HwFdG~s`-Ug#Yh8+PKqYgsX3E9;}&6s z2MM@!7D7eY;Y_dG{%&~n%|GMs=&#=!=z&oOgDMqZmZOzy#uUS?nj_c9gM*wJ zzJM1Z_qQGZ{u9Q&_t5R&j>L+lK4h@K$u6o)a-~uiBr1O|KNJ~NC>=11#VONtz&VTx zPGB@X`h88ZEgsu<5i?a_7OU%oyYW#qOJ}k~mf4f2yh3^_GP=RHT@u?^D0cR%7ykvq zN&w_xCnAVldDO9=>#^mAEC_;D*MZS=T60N#2I_@>_y?hQHXQ62dIf02gwz5UQ`Bzo zKTnk;${CBIzsXP}_#1F6{jm*!q_9DSDj}0gr_QjVbk(nRXfL?*?XI+knkRP~pL^To2czAYXVms`mJ#CDO7bnYbZup& za4u@m(td}0oTbTY6@=s#Me|GmDorz*(AZmjkSV1Qe0#C%AwbqX2nc9 zQWSyVy}iuKmicOs!jTuKA%n1#N6qS4LXgyE&2sAI0x`wgnb{4!#Rlow9D1X=DcLo1 zL;tI>dkl}PYxe{m+qR94&5mu`PKO;=oOEp4wylocvF&u!QO8qx-shY-GxKG>?(3?m zRl90e?R%~D|K0pQ1be9N(^wTaMw>d7BV(VKTjz0XMchiO`0IZwh*l)MDrnb}J!Lnm z{bb|6Wdygz&AEso%Gu91mfKrSut1>MDz#y{zYxDi?Lt2PJyOa(^KfXNAqgEIw6)nf zF~rIn_{e6xUQ3XBZjDeJf3^C4NV45Xs2*Q6qk+v@C`n%L-E3VqWp>dJizaT)JN@no zbC(4DjA9a!voNEfmtN0gj+6DwD%2$s>KFqmskWS&uAEm611eBSPqd{$#Btfh-M76- zI{6^-qC?151ZbgC7uSrDS}KKy0z47R=s07gxtsgDiDug0oGOCI$D}6xR+LM=J@&JK zA2Sq9bE@xShQAp8&n4jyZWkof2Z1=juT_hSdCAb@-e1V> z-Gi4ffOV~#!_=AWp7*``iQ_AS-lykI2Lr&HXZAuw6jR|lpPdiWvZ7woQPHEDXqgN* zGHG5gi*RwCu(Lf#fdZ;jRFY5Jm6qQnRroT>j-P|!$54nj*4EviNY2~MTL_?6r}shV z?Dgtl(*XdK2`Yv7|MvQHJfpCGzp3uMd+<%w_rCf*b>-`sbhb&CVP53LhVk}#c1i8W z*PV0!?2Xji_4Hu)JL>vbd0FUe0{X+QdcJL#b#-mD{Bdbpzz2u-(L{9g;MygWptlf(ku`gR~hDOG4-CsHxCnw10F0k(eO~4)f9e$Rk>O%G58Czm$De;iyH)mx%+2-B70g4F5kJHv_o86KoI@D3f}BuHOc6;0XLV$%poa0m!eB5o z)gn+;n>}+l{wPorWQ;#o@6NJ7c%3tOsJ)rv77d;=&6`k`KcguhnB>QeJNEqi?P8O! zl&P*LDNPQR@0L~)^7S^>(G16RP6HnsBKsf?N=<*wiL2mGGy+7D3_{rr z0uD7yUoc)}6UrP_Ed#ZhlQOQ8YiLFxW@>>>PTm%4ZcpffZfF%=I(QmKQ<~yBAU~v* z&VdFCgmw(Hn#*3AoL-pDtoS&K_UklBlM-(7^QkD%CRmvzb2p+C{nsX-ac-YA;e5$* zv0~{>I6`8yAl^mDA5sCv@0&)gJa~g16iP zZ20HApy>=^wCh>1WCEsnn$irVU4CHU3cOL{>o9PXz=}prl2ZPL<#9uL;}aQ=Sga>M z&}s{QBQYm}(6X!S2GZ#rIIBV`--0&}>>%s%2*K)eJ$5!> zOx807fK#J@U_w1)#O%O;(n>@i?5jux!^A3y1|tkxQ1pSDSP=1PAw#4Tpg9iv)Cdmk zXH9?_0p|~n-maeR`_zsJE=_FBFFteU&nqYXN9VF1zYjBK3;~Z1i^K-K&!zwhHvrrE zAuoge2Sm;lA>sSt?Z^Gm#r*0la3i9jA>hdqq4zi+GOIdd#?bF&@!X*o(A-7#%yX>( zy8py9Zsm-s{I0wCe7!d!i`;`8x=uY1>-Thdq++Nq@N2i`t#wu9(zWO9;Re`t#0b&N zhrea)Q3kukkX}`g)#4-cbijlV{rFq@@b$?P$&z<@A-WDsG>CE&n3^gwMGKBqY4fi% zWrQg~Q{Lf&F*P`paVwXTfv-UtkN)}YAMEygptSjVaRMEvY2n*{xWR1_IF%)v+Hr1j zW~540*iJYuPOWT&cS%6&q!)uALwV}F90ecnHAJ**^a%4^V$V-vizddG(4-%w(onPt zqbQD;wYt19{9I}k;QVR=A3ZYqAD3rbi*+go%) z;L3pt!IfAv%%?(N@vlNqd;J}K>j4H5*kdQ5soRi#%d3=*tX=~|WfWv+ z1zy+Y|C*mP26MQa+5Db#cUPpfjNs@*qk5Qm=mX{aV zN!$bYd;N9WcO$UjX6+$4KnL0M(gHD$IPIu~cr z+4^J8;2r4Y0qlB{?omq35P_FUCeR(w4$=#sFh^{w$$myrb@Ks)v)#RAzc#~}u_vVOcBygI^kU5obvocC~!8`u+G%OqSgf(SP@w%WIk^U=5M;MMI&2>ZY!5VqIu-(2}a7l zpTJCF2Ph~d0mB(%SJIQYs!Od&u%0^?@%X8v&GYk<}!7i- zCcNaF;`o-<(L1xAbM1Xv0((G9V8B1A%my?|>}Q2_d;*Gs_(Gc=vpSY5Vhv`9j^9*C zofabnJ)It0dj|5{+kJ*3{wvh>PrXM(iPE#r1z=cv5=rQ==tocQQM~d-=EO&@*2_mK^!xUT+DEPcP!In&WI&??9x8nOR0M8665vrO zIQ%O+qAu&9;G7`1NqbZVNG{Mc?^9);Kgz3e_dX02jz6&bq!&IEBa>Y2KFq(S0*_t3 z__0Q6oxXpVCw$D5&9q3l>v?md{i6tIdW2asY4+?|e>wslP=p%)?FeK9{^JOQemVla zZ5{8YHb6%}_dkvR7OgNwi#GL;4PIR%TtDl>RZAG=v350UG?{KYS}M8jI0{`-4M-{x zm<=Md3Y2Gf8N^RKuTbSocmd&k+68i|pI_XqiQ*?(ErW%H&(+{Evppb;Q2sY0 zaGy%8H7pGVzI!`WT?-grx1W74NcjsZ5*z=@C@nntc0V2acB-ftJr%uMD+ufNY&LJ+ z^DP$1LQ=$Jx}1U)hth(v9ylY7?Vu4-)r`M7Za2Jc+WvCxB}NFIF&Z_--$7_aYvv_b zR9u>Y1VkgZ)K;*9#*~Uq)U>LM&8+ketpx|~5=cR##qq$ID8Z6&Vb~MEgrj)s!Eli- zKt@`u{wE{Q1Y)vhX5}@RMUG1c-)qL2q!vAXS~&4gU`<)BhkE>{U{yV{p#|snKaPM2 z;%&kH8P_do$xNh3kdHbnG9A?!JQgE(ge`JdV@5{B-f#sd{a%51R=H!`?|&G9?i1iC z^7@6#>=I+p#PFKXZKWb_sm%0g2+#u!0T+}{L*T#_7TBKjPQx8(o_Sw}pTG1R`Z2}h z{rP&=Le898w`wmr+lwL+!_`CKc)mV_cXfG9FZ+Aez}%qpau1DZn?_6CVH?&Bmhv$q zO4QPEiZc?zFMe)Cdc^9ca(>&}O#FW=0oF>OB@lZ0A4{MaLU4}@QZY0x={?O2^NU0o zTu^ng5(mkWVFoRO3UsUuOp0)>MX-|>nhE$sH^RH|rE#kJ-zi2L3es<;ombe-f$@{z zGuFv?rx22q1PzdkX)VjaT2w5v2|U8_=zYS70pu(2HWZ6B93FOGoMex1sT#d<(SJK& z5MDrF_et_g3*m}P|AI|{)XhcrNsmilVo=FImm4R=NMXnk)q=;8CX_2~hE;*i#GOxM z3CkAmcfmuDHc>rEe!Tpqwnw7b5w0V$46Z}vr?g}MyO$xeYMUgZ2d z9NJ$ID`gy;_8ABmmlAK@5d$-HLmVA&vwrgwhcGbvA3$1uAb#d+}yFZ z4aGY}Jgey0%dC7fPwH>bIz_lbt6XqJCAr}tyk%YFNlN0*p5nNhy3OG`$ruax_vfB{ zyfCa$7#S8&yx1!Q*mbu8qZw<+me63u4!QHtSV%Afg~^!cOY$t)H#Zqq33zd#&`y83 zR_6l;ytTl}VK5y}lpJC}CLm)C&&aS!Qv0tW&}H#W_N2jpO(R8n`X4{w&M@^pLxbcB z)eU65QW}aG9Ct)S%ui5U9fLRP8X}!k^iIvC><*)uTc** z=1I-$h!J#S)G(oRgVdTus(Qs#wTiGf7wNRXqUjh`S)y%VmJSq|++MBesuA4}mERY# zSbhwiiYIap>8OZLhdVIHWPRH@uY5U2HgdwIw_2~vQTqgCt{rNxK zfGS3y8!-8g8(=hoP$~JZ8}Kl$b&h#(zgoBzup_PSSRn<*B+wiv4J}XJPAq;beO*&t zH@l+`@IR4t$d5m*rc^?|N>2ypqNS~Iv;Q(Oe8i@*#*q%ZS$@0L{~Mb5YO1Vwz?|D<+ z;7+}c{rr!eH_Kx0D%+RmyIhrT0rnozoT=}tZx^CbXU7X2DP&OXtf}Nsn96ZV&;hrk zvn5ejnRUlFi`<&$tG12bJHpm-MJ{lrX2rlWAe8mXb$ zVFa*El(4i7`TgDw4Zs{zPc1_`PQPRs^;uOezAfxm9#oMzwX1iBx2uNXnki&$nBQxmVwXa}6-KtLiqdHh zW+RbWY7H_Xv}mC|!kWAR!2q)F*iN|+vNU^GDOHVOksG3I<*kW+{ zYn2<{47%H%D_c2KRJ2qTX33~bC33vZ8bgXtDHOHamvQ44ywNb+ z#tX*5#LA&DL}80Gc5`LC#2`{Kp2KYU~9Wjx>_%W37Eo-OG8Gh%~mfzW>*0D0R1>19{`-9n)#Oxm`%l2 zSdKkFxIP$8w5%-;O~hKuBh8oBj_(k~XQydOpLux8G|hCoCteXZa^;Ed@$=34XD@>wwCj*@a8lkOv z?WPWEP448ZljRcrFY?J@uSQ_b{oX<~kE}m)DCzO>YFcIBZ~CUT3P`PUhhd4UW#&9V zE^#~14A|7ZoRt}}Fh5MA;LJpX?9N=ttBiZVV2;7a<9;P?zwcu+4*$6&9iqs|nK?d- zTaQatxVie87UrOw2_yq9849`%H?*~bDJ3l9=fUCORjGJ05t|&AVTX}-hn61&eH<^A z$=)Y@n^NE1-}f7jcMJn{J68k@kAYx7uRu>P`K@<_L5J`xPitxnWHyhc=p=^gQH0i{ zEc|izk{oCmA%q$aSW-To)o!yG5na{L7*w_@E-&>-k!w|I4l(-88!37o*^sPcQfpS~ zm&m%oyu@iTx%502&e@ooIi|i|{;RvWdea}r_)D+#uG$#UvBj46bNeQzSr=U5-A!U* z+ChOnlX$|OTItbTFx*hdt&^VOQ)9z(?K)XxDHdk`yofTB03@ACm#diPgy*M85C!xC z29m;yEy3->weYA%<{A-g-?cgzB8EriR2jD*O;583rNq10)rXye>31kyj0H5Xq|}Xf zV0V}3`$;vZ@nw%~oFcR`_UjG2fcS!L1CS$#NR6R!;9>gI0_xdw#nV{p@9gpzQDuoe zPpud}J%ZTci2^Gh27$#redd(uRdMwtS(i{m78JD=QQ>NKVPgjnRvo;r$+K0B`4+#l zcr_zc2j@(hKF^U8lK|-dIsBpjJ^T!ckvY13qiIuhVuDFe;m{Upg)KoO$Lgp>>gKy# z2rjE`_pR#ddEv{Yh*267C^bngmw=%U#DHK{cBF+2r7F^w!LAtc+7H@?`Pl@s&}AzcXE zn-|ZL{^6&Kuk@EL532_(ZCmjW^xb0zf^$2 z@}j6NpymC1XtDdl_vfPT%iGYK!#`9&G1P^MF{=!a3Q)t9$K&>Wd)VCfyfam>ok_KU z9;+tUFW;XR#^R~$ZUX4w9^};X(wZ4-$Iu?slbCWJ0JjfboYkkHjA5e0hzf^r3NL39 zuy&mk{~_{hpFw~41m8&&RHA~=ge;2IgKS7wHsBTmib}DjK>l@ zmV{XpJr?#Afl4!nBI>~%ZZVDC>CD+0(!I0v;mg(>t7}^+3t<=AYS)ZFh-0KWf^}aL z>Q`ehDUj6PmFxZ5`Zu{Tv~3JStjR~PO<*7qdvoQkLc*~p1PDwjVLD1H^T zdrDYMC5oQkGg;Y;>>R<*u1W6cid1T{J4E{P-%3ENj`#9EN`L%Vg(eU#- zdhcEd_ICHQ!_w61cDh}?tN7iuy3f47Qoc@<+CpK4KJ6Y=dM!r{W~oY>%{t4ywHIDG zNLruxVi;%5umY;$HHZ73r4$aEAk{o!qnz-hf}+teSmgB^*Ntq)RLf@NEY5=@l1loi z_oLzS$YhA9`-gZMMjBA~tiD=^koGT3;#ey#Cs+I0A>{m9Ax zeDU)K^7Dh~MSOHClno+xGlEhXf|7)bFg;FaVO=7b@FAPsvnMWN7(b8qL-*~j=jYpH!;cwp$s3=@ zy4Fl>AO3ROpAbKkAhiOF>SAa694q{j>r9f>qWXF39Z?CE`Cd=yd=^4Ti6Q09r)#9 zLiAi8-vd)UehtA%nEgX(UGi(Q-*Qr8SNn*&mAqw~o}=&hrW4Oxcs8 zSp6S*2a#-6KrJ8|a{F7z=H#$`FwR{?`anklk&&FwtnHzHy&Cu!|NJ0Z(a4(+A9{&!wAu!nGozwy8$GkUFIDCfYv zRX83x-F{~zjt5F&8@~XMzidO`2U`CCy(gZaG>{j zsDPf)LaS(CFb@mVS`CF;U%n-k##g6JwoTcEWbx~=up#VW-(Wxu;9AhbnN~<^AE1n@ zXvA@jlR+z|H2i5&!s2mEyy}|fnASWv0dzAIS_kdKNMuYzl9>HP+|b(=KuvwqR2`MA zaaH_Yb!6%ZN?VG^1gxq^_Gxo)dBZdt)Z>nZ9Q?!QKwKUcP>SgRe~c_@4N2`_!3f`^;8jg6lcC|8hka{#nS{jq-VHK``~4Qa6fU!#mv9Ii zLyHNXnnH$*^au(@xSN;UCm5jr=0g6fHDyPeaakh+cc|@G>+a(V5DeJ*5%jOtYFR5`O0V@k2 zl7eB$8x>Z-43vxS&bqkT?tZGV+e?w9fv~)$b^3=3xUB8xG|$Ny!sL;z*hRN$EN23C zjTps*ykyaF=)TA1>TGX#_ng!u@~it;A9|axJw8`wvN}B4_0_!Tc+YQnxCrX@YrDUk zdUi0b@adRSKeQLPu6)_FdvZM_;=~0W2XX-~L=-e%l7;C=`t{Woho@OYk86@{(VFa> zCQ#W22a;T5z$Dth)m)%3?s1Itv}q~(7s^~ntP)9$qyt+Z6bc~hZHlr;ztSUg1U>9( zl-*M|!ND)CiEq&9h&Wf)+1XDGBL0F}KC;Mif)yBo&hc}tS2$=2jrVwmkEL5Om%S}B z&5A!8D31KjP1~%#f^kvKxRE_g#~=!?JHRu<8=u%CGc0+vM0>*Mi~?q61@oiGOqiDp zWJH6Ws%fafQx8Ef+9Z24T;zwXgJ14%*YWkxz->}nc1_)zRYFiKkeC=*Ne+1$ZlGHPuFuN2g# z!|x3yiS%3^ zZ9W)X;krSSus1wVVik+8?!KX=6^eCL=G?Upyi9Lna(G4c%>Dc20-xCvuhIL3X{fWw za{4P)*Gm%Ix}OcM+=58-#nDTo%cc@ttY}ONjlE~6UJa~F2nA-vz(K@UB@L;K zJmCW9H5EL+;G$HQ;jp3}SV)14hl7No!KaN#$y#Ahd)z(Y-bM)=z9P(} z$V>AJndT_p<>UsC9%`&Sx557eDHZ`K&ja0XhGIllvtD-bg>spm^U|&p0X>u7nJil&uHi5BxM*;|f;XWYJww0A%*t7JhHcX!zrL$+3oR zf$?+f^Z^H|XbVa39MM$|Tx+{sD*b1lR0+tVl(KwrONxYBY*oBFS`xKpw&)|yMP~1n z0T_SEJA;2b4Ri(?CG9eL6TK6UV39`cRuAB|B`H#;_7e&?<0ba|-aWTYx>0?XtP7c< z(yMh$dpXP)ZB8xa?XLZDw?cKo>EM~eJ6goqRBrLMeeX3xfTqC*-}V(|HbU8mPSHOJ ziJXS;r%LX#7qF;*rZTuh?{jHp=cMS~p4+Ij=@0rm1GEZw){|zY}wXXV`IQ8;} z{v9om$1TCXej54@R9^D;O=wx8vj;B4sGS0`P?6giOd->u(u>rw+|>vnN;wsc*D$P) z_Pvjw+Q0j)83f2@j+N3kyG6gH)18(c?c~B|c9iLG8iK?R+3(9d65MrHp}AUnrp-f_ z`wZecE@s}Wx9zlv5nG;~qnbW#fYt34m{NYsraH!T^#1D53j>sih{XY{PBT1lykl@3 zFl#fhg?2$Hf;=QA5>g8gxYD>^M9y1LA8YZRXd6y+1nljfEP&oWEC5*8sY#rwsl{*g z7OSsB+AH%MHpkg{=S-#8Db>|kTeQ2TA#eZQVJAED!}6#6iGy&HY3nRH@w(5)s>{Lg zF02B(*5FHU751eAcw(FS^IQb&!-xliK1}!B-4NIJHiMZYk`=J%2Cq9M^SLxz@3==Zg6zH^=v=XQ`nY&p5-nOeW49`L`zwy0cXQ*` zKJHC+=TIlB4Z`Qy1Dcv2iLBJq>qedAJc|i~irm$`>$8^0nG2anM$WrWx~a_VWNqK$ zWI8@Q0Ozmc;rERxP`a_(-t||H*T7}Zr*3Rm-`XZ4MsNPdg9yVY*5tL&k*XW{+!r7R zpz&WEfP?sx;3Rupda`1AzMcjuQ#G1r*HWm=2{uLW4s| z&7`okaSV@2B!wbD`K1TAhM_?yvO+$vkE)#_Ax!=jL_mKA8+^dXSD6Y@NEL{Yleils zGE0(g5dMgh&CvedXKC>)hZXGMpBg|xdmi!^nN>HwT|?7&dd!$jsv!fvqPz3kLSRBENopm6nQmY}owJs73r;YpYo+`%7`5Z{pc@I4@Q@-8} z;8hMjob=R*HD%%)U;q6(gnRB(bqGCe2p}LZFpFFi-r59Mc-*ijJc&jPU`71E@jg03 zV1t9lfMts*9)sa2V>z}CO+-rpu9z<2`;ELj~)^uYc*r=SNN1;cre z%bZLx;2H=NND^n+7RE$aBRF(#iWC>ylxGZ^$(ChdVO_R4d(r52dGRJcheB`tIy`A{vIMx zwygXFY&C95a^f8+{&GpKQxF+!2l{Lov;vRH+E=tK4T89hf<$bCQ{&#C3?+D`KOihd z2zb8LOx}$U%pF(=TX34nKoteAS;z&x&a+tZT8tGp3RYE(hu000{qT1}0!Jil&GI^e zl27#GJ@SJX;YYW6MFov;PlHU(x^U5s?!73I)31f_#B!14V0xsLkIy; znY*O~65-X!0nC^+WaPG_f#MYN(u+ zVkuPl5#-nMboGpOwGQq5;ds88c|YH8m0RPgZa>GP`Ka6ZDFS37?N9wH0yI*)07ZbY ztOO&d#oRNX2(Xrgh{6U?0AJduBGFt5tn&9RMGVO+7zH^afsptL#b1bVK8|bg_DKTR z^`g*gfe<-qFjERo$73c9>5!K?2iFD5$!I(2u+mD36dx##%Y4rg^2D(5STgGw18%BX zb-CYnq2xh?yXj181!@3Y#*Qr%ngzRre4D!FKHUqGIZU*+sWtkn&G8*cZ8_|U( z3FIYgwtdF1dFG;yaO_rhbRh zUEw)sl35guul=JAw~ID0pHLG~9HX-6$PsHJQ616X>%ISgTlg|Kr^Hzm^laAqT9S8b zEacd(O#58Pe(7Ttl5WTjDyn~!kw2VN01_yM5eB5)b&N^?;(*Y};K8xkQ+rmX4X(F>@_zNa-AXOxDv>3Z-^*hO{aBdq&C@&`9er zlhaFUo9yv!VP9~0C$FtcCT49ILy*Hx?BPYPpB%cS1WGYL197C|zk?yr7pij5#%2#K zZa3>D!){G?PCQ=(1wmyh4G%(LaGdd(%}+RLRPZ_rwUtB_hk@Cr7tVL>!%+lJ<&aTZ z<5Q;8qGfYo%STxa&-9E=S$IguuW{06vNq)Q2Fk@Xn&Tm6QVB&hsf`6-xYR+|C18Wz z2&9sIsKiU2lpIPiwEj-TDDGj>-ae5-r^*3(Avt)MS|B9Kbj*xm)A4Xg;_FU5MFI7} zuF~3`CZ`FZYP72f4JP6Zl(yxe)UrpJM%H7%pI_o^dShJu+^WNbyXpvsDa9R`?Ja|k zyCjBk!}FPBV1o~`7QfX;jo_R1gi;pv1jM^*pedU)~ibGu? z*6ri3Laf#pw3T0I*I|a>T%^J(ZOWBM*o6{o85Vu8C(~pz>|hxk9hQ76(xjT8R}@M2 z{cQnrRB1Gi2e+`rD>I23CohjPf|ZLetJurSz3;oea(lt%YC94u^`-mfGK&|y25yEQ z9q(`0Eec*;fcw|mD}%1*y{Yx;7D0Ug%*=xAdS{{OT+qwkt?lhHw#%-nuJI z;#y+hcAZ#XK+<#d@;y~cWM}qh<;VNXhfej2$Wm_GySK;vp0Byjcfh&kz57B3yw2md zxBXfm3~=)EoH)1J>*Zl;-H@pH_iv1sqtivh4MDx<)cE>EdQp7eCF{J->(ZC?AK!bV zm)A+YT)q0eIxA_SM1F^?j~YLOsiVwp%?)TPs>u@~#@%76Jp9U$9AHrtb8B24%Zq1Q zN0|XmDc=|8MsPB&o5!Gn;Ut}oLaj(x+VHBf-~{|XEZ_eO!Jl#0VoMdqOsbYf5BRTl zXirKbBiunb_9wLz1&){2y_Cq7rS6eVI*E`Dql~P%h#tJ$$poHn8q_#-D+MUzqb)BGV8#)&{Px@y$F8;%#_w?n9j03)p%*{_3_Q zE7c+pvOWdwu<=B2`w93h1l(bh|HhzOvbp@N7-!pei9n(d(w;AYmJcYKmo$T4^eJWQ zTaoQ61Yvo`gsXHiE1H!br5a5!K{x-loXCzT-RdAa8uvs5C6xgkPlp)pgj2Gl8JyM` z!Ap-81!>_MChwk<%m;?QgG9vrS=8Y_(EP(S@s0y0aouibtja(wS(WX|zGFDX`Y@+a z2``NRG?mZ_*=q^@ghNeql^WYf$Ecb;5`Pnf&vAPB`gPtN)IselS@;0E*g={p4bO+& z?O^TI2bQZ3V4sWSz479B?Yt7C^u@9{h%nJQ5tsTk)?a)(T@hV3zYzX1^Z4rhzLTrh z@A2Y%CTM)C>us}jo8K>e0Zud1rMY=qN7kKd?RmRuSo?4N=0)txwy*y7Br^kB8`1Ct z7qG~Ae?A8;k-S}!uyD&1tzUe7Bj4_s5~}e&4TP`cKQ2+xx*v)}K-J&>x~FuC?mPpf*Y=40!y_ zxJ_ez7lBTaum3H^I&3QdP_0GEjY{Xq4L-Ely!CKu?ztzGdpghU($x<=#jrJ=)VMId zEJE_HXx@6pIcnP99E@1g`=%M49@?AA8-dU%1W?l|OSw}PYYN6lm>iocBDi-YS=(V( zY4QJwPVkF;?pG6WwoHQaYnjLEXsOo}WGsT25gn~=Np?xAN|Yic#}5{Fy7D@E9aU-Q z&@(wo%#>s9BKv~>xnRg++Z>LSD%XHCR6ywW%NZIdWAnIH zvR_8+n!XAQ((K7*HMLimm^69ltl{}afh;A0m4;W+#EY@NeQ}c|GO*DAn&Ap|0Z#I| z*5&EGuHiDe+Q!&j~-Q8<`@|LY3lw!-wGFZlkJlT*M4c!t(ve z87y)jTxoa|_ps?cW@jj_?1Z)rnpH<>6~yvO<~8+uU<2us@k@Dqt@XXHOqTY0ZSB4R-YG@; z>Y8JTQ(=(lApgQM4lhzdbE?*Mj#upqP=nQ+;S6w7>-d8(TdHPvLMdRknPaouF1w)@)x9+ z+Za_;p?DXv;NnU6m-m-2BMfwkfTM(foixT-98VH@o2GdMw6YnhwH!EDS@CuFJ_=vK zQ3PS05vxvyBRt4R(;~_Ry7BROl}hdux?(wHjjDatp+vQx^cc7DjazU-g7rj(Su{~& z;Fy{DL*Y;N>#A8mDu8-ir>K$J*ee2^XDUwgB!N~k)mi+DWGcVt`Oi9kPO-&OGif6* zNT{6U5h_`mtWtY}*(@@BV8XcCbxck%70X{b>B|&ol1q{7?W$GZ6!@&hJ?yY>^bVq++XIc`_;Jmii(EmVZX=I&|$}tVRuEND(PYKn9-Ds#ljqLAs5JgK-5N=`}VSu zqNE^YzgSO89WjxokX@k&(%D%H7!*HMcOh@d{8 zF^9K)Oo1e^Mb*KZ)-;nCLt#+BP0TCDF^j^Xg$zYtQ%qh54o1y0w38S+!=cewBE!UB zQJj5)aD4mO=PcI3%y;pv*Vl)~{K|yCxe~X_1P-n z`z%isMp;<7H(5Ejgb^al?WFQ(mr3-|qhwV!k#q(eBw~UqOvo6`XThXWbP6oyfH`t_ zMDE}$V@ZBz5HieBhJGTeQOYz5 z%gfU4E0rXPNNDHBz~l0zho-U|Wg2vwM^VEG_JqYd!NXAhe|5Q#?qE7VV`dEg3i zk4j;zY@Tk`$9D{(ey-9Z*{ABj$hKu09^yB3D3Rjg*%~ zE}tb9t z3ez3=|45#X@s`2OY9S+s6iM1;#Z33qOnJ{#Wy4CnlT}T$mYhzRn(06|e;S^xk5 literal 0 HcmV?d00001 diff --git a/testing/integration/logs/archive/correx-2026-06-30-1.log.gz b/testing/integration/logs/archive/correx-2026-06-30-1.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..47dc695afc0682538adc196e357d36c02376372c GIT binary patch literal 11904 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 BM#%sG literal 0 HcmV?d00001 diff --git a/testing/integration/logs/archive/correx-2026-07-01-1.log.gz b/testing/integration/logs/archive/correx-2026-07-01-1.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..ef8a299c163c52a3887ba9ec7d8eb15950f711fe GIT binary patch literal 18903 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