# review-009

## result
- changes_required

## findings

### `RouterReducer` interface and `DefaultRouterReducer` implementation
- Interface `RouterReducer` with `reduce(state: RouterState, event: StoredEvent): RouterState` matches the core architecture convention.
- `DefaultRouterReducer` correctly implements all 8 event handlers specified in the task:
  - `SteeringNoteAddedEvent` → appends `RouterTurn` with `role = TurnRole.USER` to `conversationHistory`, using `payload.content` and `event.metadata.timestamp`.
  - `WorkflowStartedEvent` → sets `workflowStatus = RUNNING` and `currentStageId = payload.startStageId`.
  - `WorkflowCompletedEvent` → sets `workflowStatus = COMPLETED`, clears `currentStageId` (`null`).
  - `WorkflowFailedEvent` → sets `workflowStatus = FAILED`, clears `currentStageId` (`null`).
  - `OrchestrationPausedEvent` → sets `workflowStatus = PAUSED`.
  - `OrchestrationResumedEvent` → sets `workflowStatus = RUNNING`.
  - `StageCompletedEvent` → appends `RouterL2Entry` with `StageOutcomeKind.SUCCESS` to `l2Memory`.
  - `StageFailedEvent` → appends `RouterL2Entry` with `StageOutcomeKind.FAILURE` to `l2Memory`, clears `currentStageId` (`null`).
- All handlers use `state.copy(...)` — state is never mutated in place.
- Unknown event types return unchanged state via `else -> state` catch-all branch. ✅

### Payload field access verification
- `SteeringNoteAddedEvent.payload.content` — ✅ correct (event has `content: String`).
- `WorkflowStartedEvent.payload.startStageId` — ✅ correct (event has `startStageId: StageId`).
- `StageCompletedEvent.payload.stageId` — ✅ correct (event has `stageId: StageId`).
- `StageFailedEvent.payload.stageId` and `payload.reason` — ✅ correct (event has `stageId: StageId` and `reason: String`).

### Architecture pattern compliance
- Interface + `Default*` naming convention matches other reducers in the codebase (e.g., `DefaultOrchestrationReducer`).
- Timestamps come from `event.metadata.timestamp` — preserves original event ordering per task notes.
- Handler methods that don't need payload data take only `state` parameter — avoids detekt `UnusedParameter` warnings (documented in task notes).

### Build & static analysis
- `:core:router:compileKotlin` — succeeds.
- `:core:router:detekt` — clean.
- **`./gradlew :core:router:check` FAILS** — `koverVerify` reports 0% coverage, expecting 70%. No tests exist for `DefaultRouterReducer` yet. This is expected since `RouterReducerTest` (task-020) is a separate downstream task, but it blocks the build gate.

### No contradictions detected
- No contradictions between spec, plan task-009, and the executed task-009.

## missing requirements
- **No tests for `DefaultRouterReducer`**: The task done-when criterion states "correctly updates `RouterState` fields for every known event type and returns unchanged state for unknown events." Verification is currently code-only review — no tests exist. Per the epic plan, `RouterReducerTest` (task-020) and its dependency `testing/deterministic/build.gradle` (task-019) are separate tasks, so this is an intentional deferment. However, the `:core:router:check` build fails until tests are added, which blocks CI.
- **`StageStartedEvent` not handled**: The task only lists the events it should handle. The pre-existing `StageStartedEvent` (from `StageEvents.kt`) is correctly omitted from this reducer's scope — it belongs to the orchestration reducer. No issue here, just noted.

## edge cases
- **L2 memory summary strings are hardcoded**: `handleStageCompleted` uses `"Stage $payload.stageId completed"` and `handleStageFailed` uses `"Stage $payload.stageId failed: ${payload.reason}"` as summaries. These are plain text summaries suitable for L2 memory context packing (the spec says L2 entries are stage summaries). The summaries are deterministic and derive only from event payloads — no issue.
- **`currentStageId` cleared on `StageFailedEvent` but not on `StageCompletedEvent`**: This is intentional per the task spec. `StageCompletedEvent` advances the workflow to the next stage (set by `WorkflowStartedEvent` or `TransitionExecutedEvent`), so clearing `currentStageId` on success would be incorrect. The reducer correctly only clears on failure.
- **`workflowStatus` on `StageFailedEvent`**: The task spec does not require changing `workflowStatus` on `StageFailedEvent` — only `currentStageId` is cleared and an L2 entry appended. The workflow may have other stages remaining. This is correct.

## suggested follow-up
1. **Implement `RouterReducerTest` (task-020) promptly** — this is the critical blocker. The reducer is straightforward and has clear expected outcomes per event, making it low-effort to test. At minimum, test: state transition from each event type, unknown event pass-through, and `StageFailedEvent` clearing `currentStageId`.
2. **Consider adding a test for `else -> state` (unknown event passthrough)** — this ensures future additions to the event system don't accidentally mutate router state.
