fix(build-gate): resolve commands by toolchain (#40)

This commit is contained in:
kami
2026-07-21 02:18:04 +04:00
parent 119f59d637
commit 6e844ef1e1
15 changed files with 157 additions and 6 deletions
+1
View File
@@ -20,6 +20,7 @@ CORREX kernel team. This is the integration point for all other `core/` modules.
- `SubagentRunner` / `InSessionSubagentRunner` — runs sub-agent invocations within an active session.
- `StaticAnalysisRunner` / `ProcessStaticAnalysisRunner` — runs static analysis tools and records results as events.
- `LspDiagnosticsRunner` — injected pull-diagnostics seam; diagnostics are filtered to stage-written files, recorded, and enforced before build/review.
- Execution gates infer the written stage's Node/JVM toolchain from its recorded file kinds and prefer the matching bound-profile command namespace, falling back to flat aliases.
- Review→rework loops use the configured three-cycle default, then route accumulated notes to recovery once and fail if the fixed DoD still cannot be approved.
- `StageCheckpointReconciler` — reconciles checkpoint state across stage transitions.
- Capability-gated failures first retry in place when the stage holds the required tool; an unchanged-fingerprint gate-budget exhaustion routes to the recovery/intent-holder stage when one is available, so capability possession alone cannot cause a frozen owner loop to fail the workflow.
@@ -0,0 +1,20 @@
package com.correx.core.kernel.orchestration
import com.correx.core.artifacts.kind.KindContractTable
import com.correx.core.artifacts.kind.KindInference
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
/**
* Recovers the produced kind from this stage's recorded file manifest, then maps it to the
* toolchain-specific build command. File writes are event facts, so this remains replay-safe.
*/
internal fun SessionOrchestrator.stageProducedToolchain(
sessionId: SessionId,
stageId: StageId,
): KindContractTable.Toolchain? = toolchainForPaths(stageWrittenPaths(sessionId, stageId))
internal fun toolchainForPaths(paths: List<String>): KindContractTable.Toolchain? =
paths.asReversed().firstNotNullOfOrNull { path ->
KindInference.kindFor(path)?.let(KindContractTable::toolchainFor)
}
@@ -172,12 +172,14 @@ internal suspend fun SessionOrchestrator.runExecutionGate(
val runner = staticAnalysisRunner
val workspaceRoot = effectives.policy?.workspaceRoot
if (runner == null || workspaceRoot == null) return StageExecutionResult.Success(emptyList())
val command = profileCommands[alias]
val toolchain = stageProducedToolchain(sessionId, stageId)?.profileKey
val command = expectation.commandFor(profileCommands, toolchain)
if (command.isNullOrBlank()) {
log.warn(
"[Orchestrator] stage {} needs a {} build gate but project profile has no '{}' " +
"[Orchestrator] stage {} needs a {} build gate but project profile has no '{}'{} " +
"command — skipping execution gate",
stageId.value, expectation, alias,
toolchain?.let { " for toolchain '$it'" }.orEmpty(),
)
return StageExecutionResult.Success(emptyList())
}
@@ -1,5 +1,6 @@
package com.correx.core.kernel.orchestration
import com.correx.core.artifacts.kind.KindContractTable
import com.correx.core.events.events.BuildPrerequisiteBootstrapAttemptedEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
@@ -17,6 +18,12 @@ import org.junit.jupiter.api.Test
class BuildPrerequisiteDecisionTest {
@Test
fun `build gate toolchain follows the stage produced kind`() {
assertEquals(KindContractTable.Toolchain.NODE, toolchainForPaths(listOf("frontend/package.json")))
assertEquals(KindContractTable.Toolchain.JVM, toolchainForPaths(listOf("core/kernel/FooService.kt")))
}
private val reason =
"stage impl repeatedly referenced missing build prerequisite 'frontend/package.json' " +
"(3 blocked attempts). Create or repair the project setup before continuing."