fix(kernel): complete artifact lifecycle and fix transition artifact state lookup

Two issues causing "no transition condition matched":

1. emitToolArtifacts only emitted ArtifactCreatedEvent; the artifact_validated
   transition condition requires VALIDATED phase, which needs the full lifecycle
   (Created → Validating → Validated). Tool success is sufficient grounds for
   auto-validation, so emit all three events.

2. DefaultSessionOrchestrator was building stageArtifacts from LiveArtifactRepository
   which uses a hot async subscription — cache was always empty at lookup time.
   Now reads ArtifactValidatedEvents directly from the synchronous EventStore.read().
This commit is contained in:
2026-05-19 15:48:32 +04:00
parent f149eff5bc
commit bc755572bd
2 changed files with 19 additions and 14 deletions
@@ -2,6 +2,10 @@ package com.correx.core.kernel.orchestration
import com.correx.core.approvals.model.ApprovalDecision import com.correx.core.approvals.model.ApprovalDecision
import com.correx.core.artifactstore.ArtifactStore import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.artifacts.ArtifactState
import com.correx.core.events.events.ArtifactValidatedEvent
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.ArtifactLifecyclePhase
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import com.correx.core.events.orchestration.OrchestrationState import com.correx.core.events.orchestration.OrchestrationState
import com.correx.core.events.types.ApprovalRequestId import com.correx.core.events.types.ApprovalRequestId
@@ -67,11 +71,16 @@ class DefaultSessionOrchestrator(
) )
val stageConfig = enriched.graph.stages[enriched.currentStageId] val stageConfig = enriched.graph.stages[enriched.currentStageId]
val stageArtifacts = stageConfig?.produces val targetIds: Set<ArtifactId> = stageConfig?.produces?.map { it.name }?.toSet() ?: emptySet()
?.let { slots -> val stageArtifacts: Map<ArtifactId, ArtifactState> = if (targetIds.isEmpty()) {
repositories.artifactRepository.getByIds(enriched.sessionId, slots.map { it.name }.toSet()) emptyMap()
} } else {
?: emptyMap() val validated = repositories.eventStore.read(enriched.sessionId)
.mapNotNull { (it.payload as? ArtifactValidatedEvent)?.artifactId }
.filter { it in targetIds }
.toSet()
validated.associateWith { ArtifactState(phase = ArtifactLifecyclePhase.VALIDATED) }
}
val decision = resolveTransition(enriched.graph, enriched.sessionId, enriched.currentStageId, stageArtifacts) val decision = resolveTransition(enriched.graph, enriched.sessionId, enriched.currentStageId, stageArtifacts)
log.debug( log.debug(
@@ -11,6 +11,8 @@ import com.correx.core.context.model.ContextPack
import com.correx.core.context.model.TokenBudget import com.correx.core.context.model.TokenBudget
import com.correx.core.events.types.ContextEntryId import com.correx.core.events.types.ContextEntryId
import com.correx.core.events.events.ArtifactCreatedEvent import com.correx.core.events.events.ArtifactCreatedEvent
import com.correx.core.events.events.ArtifactValidatedEvent
import com.correx.core.events.events.ArtifactValidatingEvent
import com.correx.core.events.events.ApprovalDecisionResolvedEvent import com.correx.core.events.events.ApprovalDecisionResolvedEvent
import com.correx.core.events.events.ApprovalRequestedEvent import com.correx.core.events.events.ApprovalRequestedEvent
import com.correx.core.events.events.EventMetadata import com.correx.core.events.events.EventMetadata
@@ -419,15 +421,9 @@ abstract class SessionOrchestrator(
stageConfig.produces stageConfig.produces
.filter { !it.kind.llmEmitted } .filter { !it.kind.llmEmitted }
.forEach { slot -> .forEach { slot ->
emit( emit(sessionId, ArtifactCreatedEvent(slot.name, sessionId, stageId, schemaVersion = 1))
sessionId, emit(sessionId, ArtifactValidatingEvent(slot.name, sessionId, stageId))
ArtifactCreatedEvent( emit(sessionId, ArtifactValidatedEvent(slot.name, sessionId, stageId))
artifactId = slot.name,
sessionId = sessionId,
stageId = stageId,
schemaVersion = 1,
),
)
} }
} }