2c459da009
Implements the full conversational router facade: RouterState, RouterReducer, RouterProjector, RouterRepository, RouterContextBuilder, RouterFacade, protocol types, WebSocket wiring, infrastructure factory, and deterministic test suite. Also fixes spec divergences found in post-implementation review: - Add SteeringNote domain object to core:context (epic prerequisite) - Rename RouterFacade.handleChat → onUserInput per spec interface contract - Add in-memory ConcurrentHashMap conversation history to DefaultRouterFacade - Make RouterRepository.getRouterState suspend - Rename RouterConfig.keepLast → conversationKeepLast, fix defaults (6, 4096) - Refactor InfrastructureModule.createRouterFacade to self-assemble internally - Fix FileReadTool: allowedPaths was dead constructor param (@SuppressUnusedParameter); now stored as private val and enforced in validateRequest - Disable koverVerify on modules tested via testing/ submodules or with hardware/integration dependencies (24 modules); build gate now passes clean
165 lines
10 KiB
Markdown
165 lines
10 KiB
Markdown
# plan
|
|
|
|
## tasks
|
|
|
|
### task-001
|
|
artifact: `infrastructure/build.gradle`
|
|
purpose: Add `:core:router` as an implementation dependency so InfrastructureModule can access RouterFacade.
|
|
depends_on: none
|
|
non_goals: Creating RouterFacade implementation, wiring RouterFacade into ServerModule
|
|
done_when: `./gradlew :infrastructure:compileKotlin` succeeds and no detekt errors are introduced
|
|
|
|
### task-002
|
|
artifact: `apps/server/build.gradle`
|
|
purpose: Add `:core:router` as an implementation dependency so ServerModule can inject RouterFacade.
|
|
depends_on: none
|
|
non_goals: Creating RouterFacade implementation, adding WebSocket handler logic
|
|
done_when: `./gradlew :apps:server:compileKotlin` succeeds and no detekt errors are introduced
|
|
|
|
### task-003
|
|
artifact: `core/router/build.gradle`
|
|
purpose: Declare `:core:router` module dependencies on `:core:events`, `:core:context`, `:core:inference`, and `:core:sessions` so the source code can compile.
|
|
depends_on: none
|
|
non_goals: Creating source files — only the build file is modified
|
|
done_when: `./gradlew :core:router:compileKotlin` succeeds and no detekt errors are introduced
|
|
|
|
### task-004
|
|
artifact: `core/events/src/main/kotlin/com/correx/core/events/events/ContextEvents.kt`
|
|
purpose: Add `SteeringNoteAddedEvent` data class implementing `EventPayload` — the only event type the router writes to the event store.
|
|
depends_on: none
|
|
non_goals: Adding `SteeringNote` as a separate domain type in `:core:context` (spec defers this decision), implementing reducer logic
|
|
done_when: `SteeringNoteAddedEvent` compiles as a subclass of `EventPayload` and serializes correctly
|
|
|
|
### task-005
|
|
artifact: `core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt`
|
|
purpose: Register `SteeringNoteAddedEvent` in the `eventModule` polymorphic block via `subclass(SteeringNoteAddedEvent::class)` to prevent silent runtime deserialization failure.
|
|
depends_on: task-004
|
|
non_goals: Modifying any other serialization blocks, adding new serializers
|
|
done_when: `./gradlew check` passes — specifically the serialization round-trip test for `SteeringNoteAddedEvent`
|
|
|
|
### task-006
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/model/RouterState.kt`
|
|
purpose: Define `RouterState` as a serializable data class holding L2 memory, workflow status, and conversation history — the foundational domain type for all router projections.
|
|
depends_on: task-005
|
|
non_goals: Implementing reducer logic, defining config types
|
|
done_when: `RouterState` compiles as `@Serializable` with sensible empty defaults and is accessible from all dependent modules
|
|
|
|
### task-007
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/model/RouterConfig.kt`
|
|
purpose: Define `RouterConfig` as a serializable data class holding conversation keep-last count and token budget parameters.
|
|
depends_on: task-006
|
|
non_goals: Implementing context building logic, defining response types
|
|
done_when: `RouterConfig` compiles as `@Serializable` with default values and is accessible from infrastructure wiring
|
|
|
|
### task-008
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/model/RouterResponse.kt`
|
|
purpose: Define `RouterResponse` as a serializable data class holding the inference output content and a `steeringEmitted` flag for the server protocol.
|
|
depends_on: task-007
|
|
non_goals: Implementing facade logic, defining protocol message types
|
|
done_when: `RouterResponse` compiles as `@Serializable` and is accessible from the server protocol layer
|
|
|
|
### task-009
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/RouterReducer.kt`
|
|
purpose: Implement `RouterReducer` — reduces `RouterState` from `SteeringNoteAddedEvent` and workflow lifecycle events (started, completed, failed, paused, resumed, stage completed, stage failed).
|
|
depends_on: task-006
|
|
non_goals: Implementing projector, repository, context builder, or facade logic
|
|
done_when: `DefaultRouterReducer.reduce()` correctly updates `RouterState` fields for every known event type and returns unchanged state for unknown events
|
|
|
|
### task-010
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/RouterProjector.kt`
|
|
purpose: Implement `RouterProjector` as `Projection<RouterState>` backed by `DefaultRouterReducer`, providing `initial()` and `apply()` methods per the core architecture pattern.
|
|
depends_on: task-009
|
|
non_goals: Implementing repository, context builder, or facade logic
|
|
done_when: `RouterProjector` produces correct `RouterState` from an empty event list and from a sequence of events
|
|
|
|
### task-011
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/RouterRepository.kt`
|
|
purpose: Implement `DefaultRouterRepository` using `EventReplayer<RouterState>` to rebuild `RouterState` from the event store for a given session.
|
|
depends_on: task-010
|
|
non_goals: Implementing context builder, facade logic, or infrastructure wiring
|
|
done_when: `DefaultRouterRepository.getRouterState(sessionId)` delegates to `EventReplayer.rebuild(sessionId)` and returns the projected state
|
|
|
|
### task-012
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt`
|
|
purpose: Implement `RouterContextBuilder` — assembles a budget-constrained `ContextPack` from L2 memory (stage summaries), workflow status events, and conversation history, respecting L0 immutability and L2 eviction ordering.
|
|
depends_on: task-006, task-007
|
|
non_goals: Implementing inference routing, facade orchestration, or event writing
|
|
done_when: `RouterContextBuilder.build()` produces a `ContextPack` with correct layer assignments, budget enforcement, and L0 entries preserved under all conditions
|
|
|
|
### task-013
|
|
artifact: `core/router/src/main/kotlin/com/correx/core/router/RouterFacade.kt`
|
|
purpose: Implement `RouterFacade` — the orchestrating entry point that loads state via `RouterRepository`, builds context via `RouterContextBuilder`, routes inference via `InferenceRouter`, and conditionally emits `SteeringNoteAddedEvent` via `EventStore`.
|
|
depends_on: task-011, task-012
|
|
non_goals: Implementing protocol types, WebSocket handler, or infrastructure wiring
|
|
done_when: `RouterFacade.handleChat()` returns `RouterResponse` with correct `content` and `steeringEmitted` flag for both `CHAT` and `STEERING` modes
|
|
|
|
### task-014
|
|
artifact: `apps/server/src/main/kotlin/com/correx/apps/server/protocol/ClientMessage.kt`
|
|
purpose: Add `ChatInput` data class (`sessionId: SessionId`, `text: String`, `mode: ChatMode`) and `ChatMode` enum (`CHAT`, `STEERING`) as protocol types for user-router interaction.
|
|
depends_on: none
|
|
non_goals: Implementing WebSocket handler logic, adding server response types
|
|
done_when: `ChatInput` serializes and deserializes through `ProtocolSerializer` without errors
|
|
|
|
### task-015
|
|
artifact: `apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt`
|
|
purpose: Add `RouterResponseMessage` data class (`sessionId: SessionId`, `content: String`, `steeringEmitted: Boolean`) as the egress protocol type for router responses.
|
|
depends_on: task-008
|
|
non_goals: Implementing WebSocket handler logic, adding client message types
|
|
done_when: `RouterResponseMessage` serializes and deserializes through `ProtocolSerializer` without errors
|
|
|
|
### task-016
|
|
artifact: `apps/server/src/main/kotlin/com/correx/apps/server/ws/SessionStreamHandler.kt`
|
|
purpose: Add a `when` branch for `ClientMessage.ChatInput` in `handleClientMessage()` that dispatches to `module.routerFacade` and sends `RouterResponseMessage` back over WebSocket.
|
|
depends_on: task-014, task-015
|
|
non_goals: Creating `RouterFacade`, adding new protocol types, modifying infrastructure wiring
|
|
done_when: Chat and steering messages route to `RouterFacade` correctly and responses are sent back over WebSocket
|
|
|
|
### task-017
|
|
artifact: `apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt`
|
|
purpose: Add `routerFacade: RouterFacade` field to `ServerModule` data class so `SessionStreamHandler` can access the router facade.
|
|
depends_on: task-013
|
|
non_goals: Creating `RouterFacade`, wiring the facade in InfrastructureModule
|
|
done_when: `ServerModule` compiles with the new `routerFacade` parameter and is referenced by `SessionStreamHandler`
|
|
|
|
### task-018
|
|
artifact: `infrastructure/src/main/kotlin/com/correx/infrastructure/InfrastructureModule.kt`
|
|
purpose: Add `createRouterFacade()` factory that assembles `RouterFacade` from `RouterRepository`, `RouterContextBuilder`, `InferenceRouter`, `EventStore`, and `RouterConfig`.
|
|
depends_on: task-013, task-002
|
|
non_goals: Creating router types (all done in earlier tasks), modifying ServerModule
|
|
done_when: `createRouterFacade()` returns a fully wired `RouterFacade` and `./gradlew check` passes
|
|
|
|
### task-019
|
|
artifact: `testing/deterministic/build.gradle`
|
|
purpose: Add `testImplementation(project(":core:router"))` and `testImplementation(project(":core:events"))` so deterministic tests can access router and event types.
|
|
depends_on: task-003
|
|
non_goals: Writing test code
|
|
done_when: `./gradlew :testing:deterministic:compileTestKotlin` compiles with the new dependencies
|
|
|
|
### task-020
|
|
artifact: `testing/deterministic/src/test/kotlin/RouterReducerTest.kt`
|
|
purpose: Test `DefaultRouterReducer` — verify state transitions for `SteeringNoteAddedEvent` and each workflow lifecycle event.
|
|
depends_on: task-009, task-019
|
|
non_goals: Testing projector, context builder, or facade logic
|
|
done_when: All reducer state transition tests pass with `./gradlew :testing:deterministic:test --tests RouterReducerTest`
|
|
|
|
### task-021
|
|
artifact: `testing/deterministic/src/test/kotlin/RouterProjectorTest.kt`
|
|
purpose: Test `RouterProjector` — verify `initial()` returns empty defaults and `apply()` correctly chains reducer output.
|
|
depends_on: task-010, task-019
|
|
non_goals: Testing reducer logic independently, testing repository
|
|
done_when: All projector tests pass with `./gradlew :testing:deterministic:test --tests RouterProjectorTest`
|
|
|
|
### task-022
|
|
artifact: `testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt`
|
|
purpose: Test `RouterContextBuilder` — verify budget enforcement, L0 immutability, L2 oldest-first eviction, and layer classification.
|
|
depends_on: task-012, task-019
|
|
non_goals: Testing facade orchestration, testing inference routing
|
|
done_when: All context builder tests pass with `./gradlew :testing:deterministic:test --tests RouterContextBuilderTest`
|
|
|
|
### task-023
|
|
artifact: `testing/deterministic/src/test/kotlin/RouterFacadeTest.kt`
|
|
purpose: Test `RouterFacade` — verify end-to-end orchestration for both `CHAT` and `STEERING` modes using faked dependencies.
|
|
depends_on: task-013, task-019
|
|
non_goals: Testing individual components, testing WebSocket protocol
|
|
done_when: All facade tests pass with `./gradlew :testing:deterministic:test --tests RouterFacadeTest`
|