fix(kernel): "not yet checked" must never read as "clean" in the repair ledger (#309)

fileRepairOutcomes mapped both "no diagnostic run after this write" and
"diagnostics ran and found nothing" to emptySet(), so resolved was true for a
file that was never actually re-checked. Two consequences, both the inverse of
what #309 is for:

- the ledger told the model "done, leave it" about a file whose last write was
  never verified — the precise mis-signal the tab-keeping exists to prevent;
- the loop-breaker counted unchecked writes as unresolved, so it could kill a
  run terminally on the ABSENCE of evidence rather than on recorded proof the
  rewrites weren't working.

Track it as a distinct `unchecked` state: the ledger says "not re-checked since
the last write — outcome unknown", and the breaker requires !unchecked.

./gradlew check green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgDL1v3GuQ9RZnYR6fDT95
This commit is contained in:
2026-07-26 23:01:37 +04:00
parent ee69f9becc
commit 9db4e3dd4d
2 changed files with 43 additions and 8 deletions
@@ -26,8 +26,16 @@ import com.correx.core.transitions.graph.WorkflowGraph
internal data class FileRepairOutcome(
val path: String,
val writeCount: Int,
/** True when the diagnostic run following the LAST write for this path reported no errors. */
/**
* True when the diagnostic run following the LAST write for this path reported no errors. False
* when it reported errors OR when no run has happened since that write — see [unchecked], which
* separates the two. "Not yet checked" must never read as "clean": telling the model to leave a
* file alone on the strength of a run that never happened is the exact mis-signal this ledger exists
* to prevent.
*/
val resolved: Boolean,
/** True when NO diagnostic run has been recorded since the last write for this path. */
val unchecked: Boolean,
/** Diagnostic codes present after EVERY write (only meaningful when [resolved] is false). */
val persistentCodes: Set<String>,
/** 1-based write index at which the diagnostic first went clean (only set when [resolved]). */
@@ -50,21 +58,28 @@ internal fun fileRepairOutcomes(events: List<StoredEvent>, stageId: StageId): Li
val paths = writes.map { (it.payload as FileWrittenEvent).path }.distinct()
return paths.map { path ->
val pathWrites = writes.filter { (it.payload as FileWrittenEvent).path == path }
val codesPerWrite = pathWrites.map { w ->
// null = no diagnostic run recorded after that write, which is NOT the same as a clean run.
val codesPerWrite: List<Set<String>?> = pathWrites.map { w ->
diagRuns.firstOrNull { it.sequence > w.sequence }
?.let { (it.payload as LspDiagnosticsCompletedEvent).diagnostics }
?.filter { d -> d.path == path && d.severity.equals("error", ignoreCase = true) && !d.isLint }
?.mapNotNull { it.code }
?.toSet()
?: emptySet()
}
val resolved = codesPerWrite.last().isEmpty()
val checked = codesPerWrite.filterNotNull()
val unchecked = codesPerWrite.last() == null
val resolved = !unchecked && codesPerWrite.last()!!.isEmpty()
FileRepairOutcome(
path = path,
writeCount = pathWrites.size,
resolved = resolved,
persistentCodes = if (resolved) emptySet() else codesPerWrite.reduce { a, b -> a intersect b },
clearedAtWrite = if (resolved) codesPerWrite.indexOfFirst { it.isEmpty() } + 1 else null,
unchecked = unchecked,
persistentCodes = if (resolved || checked.isEmpty()) {
emptySet()
} else {
checked.reduce { a, b -> a intersect b }
},
clearedAtWrite = if (resolved) codesPerWrite.indexOfFirst { it?.isEmpty() == true } + 1 else null,
)
}
}
@@ -72,6 +87,7 @@ internal fun fileRepairOutcomes(events: List<StoredEvent>, stageId: StageId): Li
internal fun describeFileRepairOutcome(o: FileRepairOutcome): String {
val header = "${o.path} — written ${o.writeCount}x."
return when {
o.unchecked -> "$header not re-checked since the last write — outcome unknown."
o.resolved && o.writeCount > 1 -> "$header cleared after write ${o.clearedAtWrite}. done, leave it."
o.resolved -> "$header done, leave it."
o.persistentCodes.isNotEmpty() -> "$header ${o.persistentCodes.joinToString(", ")}: present before " +
@@ -101,7 +117,9 @@ internal fun DefaultSessionOrchestrator.recoveryFileLoopBreak(
limit: Int,
): String? {
val stuck = fileRepairOutcomes(repositories.eventStore.read(sessionId), stageId)
.firstOrNull { !it.resolved && it.writeCount >= limit }
// `unchecked` is excluded deliberately: killing a run terminally demands recorded proof the
// rewrites aren't working, not the absence of proof that they are.
.firstOrNull { !it.resolved && !it.unchecked && it.writeCount >= limit }
?: return null
val codes = stuck.persistentCodes.takeIf { it.isNotEmpty() }?.joinToString(", ") ?: "its diagnostic"
return "recovery stage ${stageId.value} rewrote '${stuck.path}' ${stuck.writeCount}x without " +
@@ -74,11 +74,26 @@ class RecoveryFileLoopBreakTest {
assertTrue(describeFileRepairOutcome(fixed).let { it.contains("cleared after write 2") })
}
@Test
fun `a write with no diagnostic run after it reads as unchecked, never as resolved`() {
val events = buildList {
repeat(3) { addAll(writeThenDiagnose("SessionsList.tsx", cleared = false, code = "TS6133")) }
addAll(writeOnly("SessionsList.tsx"))
}
val outcome = fileRepairOutcomes(events, stage).single()
assertTrue(outcome.unchecked)
assertEquals(false, outcome.resolved, "an unverified write must not read as clean")
assertTrue(describeFileRepairOutcome(outcome).contains("not re-checked"))
// and it must not terminally kill the run on the absence of evidence
assertNull(recoveryFileLoopBreakPure(events, stage, limit = 3))
}
// recoveryFileLoopBreak is an extension on DefaultSessionOrchestrator that reads the event store;
// its pure core is fileRepairOutcomes, exercised directly here for the same result without needing
// to stand up an orchestrator instance.
private fun recoveryFileLoopBreakPure(events: List<StoredEvent>, stageId: StageId, limit: Int): String? {
val stuck = fileRepairOutcomes(events, stageId).firstOrNull { !it.resolved && it.writeCount >= limit }
val stuck = fileRepairOutcomes(events, stageId)
.firstOrNull { !it.resolved && !it.unchecked && it.writeCount >= limit }
?: return null
val codes = stuck.persistentCodes.takeIf { it.isNotEmpty() }?.joinToString(", ") ?: "its diagnostic"
return "recovery stage ${stageId.value} rewrote '${stuck.path}' ${stuck.writeCount}x without " +
@@ -100,6 +115,8 @@ class RecoveryFileLoopBreakTest {
payload = payload,
)
private fun writeOnly(path: String): List<StoredEvent> = writeThenDiagnose(path, cleared = true).dropLast(1)
private fun writeThenDiagnose(path: String, cleared: Boolean, code: String? = null): List<StoredEvent> {
val inv = ToolInvocationId("inv-${seq}")
val req = ev(