Files
correx/docs/specs/2026-05-20-router/plan.md
T
kami 2c459da009 feat(router): implement Epic 14 — core:router module
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
2026-05-21 15:06:20 +04:00

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`