feat(router): enrich L2 decision points with steering and failure reason
Fix broken stage-summary interpolation ($payload.stageId rendered the event's toString()). RouterL2Entry gains reason + steeringNotes; the reducer folds SteeringNoteAddedEvent into pendingSteeringNotes and harvests them into the completing stage's L2 entry, then clears (adr-0003 §4, pure field extraction, zero inference). RouterContextBuilder renders summary + reason + steering.
This commit is contained in:
@@ -147,13 +147,13 @@ class RouterProjectorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apply(SteeringNoteAddedEvent) leaves state unchanged`() {
|
||||
fun `apply(SteeringNoteAddedEvent) accumulates the note into pendingSteeringNotes`() {
|
||||
val state = projector.initial()
|
||||
val updated = projector.apply(
|
||||
state,
|
||||
stored(payload = SteeringNoteAddedEvent(sessionId, "do something different")),
|
||||
)
|
||||
assertEquals(state, updated)
|
||||
assertEquals(listOf("do something different"), updated.pendingSteeringNotes)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -155,7 +155,7 @@ class RouterReducerTest {
|
||||
val entry = updated.l2Memory[0]
|
||||
assertEquals(stageId, entry.stageId)
|
||||
assertEquals(StageOutcomeKind.FAILURE, entry.outcome)
|
||||
assertTrue(entry.summary.contains("timeout"))
|
||||
assertEquals("timeout", entry.reason)
|
||||
assertNull(updated.currentStageId)
|
||||
}
|
||||
|
||||
@@ -175,18 +175,18 @@ class RouterReducerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SteeringNoteAddedEvent does not change state`() {
|
||||
fun `SteeringNoteAddedEvent accumulates into pendingSteeringNotes without touching conversation`() {
|
||||
val state = reducer.initial
|
||||
val updated = reducer.reduce(
|
||||
state,
|
||||
stored(payload = SteeringNoteAddedEvent(sessionId, "do something different")),
|
||||
)
|
||||
assertEquals(state, updated)
|
||||
assertEquals(listOf("do something different"), updated.pendingSteeringNotes)
|
||||
assertTrue(updated.conversationHistory.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SteeringNoteAddedEvent on non-initial state does not change state`() {
|
||||
fun `SteeringNoteAddedEvent preserves workflow status and current stage`() {
|
||||
val initial = reducer.initial
|
||||
val started = reducer.reduce(
|
||||
initial,
|
||||
@@ -196,11 +196,22 @@ class RouterReducerTest {
|
||||
started,
|
||||
stored(payload = SteeringNoteAddedEvent(sessionId, "shift focus")),
|
||||
)
|
||||
assertEquals(started, withNote)
|
||||
assertEquals(listOf("shift focus"), withNote.pendingSteeringNotes)
|
||||
assertEquals(WorkflowStatus.RUNNING, withNote.workflowStatus)
|
||||
assertEquals(stageId, withNote.currentStageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `steering notes are harvested into the completing stage's L2 entry and then cleared`() {
|
||||
val initial = reducer.initial
|
||||
val started = reducer.reduce(initial, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
|
||||
val steered = reducer.reduce(started, stored(payload = SteeringNoteAddedEvent(sessionId, "call it the runtime")))
|
||||
val completed = reducer.reduce(steered, stored(payload = StageCompletedEvent(sessionId, stageId, StageId("t1"))))
|
||||
|
||||
assertEquals(listOf("call it the runtime"), completed.l2Memory[0].steeringNotes)
|
||||
assertTrue(completed.pendingSteeringNotes.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown event type returns unchanged state`() {
|
||||
val state = reducer.initial
|
||||
|
||||
Reference in New Issue
Block a user