fix(build-gate): a mid-plan declared build never suppresses the terminal floor (#263)

autoGateStages returned an empty set as soon as any stage declared
PROJECT/TESTS, so a plan that builds at stage 3 of 9 had nothing
verifying the six stages written after it. Keep the suppression for the
redundant per-writing-stage gates, always keep the terminal stage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 21:27:09 +04:00
parent f61864ff1d
commit 867e99d1cb
2 changed files with 13 additions and 6 deletions
@@ -329,8 +329,10 @@ class ExecutionPlanCompiler(
val ownsRealBuild = declaredExpectations.values.any {
it == BuildExpectation.PROJECT || it == BuildExpectation.TESTS
}
if (ownsRealBuild) return emptySet()
val writing = plan.stages.filter { it.writes.any { path -> path.isNotBlank() } }.map { it.id }.toSet()
// A mid-plan PROJECT/TESTS declaration verifies nothing written after it, so it suppresses
// the per-writing-stage gates (those builds would be redundant) but never the terminal floor.
if (ownsRealBuild) return setOf(terminalStageId(plan))
return if (writing.isEmpty()) emptySet() else writing + terminalStageId(plan)
}
@@ -8,6 +8,7 @@ import com.correx.core.events.types.StageId
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@@ -590,9 +591,9 @@ class ExecutionPlanCompilerTest {
}
@Test
fun `an explicitly declared PROJECT gate suppresses the auto gate`() {
// A PROJECT build is a real whole-project build the planner owns, so the compiler must
// not add its own terminal floor on top.
fun `an explicitly declared PROJECT gate suppresses per-stage gates but not the terminal floor`() {
// A mid-plan PROJECT build verifies only what existed when it ran, so it drops the
// redundant per-writing-stage gates while the terminal floor stays (#263).
val planned = """
{
"goal": "scaffold a react app",
@@ -613,9 +614,13 @@ class ExecutionPlanCompilerTest {
com.correx.core.transitions.graph.BuildExpectation.PROJECT,
graph.stages[StageId("entry")]!!.buildExpectation,
)
assertFalse(
graph.stages.getValue(StageId("entry")).autoBuildGate,
"a declared PROJECT build suppresses the redundant per-writing-stage gates",
)
assertTrue(
graph.stages.values.none { it.autoBuildGate },
"a real PROJECT build declared anywhere suppresses the auto gate",
graph.stages.getValue(StageId("views")).autoBuildGate,
"the terminal stage keeps its build floor — the declared build ran before later writes",
)
}