fix(server): artifact viewer falls back to raw stage output when no content hash
Artifacts with no ArtifactContentStored mapping (older sessions, or stages where the slot->hash link was lost) showed a blank pane. listArtifacts now falls back to the stage's last InferenceCompleted output so the operator can still see what the stage produced. Empty-string content is normalised to null so the viewer shows a clear '(no content stored)' instead of a blank pane. Verified: role_pipeline sessions already resolve content from CAS (index + segments intact, store rooted at ~/.config/correx/artifacts); the previously-blank sessions are healthcheck/degenerate runs whose inference output was genuinely empty.
This commit is contained in:
@@ -10,6 +10,7 @@ import com.correx.core.events.events.ArtifactContentStoredEvent
|
|||||||
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.ArtifactValidatedEvent
|
||||||
import com.correx.core.events.events.ArtifactValidatingEvent
|
import com.correx.core.events.events.ArtifactValidatingEvent
|
||||||
|
import com.correx.core.events.events.InferenceCompletedEvent
|
||||||
import com.correx.core.events.types.ArtifactId
|
import com.correx.core.events.types.ArtifactId
|
||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -31,6 +32,11 @@ class StreamQueries(private val module: ServerModule) {
|
|||||||
suspend fun listArtifacts(sessionId: SessionId): ServerMessage.ArtifactList {
|
suspend fun listArtifacts(sessionId: SessionId): ServerMessage.ArtifactList {
|
||||||
val events = withContext(Dispatchers.IO) { module.eventStore.read(sessionId) }
|
val events = withContext(Dispatchers.IO) { module.eventStore.read(sessionId) }
|
||||||
val accs = LinkedHashMap<String, ArtifactAcc>()
|
val accs = LinkedHashMap<String, ArtifactAcc>()
|
||||||
|
// Per-stage raw inference output (last response), used as a content fallback for artifacts
|
||||||
|
// that never recorded a slot->hash mapping (ArtifactContentStoredEvent) — e.g. older sessions
|
||||||
|
// or stages where the validated-artifact link was lost. Lets the operator still see what the
|
||||||
|
// stage actually produced instead of a blank pane.
|
||||||
|
val lastInferenceByStage = HashMap<String, ArtifactId>()
|
||||||
for (event in events) {
|
for (event in events) {
|
||||||
when (val p = event.payload) {
|
when (val p = event.payload) {
|
||||||
is ArtifactCreatedEvent -> accs.getOrPut(p.artifactId.value) { ArtifactAcc() }.apply {
|
is ArtifactCreatedEvent -> accs.getOrPut(p.artifactId.value) { ArtifactAcc() }.apply {
|
||||||
@@ -40,19 +46,21 @@ class StreamQueries(private val module: ServerModule) {
|
|||||||
is ArtifactValidatingEvent -> accs[p.artifactId.value]?.let { it.phase = "VALIDATING" }
|
is ArtifactValidatingEvent -> accs[p.artifactId.value]?.let { it.phase = "VALIDATING" }
|
||||||
is ArtifactValidatedEvent -> accs[p.artifactId.value]?.let { it.phase = "VALIDATED" }
|
is ArtifactValidatedEvent -> accs[p.artifactId.value]?.let { it.phase = "VALIDATED" }
|
||||||
is ArtifactContentStoredEvent -> accs[p.artifactId.value]?.let { it.contentHash = p.contentHash }
|
is ArtifactContentStoredEvent -> accs[p.artifactId.value]?.let { it.contentHash = p.contentHash }
|
||||||
|
is InferenceCompletedEvent -> lastInferenceByStage[p.stageId.value] = p.responseArtifactId
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val artifacts = accs.map { (id, acc) ->
|
val artifacts = accs.map { (id, acc) ->
|
||||||
val content = acc.contentHash?.let { hash ->
|
val hash = acc.contentHash ?: lastInferenceByStage[acc.stageId]
|
||||||
withContext(Dispatchers.IO) { module.artifactStore.get(hash) }?.toString(Charsets.UTF_8)
|
val content = hash?.let {
|
||||||
|
withContext(Dispatchers.IO) { module.artifactStore.get(it) }?.toString(Charsets.UTF_8)
|
||||||
}
|
}
|
||||||
ArtifactSummaryDto(
|
ArtifactSummaryDto(
|
||||||
artifactId = id,
|
artifactId = id,
|
||||||
stageId = acc.stageId,
|
stageId = acc.stageId,
|
||||||
schemaVersion = acc.schemaVersion,
|
schemaVersion = acc.schemaVersion,
|
||||||
phase = acc.phase,
|
phase = acc.phase,
|
||||||
content = content,
|
content = content?.takeIf { it.isNotBlank() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return ServerMessage.ArtifactList(sessionId = sessionId, artifacts = artifacts)
|
return ServerMessage.ArtifactList(sessionId = sessionId, artifacts = artifacts)
|
||||||
|
|||||||
Reference in New Issue
Block a user