fix(server,kernel,events): harden artifact read + restore workspace on reopen + cleanups
Follow-ups spotted while fixing the artifact-content bug: - #1 robustness: listArtifacts resolves content via runCatching, degrading to null instead of failing the whole listing on one bad CAS read (resolveContent helper) - #2 eviction visibility: WARN when a referenced artifact hash resolves to null (most likely evicted/compacted while still referenced by the event log) - #3 ordering trap: document on ArtifactCreated/ArtifactContentStored that 'Created' fires at validation, AFTER ContentStored at inference — the latent fold hazard - #4 reopen gap: SessionSnapshot now carries workspaceRoot (derived from SessionWorkspaceBound), so a reopened session restores its workspace label - #5 orchestrator headroom: isBackEdge externalised to a top-level function so DefaultSessionOrchestrator drops below the TooManyFunctions threshold (was at it) #6 (orphaned empty F-010-era artifacts) needs no code change — empty content is already normalised to null ('(no content stored)'); emission is prevented going forward by the shipped F-010/F-018/F-020 fixes.
This commit is contained in:
+7
-5
@@ -14,6 +14,7 @@ import com.correx.core.events.events.ArtifactContentStoredEvent
|
||||
import com.correx.core.events.events.ArtifactValidatedEvent
|
||||
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||
import com.correx.core.events.events.RefinementIterationEvent
|
||||
import com.correx.core.events.events.StoredEvent
|
||||
import com.correx.core.events.events.TransitionExecutedEvent
|
||||
import com.correx.core.events.orchestration.OrchestrationState
|
||||
import com.correx.core.events.types.ApprovalDecisionId
|
||||
@@ -274,7 +275,7 @@ class DefaultSessionOrchestrator(
|
||||
// run, e.g. reviewer→implementer) increments a per-cycle counter recorded as an
|
||||
// event, so the guard is replay-deterministic. Exceeding the cap escalates to a
|
||||
// terminal failure instead of looping forever.
|
||||
if (isBackEdge(ctx, nextStageId)) {
|
||||
if (isBackEdge(repositories.eventStore.read(ctx.sessionId), nextStageId)) {
|
||||
val cycleKey = "${ctx.currentStageId.value}->${nextStageId.value}"
|
||||
val maxIterations = ctx.graph.stages[nextStageId]?.maxRetries ?: DEFAULT_MAX_REFINEMENT
|
||||
val iteration = (orchestrationRepository.getState(ctx.sessionId).refinementIterations[cycleKey] ?: 0) + 1
|
||||
@@ -391,10 +392,6 @@ class DefaultSessionOrchestrator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBackEdge(ctx: EnrichedExecutionContext, target: StageId): Boolean =
|
||||
repositories.eventStore.read(ctx.sessionId)
|
||||
.any { (it.payload as? TransitionExecutedEvent)?.to == target }
|
||||
|
||||
private fun ExecutionContext.enrich() = EnrichedExecutionContext(
|
||||
graph, sessionId, stageCount, currentStageId, config,
|
||||
session = repositories.sessionRepository.getSession(sessionId),
|
||||
@@ -402,6 +399,11 @@ class DefaultSessionOrchestrator(
|
||||
)
|
||||
}
|
||||
|
||||
// A back-edge re-enters a stage already transitioned into this run (e.g. reviewer→implementer).
|
||||
// Top-level (not a member) to keep the orchestrator off the TooManyFunctions threshold.
|
||||
private fun isBackEdge(events: List<StoredEvent>, target: StageId): Boolean =
|
||||
events.any { (it.payload as? TransitionExecutedEvent)?.to == target }
|
||||
|
||||
private sealed class StepResult {
|
||||
data class Continue(val ctx: EnrichedExecutionContext) : StepResult()
|
||||
data class Terminal(val result: WorkflowResult) : StepResult()
|
||||
|
||||
Reference in New Issue
Block a user