refactor(detekt): extract magic-number constants, wrap long lines, drop legit suppressions
Mechanical detekt cleanup across apps/core/infrastructure (behavior-preserving): - MagicNumber 62->10: named constants + removed 'legit' MagicNumber suppressions (Tier level from ordinal, ReplayInferenceProvider CHARS_PER_TOKEN, ConfigLoader DEFAULT_* consts, capability scores, HTTP ranges, column widths, etc.) - MaxLineLength cut to ~0 in production modules (line wraps, no logic change) - Dropped one dead parameter (ConfigLoader.parseArray lineNum) Structural findings (ReturnCount/LongMethod/Complexity/LargeClass) left for a deliberate decomposition pass. Full build + detekt gate green.
This commit is contained in:
@@ -212,7 +212,12 @@ class ServerModule(
|
||||
|
||||
// Live-only: subscribeAll() replays nothing and ServerModule is never built under
|
||||
// ReplayOrchestrator, so narration never re-fires on restart/replay (invariant #8).
|
||||
NarrationSubscriber(eventStore = eventStore, routerFacade = routerFacade, scope = moduleScope, maxPerRun = narrationMaxPerRun).start()
|
||||
NarrationSubscriber(
|
||||
eventStore = eventStore,
|
||||
routerFacade = routerFacade,
|
||||
scope = moduleScope,
|
||||
maxPerRun = narrationMaxPerRun,
|
||||
).start()
|
||||
|
||||
// Continuous, edge-triggered health watch (observability-spec §4). Live-only like narration:
|
||||
// probes read the environment and record degraded/restored events; replay reads those facts.
|
||||
@@ -449,7 +454,10 @@ class ServerModule(
|
||||
* TOML registry, so the resume route cannot find them there. Rehydrates the artifact
|
||||
* cache first because the plan content lives in it after a restart.
|
||||
*/
|
||||
suspend fun freestyleResumeGraph(sessionId: SessionId, workflowId: String): com.correx.core.transitions.graph.WorkflowGraph? {
|
||||
suspend fun freestyleResumeGraph(
|
||||
sessionId: SessionId,
|
||||
workflowId: String,
|
||||
): com.correx.core.transitions.graph.WorkflowGraph? {
|
||||
if (!workflowId.startsWith("freestyle-")) return null
|
||||
orchestrator.rehydrate(sessionId)
|
||||
return freestyleDriver?.compiledGraph(sessionId)
|
||||
@@ -614,7 +622,11 @@ class ServerModule(
|
||||
return@forEach
|
||||
}
|
||||
val graph = workflowRegistry.find(orchState.workflowId) ?: run {
|
||||
log.warn("resumeAbandoned: no graph for session={} workflowId={}", sessionId.value, orchState.workflowId)
|
||||
log.warn(
|
||||
"resumeAbandoned: no graph for session={} workflowId={}",
|
||||
sessionId.value,
|
||||
orchState.workflowId,
|
||||
)
|
||||
return@forEach
|
||||
}
|
||||
log.info("resumeAbandoned: launching session={} workflow={}", sessionId.value, orchState.workflowId)
|
||||
|
||||
@@ -60,7 +60,8 @@ class LoggingEventStore(private val delegate: EventStore) : EventStore {
|
||||
override fun subscribe(sessionId: SessionId): Flow<StoredEvent> = delegate.subscribe(sessionId)
|
||||
|
||||
override fun allEvents(): Sequence<StoredEvent> = delegate.allEvents()
|
||||
override fun allSessionIds(): Set<SessionId> = delegate.allSessionIds().also { log.debug("got session ids from store: {}", it) }
|
||||
override fun allSessionIds(): Set<SessionId> =
|
||||
delegate.allSessionIds().also { log.debug("got session ids from store: {}", it) }
|
||||
|
||||
override fun subscribeAll(): Flow<StoredEvent> = delegate.subscribeAll()
|
||||
|
||||
|
||||
@@ -143,14 +143,28 @@ class RepoMapIndexer(
|
||||
|
||||
// Top-level declarations only — best-effort per language. Bodies/locals are never matched.
|
||||
val SYMBOL_PATTERNS: Map<String, Regex> = mapOf(
|
||||
"kt" to Regex("""(?m)^\s*(?:public |internal |open |abstract |sealed |data )*(?:class|interface|object|fun)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)"""),
|
||||
"java" to Regex("""(?m)^\s*(?:public |private |protected |final |abstract |static )*(?:class|interface|enum|record)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)"""),
|
||||
"kt" to Regex(
|
||||
"""(?m)^\s*(?:public |internal |open |abstract |sealed |data )*""" +
|
||||
"""(?:class|interface|object|fun)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)""",
|
||||
),
|
||||
"java" to Regex(
|
||||
"""(?m)^\s*(?:public |private |protected |final |abstract |static )*""" +
|
||||
"""(?:class|interface|enum|record)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)""",
|
||||
),
|
||||
"py" to Regex("""(?m)^(?:def|class)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)"""),
|
||||
"go" to Regex("""(?m)^\s*(?:func\s+(?:\([^)]*\)\s*)?|type\s+)(?<name>[A-Za-z_][A-Za-z0-9_]*)"""),
|
||||
"js" to Regex("""(?m)^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class)\s+(?<name>[A-Za-z_$][A-Za-z0-9_$]*)"""),
|
||||
"ts" to Regex("""(?m)^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+(?<name>[A-Za-z_$][A-Za-z0-9_$]*)"""),
|
||||
"js" to Regex(
|
||||
"""(?m)^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class)\s+""" +
|
||||
"""(?<name>[A-Za-z_$][A-Za-z0-9_$]*)""",
|
||||
),
|
||||
"ts" to Regex(
|
||||
"""(?m)^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+""" +
|
||||
"""(?<name>[A-Za-z_$][A-Za-z0-9_$]*)""",
|
||||
),
|
||||
// GDScript: column-0 declarations only, so indented inner-class members never match.
|
||||
"gd" to Regex("""(?m)^(?:static\s+)?(?:func|class_name|class|signal|enum)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)"""),
|
||||
"gd" to Regex(
|
||||
"""(?m)^(?:static\s+)?(?:func|class_name|class|signal|enum)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)""",
|
||||
),
|
||||
// C# / Godot Mono: top-level type declarations with leading modifiers, kept conservative
|
||||
// (no method capture — return-type heuristics there are noisy and risk false positives).
|
||||
"cs" to Regex(
|
||||
|
||||
+17
-9
@@ -116,11 +116,13 @@ class NarrationSubscriber(
|
||||
closeLane(sid)
|
||||
}
|
||||
is WorkflowFailedEvent -> {
|
||||
val instruction = "The workflow failed in stage ${p.stageId.value}: ${p.reason}. " +
|
||||
"Explain the failure to the user."
|
||||
enqueue(
|
||||
sid,
|
||||
NarrationTrigger(
|
||||
kind = "workflow_failed",
|
||||
instruction = "The workflow failed in stage ${p.stageId.value}: ${p.reason}. Explain the failure to the user.",
|
||||
instruction = instruction,
|
||||
stageId = p.stageId.value,
|
||||
),
|
||||
)
|
||||
@@ -139,21 +141,27 @@ class NarrationSubscriber(
|
||||
),
|
||||
)
|
||||
}
|
||||
is ExecutionPlanRejectedEvent -> enqueue(
|
||||
sid,
|
||||
NarrationTrigger(
|
||||
kind = "plan_rejected",
|
||||
instruction = "The execution plan was rejected (${p.source}): ${p.reason}. Explain to the user what happened and what they can do next.",
|
||||
),
|
||||
)
|
||||
is ExecutionPlanRejectedEvent -> {
|
||||
val instruction = "The execution plan was rejected (${p.source}): ${p.reason}. " +
|
||||
"Explain to the user what happened and what they can do next."
|
||||
enqueue(
|
||||
sid,
|
||||
NarrationTrigger(
|
||||
kind = "plan_rejected",
|
||||
instruction = instruction,
|
||||
),
|
||||
)
|
||||
}
|
||||
is OrchestrationPausedEvent -> {
|
||||
// Approval pauses are narrated from ApprovalRequestedEvent above.
|
||||
if (!p.reason.contains("APPROVAL", ignoreCase = true)) {
|
||||
val instruction = "Orchestration paused in stage ${p.stageId.value}: ${p.reason}. " +
|
||||
"Inform the user."
|
||||
enqueue(
|
||||
sid,
|
||||
NarrationTrigger(
|
||||
kind = "paused",
|
||||
instruction = "Orchestration paused in stage ${p.stageId.value}: ${p.reason}. Inform the user.",
|
||||
instruction = instruction,
|
||||
stageId = p.stageId.value,
|
||||
),
|
||||
pauseKey = SESSION_PAUSE_KEY,
|
||||
|
||||
+42
-13
@@ -63,20 +63,49 @@ class ReplayInspectionService(private val eventStore: EventStore) {
|
||||
|
||||
private fun toTimelineEntry(sequence: Long, payload: EventPayload, type: String): TimelineEntry =
|
||||
when (payload) {
|
||||
is WorkflowStartedEvent -> TimelineEntry(sequence, type, payload.startStageId.value, "Workflow started: ${payload.workflowId}")
|
||||
is StageCompletedEvent -> TimelineEntry(sequence, type, payload.stageId.value, "Stage completed: ${payload.stageId.value}")
|
||||
is StageFailedEvent -> TimelineEntry(sequence, type, payload.stageId.value, "Stage failed: ${payload.stageId.value} — ${payload.reason}")
|
||||
is WorkflowStartedEvent ->
|
||||
TimelineEntry(sequence, type, payload.startStageId.value, "Workflow started: ${payload.workflowId}")
|
||||
is StageCompletedEvent ->
|
||||
TimelineEntry(sequence, type, payload.stageId.value, "Stage completed: ${payload.stageId.value}")
|
||||
is StageFailedEvent -> TimelineEntry(
|
||||
sequence, type, payload.stageId.value, "Stage failed: ${payload.stageId.value} — ${payload.reason}",
|
||||
)
|
||||
is ToolExecutionCompletedEvent -> TimelineEntry(sequence, type, null, "Tool executed: ${payload.toolName}")
|
||||
is ToolExecutionFailedEvent -> TimelineEntry(sequence, type, null, "Tool failed: ${payload.toolName} — ${payload.reason}")
|
||||
is ToolExecutionRejectedEvent -> TimelineEntry(sequence, type, null, "Tool rejected: ${payload.toolName} — ${payload.reason}")
|
||||
is ApprovalRequestedEvent -> TimelineEntry(sequence, type, payload.stageId?.value, "Approval requested: tier ${payload.tier}")
|
||||
is ApprovalDecisionResolvedEvent -> TimelineEntry(sequence, type, null, "Approval resolved: ${payload.outcome}")
|
||||
is OrchestrationPausedEvent -> TimelineEntry(sequence, type, payload.stageId.value, "Orchestration paused at ${payload.stageId.value}: ${payload.reason}")
|
||||
is OrchestrationResumedEvent -> TimelineEntry(sequence, type, payload.stageId.value, "Orchestration resumed at ${payload.stageId.value}")
|
||||
is RetryAttemptedEvent -> TimelineEntry(sequence, type, payload.stageId.value, "Retry attempt ${payload.attemptNumber}/${payload.maxAttempts} at ${payload.stageId.value}: ${payload.failureReason}")
|
||||
is TransitionExecutedEvent -> TimelineEntry(sequence, type, payload.from.value, "Transition ${payload.from.value} → ${payload.to.value}")
|
||||
is WorkflowCompletedEvent -> TimelineEntry(sequence, type, payload.terminalStageId.value, "Workflow completed after ${payload.totalStages} stages")
|
||||
is WorkflowFailedEvent -> TimelineEntry(sequence, type, payload.stageId.value, "Workflow failed: ${payload.reason}")
|
||||
is ToolExecutionFailedEvent ->
|
||||
TimelineEntry(sequence, type, null, "Tool failed: ${payload.toolName} — ${payload.reason}")
|
||||
is ToolExecutionRejectedEvent ->
|
||||
TimelineEntry(sequence, type, null, "Tool rejected: ${payload.toolName} — ${payload.reason}")
|
||||
is ApprovalRequestedEvent ->
|
||||
TimelineEntry(sequence, type, payload.stageId?.value, "Approval requested: tier ${payload.tier}")
|
||||
is ApprovalDecisionResolvedEvent ->
|
||||
TimelineEntry(sequence, type, null, "Approval resolved: ${payload.outcome}")
|
||||
is OrchestrationPausedEvent -> TimelineEntry(
|
||||
sequence,
|
||||
type,
|
||||
payload.stageId.value,
|
||||
"Orchestration paused at ${payload.stageId.value}: ${payload.reason}",
|
||||
)
|
||||
is OrchestrationResumedEvent -> TimelineEntry(
|
||||
sequence, type, payload.stageId.value, "Orchestration resumed at ${payload.stageId.value}",
|
||||
)
|
||||
is RetryAttemptedEvent -> TimelineEntry(
|
||||
sequence,
|
||||
type,
|
||||
payload.stageId.value,
|
||||
"Retry attempt ${payload.attemptNumber}/${payload.maxAttempts} at " +
|
||||
"${payload.stageId.value}: ${payload.failureReason}",
|
||||
)
|
||||
is TransitionExecutedEvent -> TimelineEntry(
|
||||
sequence, type, payload.from.value, "Transition ${payload.from.value} → ${payload.to.value}",
|
||||
)
|
||||
is WorkflowCompletedEvent -> TimelineEntry(
|
||||
sequence,
|
||||
type,
|
||||
payload.terminalStageId.value,
|
||||
"Workflow completed after ${payload.totalStages} stages",
|
||||
)
|
||||
is WorkflowFailedEvent ->
|
||||
TimelineEntry(sequence, type, payload.stageId.value, "Workflow failed: ${payload.reason}")
|
||||
else -> TimelineEntry(sequence, type, null, type)
|
||||
}
|
||||
|
||||
|
||||
@@ -566,7 +566,8 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
||||
suspend fun requireToolName(scopeLabel: String): String? =
|
||||
msg.toolName?.takeIf { it.isNotBlank() }
|
||||
?: run {
|
||||
sendFrame(errorResponse("CreateGrant: $scopeLabel scope requires toolName to prevent blanket approval"))
|
||||
val errMsg = "CreateGrant: $scopeLabel scope requires toolName to prevent blanket approval"
|
||||
sendFrame(errorResponse(errMsg))
|
||||
null
|
||||
}
|
||||
|
||||
@@ -683,7 +684,9 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
||||
// Bind the per-repo project profile so router triage can cite conventions/commands.
|
||||
// Parity with launchSessionRun — chat sessions previously never bound a profile.
|
||||
runCatching { module.bindProjectProfile(sessionId) }
|
||||
.onFailure { log.warn("project profile binding failed for chat session={}: {}", sessionId.value, it.message) }
|
||||
.onFailure {
|
||||
log.warn("project profile binding failed for chat session={}: {}", sessionId.value, it.message)
|
||||
}
|
||||
|
||||
withSessionContext(sessionId) {
|
||||
runCatching {
|
||||
|
||||
@@ -102,7 +102,12 @@ class SessionStreamHandler(private val module: ServerModule) {
|
||||
),
|
||||
)
|
||||
}.onFailure {
|
||||
log.error("routerFacade.onUserInput failed for session={}: {}", msg.sessionId.value, it.message, it)
|
||||
log.error(
|
||||
"routerFacade.onUserInput failed for session={}: {}",
|
||||
msg.sessionId.value,
|
||||
it.message,
|
||||
it,
|
||||
)
|
||||
val error = ServerMessage.ProtocolError("Router error: ${it.message}")
|
||||
session.send(Frame.Text(ProtocolSerializer.encodeServerMessage(error)))
|
||||
}
|
||||
|
||||
@@ -325,7 +325,9 @@ class DomainEventMapperTest {
|
||||
)
|
||||
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L)
|
||||
assertEquals(
|
||||
ServerMessage.ToolStarted(sessionId, "file_write", Tier.T2, sequence = event.sequence, sessionSequence = 0L),
|
||||
ServerMessage.ToolStarted(
|
||||
sessionId, "file_write", Tier.T2, sequence = event.sequence, sessionSequence = 0L,
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
@@ -436,7 +438,8 @@ class DomainEventMapperTest {
|
||||
projectId = null,
|
||||
),
|
||||
)
|
||||
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L) as ServerMessage.ApprovalRequired
|
||||
val result =
|
||||
domainEventToServerMessage(event, noopStore, sessionSequence = 0L) as ServerMessage.ApprovalRequired
|
||||
assertEquals(requestId, result.requestId)
|
||||
assertEquals(Tier.T3, result.tier)
|
||||
assertNull(result.toolName)
|
||||
@@ -470,11 +473,15 @@ class DomainEventMapperTest {
|
||||
projectId = null,
|
||||
),
|
||||
)
|
||||
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L) as ServerMessage.ApprovalRequired
|
||||
val result =
|
||||
domainEventToServerMessage(event, noopStore, sessionSequence = 0L) as ServerMessage.ApprovalRequired
|
||||
assertEquals(requestId, result.requestId)
|
||||
assertEquals("HIGH", result.riskSummary.level)
|
||||
assertEquals("PROMPT_USER", result.riskSummary.recommendedAction)
|
||||
assertEquals(listOf("[ERR001] Schema mismatch", "[ERR002] Missing required field"), result.riskSummary.rationale)
|
||||
assertEquals(
|
||||
listOf("[ERR001] Schema mismatch", "[ERR002] Missing required field"),
|
||||
result.riskSummary.rationale,
|
||||
)
|
||||
assertEquals(2, result.riskSummary.factors.size)
|
||||
assertTrue(result.riskSummary.factors[0].contains("Validation errors"))
|
||||
assertTrue(result.riskSummary.factors[1].contains("Repeated failure"))
|
||||
@@ -495,7 +502,8 @@ class DomainEventMapperTest {
|
||||
userSteering = null,
|
||||
),
|
||||
)
|
||||
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 5L) as ServerMessage.ApprovalResolved
|
||||
val result =
|
||||
domainEventToServerMessage(event, noopStore, sessionSequence = 5L) as ServerMessage.ApprovalResolved
|
||||
assertEquals(sessionId, result.sessionId)
|
||||
assertEquals(requestId, result.requestId)
|
||||
assertEquals("APPROVED", result.outcome)
|
||||
@@ -519,7 +527,8 @@ class DomainEventMapperTest {
|
||||
userSteering = null,
|
||||
),
|
||||
)
|
||||
val result = domainEventToServerMessage(event, noopStore, sessionSequence = 0L) as ServerMessage.ApprovalResolved
|
||||
val result =
|
||||
domainEventToServerMessage(event, noopStore, sessionSequence = 0L) as ServerMessage.ApprovalResolved
|
||||
assertEquals("REJECTED", result.outcome)
|
||||
assertNull(result.reason)
|
||||
}
|
||||
|
||||
+36
-6
@@ -325,8 +325,14 @@ class SessionEventBridgeTest {
|
||||
val snapshot = sent[0] as ServerMessage.SessionSnapshot
|
||||
// WorkflowStartedEvent is filtered out by eventToEntry (maps to null)
|
||||
assertEquals(3, snapshot.recentEvents.size)
|
||||
assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "StageStarted", "stage-2"), snapshot.recentEvents[0])
|
||||
assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "StageCompleted", stageId.value), snapshot.recentEvents[1])
|
||||
assertEquals(
|
||||
EventEntryDto(timestamp.toEpochMilliseconds(), "StageStarted", "stage-2"),
|
||||
snapshot.recentEvents[0],
|
||||
)
|
||||
assertEquals(
|
||||
EventEntryDto(timestamp.toEpochMilliseconds(), "StageCompleted", stageId.value),
|
||||
snapshot.recentEvents[1],
|
||||
)
|
||||
assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "SessionCompleted", ""), snapshot.recentEvents[2])
|
||||
}
|
||||
|
||||
@@ -359,7 +365,13 @@ class SessionEventBridgeTest {
|
||||
fun `replaySnapshot with no sessions emits only SnapshotComplete`() = runTest {
|
||||
val store = fakeEventStore(allEventsList = emptyList())
|
||||
val sent = mutableListOf<ServerMessage>()
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
activeOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) { sent.add(it) }
|
||||
bridge.replaySnapshot()
|
||||
assertEquals(1, sent.size)
|
||||
assertEquals(ServerMessage.SnapshotComplete, sent[0])
|
||||
@@ -370,7 +382,13 @@ class SessionEventBridgeTest {
|
||||
val events = listOf(storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L))
|
||||
val store = fakeEventStore(allEventsList = events)
|
||||
val sent = mutableListOf<ServerMessage>()
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
activeOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) { sent.add(it) }
|
||||
bridge.replaySnapshot()
|
||||
assertEquals(ServerMessage.SnapshotComplete, sent.last())
|
||||
}
|
||||
@@ -385,7 +403,13 @@ class SessionEventBridgeTest {
|
||||
override suspend fun lastGlobalSequence(): Long = 5L
|
||||
}
|
||||
val sent = mutableListOf<ServerMessage>()
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
activeOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) { sent.add(it) }
|
||||
bridge.replaySnapshot()
|
||||
val snapshot = sent[0] as ServerMessage.SessionSnapshot
|
||||
assertEquals(5L, snapshot.lastSequence)
|
||||
@@ -399,7 +423,13 @@ class SessionEventBridgeTest {
|
||||
)
|
||||
val store = fakeEventStore(allEventsList = events)
|
||||
val sent = mutableListOf<ServerMessage>()
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
activeOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) { sent.add(it) }
|
||||
bridge.replaySnapshot()
|
||||
val snapshot = sent[0] as ServerMessage.SessionSnapshot
|
||||
assertEquals(3L, snapshot.lastSessionSequence)
|
||||
|
||||
@@ -257,7 +257,10 @@ class FreestyleDriverTest {
|
||||
assertFalse(approvalRequested, "operator must not be asked to approve a plan that already failed lint")
|
||||
|
||||
val lint = payloads.filterIsInstance<PlanLintCompletedEvent>().single()
|
||||
assertTrue(lint.hardFailures.any { it.code == "unproduced_need" }, "expected unproduced_need: ${lint.hardFailures}")
|
||||
assertTrue(
|
||||
lint.hardFailures.any { it.code == "unproduced_need" },
|
||||
"expected unproduced_need: ${lint.hardFailures}",
|
||||
)
|
||||
|
||||
val rejected = payloads.filterIsInstance<ExecutionPlanRejectedEvent>().single()
|
||||
assertEquals("lint", rejected.source)
|
||||
|
||||
+2
-1
@@ -175,7 +175,8 @@ class ModelLifecycleLiveTest {
|
||||
val raw = frame.readText()
|
||||
when (val msg = decodeServerMessage(raw)) {
|
||||
is ServerMessage.ModelChanged -> modelChanged = msg
|
||||
is ServerMessage.ProtocolError -> error("Unexpected protocol error during swap: ${msg.message}")
|
||||
is ServerMessage.ProtocolError ->
|
||||
error("Unexpected protocol error during swap: ${msg.message}")
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -29,7 +29,11 @@ private class OnesEmbedder(override val dimension: Int = 8) : Embedder {
|
||||
|
||||
class ProjectMemoryServiceTest {
|
||||
|
||||
private fun store(sessionId: SessionId, payload: com.correx.core.events.events.EventPayload, es: InMemoryEventStore) =
|
||||
private fun store(
|
||||
sessionId: SessionId,
|
||||
payload: com.correx.core.events.events.EventPayload,
|
||||
es: InMemoryEventStore,
|
||||
) =
|
||||
runBlocking {
|
||||
es.append(
|
||||
NewEvent(
|
||||
@@ -58,7 +62,11 @@ class ProjectMemoryServiceTest {
|
||||
|
||||
val sessionA = SessionId("A")
|
||||
store(sessionA, SteeringNoteAddedEvent(sessionA, "use jwt for auth"), eventStore)
|
||||
store(sessionA, TransitionExecutedEvent(sessionA, StageId("plan"), StageId("impl"), TransitionId("t1")), eventStore)
|
||||
store(
|
||||
sessionA,
|
||||
TransitionExecutedEvent(sessionA, StageId("plan"), StageId("impl"), TransitionId("t1")),
|
||||
eventStore,
|
||||
)
|
||||
|
||||
service(config, eventStore, l3).persist(sessionA, "/repo")
|
||||
|
||||
@@ -67,7 +75,10 @@ class ProjectMemoryServiceTest {
|
||||
|
||||
assertTrue(seeded.any { it.contains("use jwt for auth") }, "expected prior decision retrieved")
|
||||
val note = eventStore.read(sessionB).mapNotNull { it.payload as? SteeringNoteAddedEvent }.firstOrNull()
|
||||
assertTrue(note != null && note.content.contains("Project memory"), "expected seeded steering note in session B")
|
||||
assertTrue(
|
||||
note != null && note.content.contains("Project memory"),
|
||||
"expected seeded steering note in session B",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-1
@@ -109,7 +109,9 @@ class NarrationSubscriberTest {
|
||||
|
||||
liveFlow.emit(storedEvent(WorkflowStartedEvent(sessionId, "wf", StageId("s0")), seq = 1L))
|
||||
liveFlow.emit(storedEvent(StageCompletedEvent(sessionId, StageId("a"), TransitionId("t1")), seq = 2L))
|
||||
liveFlow.emit(storedEvent(StageFailedEvent(sessionId, StageId("b"), TransitionId("t2"), reason = "x"), seq = 3L))
|
||||
liveFlow.emit(
|
||||
storedEvent(StageFailedEvent(sessionId, StageId("b"), TransitionId("t2"), reason = "x"), seq = 3L),
|
||||
)
|
||||
liveFlow.emit(storedEvent(WorkflowCompletedEvent(sessionId, StageId("c"), totalStages = 2), seq = 4L))
|
||||
|
||||
withTimeout(2_000L) {
|
||||
|
||||
+3
-1
@@ -282,7 +282,9 @@ class ServerMessageSerializationTest {
|
||||
val jsonStr = ProtocolSerializer.encodeServerMessage(msg)
|
||||
assert(jsonStr.contains("\"type\":\"workflow.proposed\"")) { "expected type=workflow.proposed" }
|
||||
assert(jsonStr.contains("\"workflowId\":\"research\"")) { "expected candidate id" }
|
||||
assert(jsonStr.contains("\"originalRequest\":\"find papers on event sourcing\"")) { "expected original request" }
|
||||
assert(
|
||||
jsonStr.contains("\"originalRequest\":\"find papers on event sourcing\""),
|
||||
) { "expected original request" }
|
||||
|
||||
val decoded = json.decodeFromString<ServerMessage.WorkflowProposed>(jsonStr)
|
||||
assertEquals(2, decoded.candidates.size)
|
||||
|
||||
+4
-2
@@ -56,8 +56,10 @@ class FileSystemWorkflowRegistryTest {
|
||||
|
||||
@Test
|
||||
fun `listAll falls back to workflowId when description absent`() {
|
||||
dir.resolve("healthcheck.toml").writeText(validToml.replace("description = \"Runs the health check pipeline\"\n", ""))
|
||||
assertEquals("healthcheck", FileSystemWorkflowRegistry(TomlWorkflowLoader(), dir).listAll().single().description)
|
||||
val noDescToml = validToml.replace("description = \"Runs the health check pipeline\"\n", "")
|
||||
dir.resolve("healthcheck.toml").writeText(noDescToml)
|
||||
val registry = FileSystemWorkflowRegistry(TomlWorkflowLoader(), dir)
|
||||
assertEquals("healthcheck", registry.listAll().single().description)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+4
-2
@@ -87,10 +87,12 @@ class ReplayInspectionServiceTest {
|
||||
private class SeededEventStore(private val events: List<StoredEvent>) : EventStore {
|
||||
override suspend fun append(event: NewEvent): StoredEvent = error("unused")
|
||||
override suspend fun appendAll(events: List<NewEvent>): List<StoredEvent> = error("unused")
|
||||
override fun read(sessionId: SessionId): List<StoredEvent> = events.filter { it.metadata.sessionId == sessionId }
|
||||
override fun read(sessionId: SessionId): List<StoredEvent> =
|
||||
events.filter { it.metadata.sessionId == sessionId }
|
||||
override fun readFrom(sessionId: SessionId, fromSequence: Long): List<StoredEvent> =
|
||||
events.filter { it.metadata.sessionId == sessionId && it.sequence >= fromSequence }
|
||||
override fun lastSequence(sessionId: SessionId): Long? = events.filter { it.metadata.sessionId == sessionId }.lastOrNull()?.sequence
|
||||
override fun lastSequence(sessionId: SessionId): Long? =
|
||||
events.filter { it.metadata.sessionId == sessionId }.lastOrNull()?.sequence
|
||||
override fun subscribe(sessionId: SessionId): Flow<StoredEvent> = emptyFlow()
|
||||
override fun subscribeAll(): Flow<StoredEvent> = emptyFlow()
|
||||
override suspend fun lastGlobalSequence(): Long = 0L
|
||||
|
||||
+12
-4
@@ -86,18 +86,26 @@ class EventInspectRoutesTest {
|
||||
)
|
||||
|
||||
private val seedEvents = listOf(
|
||||
storedEvent(WorkspaceStateObservedEvent(sessionId = s1, workspaceRoot = "/repo", stateKey = "git:abc", source = "git"), 1L),
|
||||
storedEvent(WorkspaceStateObservedEvent(sessionId = s1, workspaceRoot = "/repo", stateKey = "git:def", source = "git"), 2L),
|
||||
storedEvent(
|
||||
WorkspaceStateObservedEvent(sessionId = s1, workspaceRoot = "/repo", stateKey = "git:abc", source = "git"),
|
||||
1L,
|
||||
),
|
||||
storedEvent(
|
||||
WorkspaceStateObservedEvent(sessionId = s1, workspaceRoot = "/repo", stateKey = "git:def", source = "git"),
|
||||
2L,
|
||||
),
|
||||
storedEvent(InitialIntentEvent(sessionId = s1, intent = "build the thing"), 3L),
|
||||
)
|
||||
|
||||
private class SeededEventStore(private val events: List<StoredEvent>) : EventStore {
|
||||
override suspend fun append(event: NewEvent): StoredEvent = error("unused")
|
||||
override suspend fun appendAll(events: List<NewEvent>): List<StoredEvent> = error("unused")
|
||||
override fun read(sessionId: SessionId): List<StoredEvent> = events.filter { it.metadata.sessionId == sessionId }
|
||||
override fun read(sessionId: SessionId): List<StoredEvent> =
|
||||
events.filter { it.metadata.sessionId == sessionId }
|
||||
override fun readFrom(sessionId: SessionId, fromSequence: Long): List<StoredEvent> =
|
||||
events.filter { it.metadata.sessionId == sessionId && it.sequence >= fromSequence }
|
||||
override fun lastSequence(sessionId: SessionId): Long? = events.filter { it.metadata.sessionId == sessionId }.lastOrNull()?.sequence
|
||||
override fun lastSequence(sessionId: SessionId): Long? =
|
||||
events.filter { it.metadata.sessionId == sessionId }.lastOrNull()?.sequence
|
||||
override fun subscribe(sessionId: SessionId): Flow<StoredEvent> = emptyFlow()
|
||||
override fun subscribeAll(): Flow<StoredEvent> = emptyFlow()
|
||||
override suspend fun lastGlobalSequence(): Long = 0L
|
||||
|
||||
@@ -180,7 +180,11 @@ class SessionUndoServiceTest {
|
||||
val noWorkspaceEventStore = FakeEventStore(listOf(storedEvent(fileWritten, sessionId, 1L)))
|
||||
val serviceBootOnly = SessionUndoService(noWorkspaceEventStore, storeNoWorkspace, setOf(bootDir))
|
||||
val summaryBootOnly = serviceBootOnly.undo(sessionId)
|
||||
assertEquals(1, summaryBootOnly.rejected, "boot-only roots must jail the workspace-dir file when no bound event exists")
|
||||
assertEquals(
|
||||
1,
|
||||
summaryBootOnly.rejected,
|
||||
"boot-only roots must jail the workspace-dir file when no bound event exists",
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -121,7 +121,13 @@ class GlobalStreamHandlerTest {
|
||||
orchRepo: OrchestrationRepository,
|
||||
): Pair<SessionEventBridge, Channel<ServerMessage>> {
|
||||
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, orchRepo, noopWorkflowRegistry, noopToolRegistry) { received.send(it) }
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
orchRepo,
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) { received.send(it) }
|
||||
return bridge to received
|
||||
}
|
||||
|
||||
@@ -167,7 +173,13 @@ class GlobalStreamHandlerTest {
|
||||
val liveFlow = MutableSharedFlow<StoredEvent>(extraBufferCapacity = 64)
|
||||
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 5L)
|
||||
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) {
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
idleOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) {
|
||||
received.send(it)
|
||||
}
|
||||
val mapper = DomainEventMapper(noopArtifactStore)
|
||||
@@ -209,7 +221,13 @@ class GlobalStreamHandlerTest {
|
||||
val liveFlow = MutableSharedFlow<StoredEvent>(extraBufferCapacity = 16)
|
||||
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 0L)
|
||||
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) {
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
idleOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) {
|
||||
received.send(it)
|
||||
}
|
||||
val mapper = DomainEventMapper(noopArtifactStore)
|
||||
@@ -257,7 +275,13 @@ class GlobalStreamHandlerTest {
|
||||
return 0L
|
||||
}
|
||||
}
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { }
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
idleOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) { }
|
||||
val mapper = DomainEventMapper(noopArtifactStore)
|
||||
|
||||
val job = launch {
|
||||
@@ -274,7 +298,13 @@ class GlobalStreamHandlerTest {
|
||||
fun `shutdown - cancel outer scope ends subscription with no leaks`() = runTest {
|
||||
val liveFlow = MutableSharedFlow<StoredEvent>()
|
||||
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 0L)
|
||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { }
|
||||
val bridge = SessionEventBridge(
|
||||
store,
|
||||
noopArtifactStore,
|
||||
idleOrchestrationRepository(),
|
||||
noopWorkflowRegistry,
|
||||
noopToolRegistry,
|
||||
) { }
|
||||
val mapper = DomainEventMapper(noopArtifactStore)
|
||||
|
||||
val job = launch {
|
||||
|
||||
@@ -250,7 +250,8 @@ class WorkspaceHandshakeTest {
|
||||
delay(50)
|
||||
|
||||
// Phase 2: start a session
|
||||
send(Frame.Text(encode(ClientMessage.StartSession(workflowId = "handshake-test-workflow", config = null))))
|
||||
val startMsg = ClientMessage.StartSession(workflowId = "handshake-test-workflow", config = null)
|
||||
send(Frame.Text(encode(startMsg)))
|
||||
|
||||
// Give the server time to process the event emission (before orchestrator runs)
|
||||
delay(200)
|
||||
@@ -298,7 +299,8 @@ class WorkspaceHandshakeTest {
|
||||
delay(50)
|
||||
|
||||
// StartSession — this closes the Hello window
|
||||
send(Frame.Text(encode(ClientMessage.StartSession(workflowId = "handshake-test-workflow", config = null))))
|
||||
val startMsg = ClientMessage.StartSession(workflowId = "handshake-test-workflow", config = null)
|
||||
send(Frame.Text(encode(startMsg)))
|
||||
delay(100)
|
||||
|
||||
// Late Hello with a different path — must be ignored
|
||||
@@ -334,7 +336,8 @@ class WorkspaceHandshakeTest {
|
||||
delay(100)
|
||||
|
||||
// No Hello — send StartSession directly
|
||||
send(Frame.Text(encode(ClientMessage.StartSession(workflowId = "handshake-test-workflow", config = null))))
|
||||
val startMsg = ClientMessage.StartSession(workflowId = "handshake-test-workflow", config = null)
|
||||
send(Frame.Text(encode(startMsg)))
|
||||
delay(200)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user