fix(build-gate): close two frontend COMPLETE-lie holes (#277)
Run 771c0b96 marked COMPLETE with a frontend that did not build. Two verification holes let a broken import through: 1. A MODULE build_expectation delegates to LSP and the execution gate returned Success trusting it — but tsserver failed to initialize every stage, so empty diagnostics read as clean. runExecutionGate now only trusts the MODULE->LSP short-circuit when the LSP run actually ran (new lspDiagnosticsSkipped projection); on skip it falls through to the real build command. 2. ExecutionPlanCompiler disabled the terminal whole-project auto build gate whenever ANY stage declared a build_expectation — so a MODULE (typecheck- only) declaration removed the real `npm run build` floor. Now only a real whole-project build (PROJECT/TESTS) suppresses the auto gate; MODULE/NONE do not. Extracted autoGateStages() helper. Two new compiler tests. core:kernel + infrastructure:workflow tests green; no new detekt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+21
-2
@@ -109,6 +109,18 @@ internal suspend fun SessionOrchestrator.runLspDiagnostics(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the most recent LSP diagnostics run for [stageId] was skipped (e.g. tsserver failed to
|
||||
* initialize) rather than actually run. A skipped run emits empty diagnostics, which the LSP gate
|
||||
* treats as clean — so the execution gate must not trust MODULE→LSP delegation when this holds, and
|
||||
* falls through to the real build command. Pure projection over recorded events (invariant #9).
|
||||
*/
|
||||
internal fun SessionOrchestrator.lspDiagnosticsSkipped(sessionId: SessionId, stageId: StageId): Boolean =
|
||||
eventStore.read(sessionId)
|
||||
.mapNotNull { it.payload as? LspDiagnosticsCompletedEvent }
|
||||
.lastOrNull { it.stageId == stageId }
|
||||
?.skippedReason != null
|
||||
|
||||
internal fun SessionOrchestrator.sessionWrittenPaths(sessionId: SessionId): List<String> =
|
||||
eventStore.read(sessionId)
|
||||
.mapNotNull { it.payload as? com.correx.core.events.events.FileWrittenEvent }
|
||||
@@ -145,8 +157,15 @@ internal suspend fun SessionOrchestrator.runExecutionGate(
|
||||
else -> null
|
||||
}
|
||||
// LSP pull diagnostics are the compiler-front-end/typecheck gate. Keep PROJECT bundlers and
|
||||
// TESTS as real commands, but do not invoke the profile's flat `typecheck` alias as well.
|
||||
if (expectation == BuildExpectation.MODULE && lspDiagnosticsRunner != null) {
|
||||
// TESTS as real commands, but do not invoke the profile's flat `typecheck` alias as well —
|
||||
// UNLESS the LSP run for this stage was skipped (no tsserver, init failure): a skipped check
|
||||
// verified nothing, and empty diagnostics then read as "clean", so fall through to the real
|
||||
// build command instead of trusting a gate that never ran.
|
||||
// ponytail: no unit test — first test on this path needs a full orchestrator + bound-profile +
|
||||
// LSP-runner fixture (none exists); validated by live QA re-run instead. Add if it regresses.
|
||||
if (expectation == BuildExpectation.MODULE && lspDiagnosticsRunner != null &&
|
||||
!lspDiagnosticsSkipped(sessionId, stageId)
|
||||
) {
|
||||
return StageExecutionResult.Success(emptyList())
|
||||
}
|
||||
val alias = expectation?.commandAlias ?: return StageExecutionResult.Success(emptyList())
|
||||
|
||||
Reference in New Issue
Block a user