feat(kernel): label reviewer artifacts as critic feedback on back-edge re-entry

This commit is contained in:
2026-06-10 21:56:05 +04:00
parent 629b910e7f
commit beed501d60
3 changed files with 88 additions and 4 deletions
@@ -1,11 +1,20 @@
import com.correx.core.artifacts.kind.FileWrittenKind
import com.correx.core.artifacts.kind.TypedArtifactSlot
import com.correx.core.context.model.ContextLayer
import com.correx.core.context.model.EntryRole
import com.correx.core.events.events.RefinementIterationEvent
import com.correx.core.events.events.RetryAttemptedEvent
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.TransitionId
import com.correx.core.kernel.orchestration.buildProjectProfileEntry
import com.correx.core.kernel.orchestration.buildRetryFeedbackEntry
import com.correx.core.kernel.orchestration.criticArtifactIds
import com.correx.core.sessions.BoundProjectProfile
import com.correx.core.transitions.graph.StageConfig
import com.correx.core.transitions.graph.TransitionEdge
import com.correx.core.transitions.graph.WorkflowGraph
import com.correx.testing.fixtures.EventFixtures.stored
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
@@ -72,4 +81,49 @@ class ContextFeedbackTest {
assertTrue(!entry.content.contains("### Commands"), "should not contain Commands: ${entry.content}")
assertEquals("projectProfile", entry.sourceType)
}
@Test
fun `criticArtifactIds resolves the from-stage produces on a back-edge`() {
val graph = graphWith(
"implement" to producesSlots("patch"),
"review" to producesSlots("review_report"),
)
val events = listOf(
stored(payload = RefinementIterationEvent(SessionId("s"), "review->implement", 1, 3)),
)
assertEquals(setOf(ArtifactId("review_report")), criticArtifactIds(events, graph, StageId("implement")))
}
@Test
fun `no refinement events yields empty set`() {
assertTrue(criticArtifactIds(emptyList(), graphWith(), StageId("implement")).isEmpty())
}
@Test
fun `criticArtifactIds ignores events targeting a different stage`() {
val graph = graphWith(
"implement" to producesSlots("patch"),
"review" to producesSlots("review_report"),
)
val events = listOf(
stored(payload = RefinementIterationEvent(SessionId("s"), "review->other", 1, 3)),
)
assertTrue(criticArtifactIds(events, graph, StageId("implement")).isEmpty())
}
}
private fun producesSlots(vararg names: String): List<TypedArtifactSlot> =
names.map { TypedArtifactSlot(name = ArtifactId(it), kind = FileWrittenKind) }
private fun graphWith(vararg stages: Pair<String, List<TypedArtifactSlot>>): WorkflowGraph {
val stageMap = stages.associate { (id, slots) ->
StageId(id) to StageConfig(produces = slots)
}.toMutableMap()
if (stageMap.isEmpty()) stageMap[StageId("start")] = StageConfig()
return WorkflowGraph(
id = "test-graph",
stages = stageMap,
transitions = emptySet(),
start = stageMap.keys.first(),
)
}