feat(recovery,context): tier-2 intent-holder arbiter + remaining-delta pinning + surfaced signal events

- recovery: two-tier repair ladder — owner then arbiter (recovery stage recast
  as intent-holder, holds initial intent, reconciles cross-file contract disputes)
  with independent INTENT_ROUTE_BUDGET; RecoveryRoutingTest coverage
- context: remaining-delta checklist pinning (RemainingDelta{Pinning,Entry}Test)
- surface write-only events (session name, quality signals) into stats/browse
- parseToolArguments lenient-Json fallback for bare-key drift
- tools: ChildProcess seam
This commit is contained in:
2026-07-11 23:56:52 +04:00
parent 3d5e05c1fb
commit 15248cae8a
55 changed files with 1932 additions and 231 deletions
@@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class SessionSummary(
val sessionId: SessionId,
val name: String?,
val intent: String?,
val workflowId: String?,
val status: String,
@@ -2,6 +2,7 @@ package com.correx.core.sessions
import com.correx.core.events.events.InitialIntentEvent
import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.SessionNamedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.events.WorkflowCompletedEvent
@@ -12,6 +13,7 @@ import com.correx.core.events.types.SessionId
class SessionSummaryProjector {
fun project(sessionId: SessionId, events: List<StoredEvent>): SessionSummary {
var intent: String? = null
var name: String? = null
var workflowId: String? = null
var stageCount = 0
var status = "UNKNOWN"
@@ -21,6 +23,7 @@ class SessionSummaryProjector {
events.forEach { event ->
when (val p = event.payload) {
is InitialIntentEvent -> intent = p.intent
is SessionNamedEvent -> name = p.name
is WorkflowStartedEvent -> {
workflowId = p.workflowId
status = SessionStatus.ACTIVE.name
@@ -35,6 +38,7 @@ class SessionSummaryProjector {
return SessionSummary(
sessionId = sessionId,
name = name,
intent = intent,
workflowId = workflowId,
status = status,
@@ -2,6 +2,7 @@ package com.correx.core.sessions
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.InitialIntentEvent
import com.correx.core.events.events.SessionNamedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.types.EventId
@@ -58,6 +59,15 @@ class SessionSummaryProjectorTest {
assertEquals("add auth layer", summary.intent)
}
@Test
fun `SessionNamedEvent surfaces the human name in the summary`() {
val event = stored(SessionNamedEvent(sessionId, "Add auth layer", timestampMs = 0L))
val summary = projector.project(sessionId, listOf(event))
assertEquals("Add auth layer", summary.name)
}
@Test
fun `two TransitionExecutedEvents yield stageCount of two`() {
val t1 = stored(