fix(apps): send workflowId not path from CLI; wire sandboxRoot and systemPromptPath in server; update prompts and fixtures

This commit is contained in:
2026-05-18 21:40:17 +04:00
parent 5c3a8fda63
commit d2518849d7
12 changed files with 63 additions and 18 deletions
@@ -97,6 +97,9 @@ class RunCommand : CliktCommand(name = "run") {
}
}
private fun resolveWorkflowId(path: String): String =
java.nio.file.Path.of(path).fileName.toString().removeSuffix(".toml")
private suspend fun startSession(
client: HttpClient,
host: String,
@@ -105,7 +108,7 @@ class RunCommand : CliktCommand(name = "run") {
): String? = runCatching {
val resp = client.post("http://$host:$portInt/sessions") {
contentType(ContentType.Application.Json)
setBody(StartSessionRequest(workflowId = workflow, sessionId = sessionId))
setBody(StartSessionRequest(workflowId = resolveWorkflowId(workflow), sessionId = sessionId))
}
resp.body<StartSessionResponse>().sessionId
}.getOrElse { e ->
+1
View File
@@ -21,6 +21,7 @@ dependencies {
implementation project(':core:artifacts')
implementation project(':core:artifacts-store')
implementation project(':infrastructure')
implementation project(':infrastructure:artifacts-cas')
implementation project(':infrastructure:workflow')
implementation project(':infrastructure:persistence')
implementation project(':infrastructure:inference')
@@ -22,6 +22,7 @@ import com.correx.core.sessions.SessionProjector
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.core.transitions.evaluation.PromptResolver
import com.correx.core.transitions.resolution.DefaultTransitionResolver
import com.correx.core.validation.artifact.ArtifactPayloadValidator
import com.correx.core.validation.pipeline.ValidationPipeline
import com.correx.apps.server.logging.LoggingEventStore
import com.correx.infrastructure.InfrastructureModule
@@ -70,7 +71,7 @@ fun main() {
transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) },
contextPackBuilder = DefaultContextPackBuilder(DefaultContextCompressor()),
inferenceRouter = inferenceRouter,
validationPipeline = ValidationPipeline(validators = emptyList()),
validationPipeline = ValidationPipeline(validators = listOf(ArtifactPayloadValidator(artifactStore))),
approvalEngine = DefaultApprovalEngine(),
riskAssessor = DefaultRiskAssessor(),
promptResolver = promptResolver,
@@ -67,8 +67,11 @@ private fun Route.startSessionRoute(module: ServerModule) {
val graph = module.workflowRegistry.find(body.workflowId)
?: return@post call.respond(HttpStatusCode.BadRequest, "Unknown workflowId: ${body.workflowId}")
val sessionId: SessionId = TypeId(UUID.randomUUID().toString())
val config = OrchestrationConfig(
defaultSystemPromptPath = "~/.config/correx/prompts/default_system.md",
)
call.application.launch {
runCatching { module.orchestrator.run(sessionId, graph, OrchestrationConfig()) }
runCatching { module.orchestrator.run(sessionId, graph, config) }
.onFailure { ex ->
log.error("run failed for session={}: {}", sessionId.value, ex.message, ex)
module.eventStore.append(
@@ -103,7 +103,10 @@ class GlobalStreamHandler(private val module: ServerModule) {
val sessionId: SessionId = TypeId(UUID.randomUUID().toString())
log.info("starting session={} workflow={}", sessionId.value, msg.workflowId)
session.launch {
runCatching { module.orchestrator.run(sessionId, graph, OrchestrationConfig()) }
val config = OrchestrationConfig(
defaultSystemPromptPath = "~/.config/correx/prompts/default_system.md",
)
runCatching { module.orchestrator.run(sessionId, graph, config) }
.onFailure { ex ->
log.error("run failed for session={}: {}", sessionId.value, ex.message, ex)
module.eventStore.append(
+31
View File
@@ -0,0 +1,31 @@
id = "healthcheck"
start = "write_script"
[[stages]]
id = "write_script"
prompt = "prompts/healthcheck_write.md"
produces = [{ name = "healthcheck_script", kind = "file_written" }]
allowed_tools = ["file_write"]
token_budget = 4096
max_retries = 2
[[stages]]
id = "execute_script"
prompt = "prompts/healthcheck_execute.md"
needs = ["healthcheck_script"]
produces = [{ name = "healthcheck_result", kind = "process_result" }]
allowed_tools = ["ShellTool"]
token_budget = 2048
[[transitions]]
id = "write-to-execute"
from = "write_script"
to = "execute_script"
condition_type = "artifact_validated"
condition_artifact_id = "healthcheck_script"
[[transitions]]
id = "execute-to-done"
from = "execute_script"
to = "done"
condition_type = "always_true"
+2 -4
View File
@@ -1,10 +1,8 @@
# healthcheck_write.md
Write a bash script to `scripts/healthcheck.sh` that reports:
Write a bash healthcheck script that reports:
- RAM usage %
- CPU usage %
- GPU usage % and VRAM usage % if an NVIDIA or AMD GPU is detected
Use standard tools only (free, top/vmstat, nvidia-smi, rocm-smi).
If no GPU is detected, skip that section gracefully.
Make the script executable.
Output path: scripts/healthcheck.sh
@@ -4,13 +4,14 @@ import com.correx.core.validation.graph.GraphValidator
import com.correx.core.validation.model.ValidationContext
import com.correx.core.validation.pipeline.ValidationPipeline
import com.correx.testing.fixtures.WorkflowFixtures
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class ValidationReportContractTest {
@Test
fun `report must be deterministic`() {
fun `report must be deterministic`(): Unit = runBlocking {
val graph = WorkflowFixtures.simpleGraph()
@@ -9,6 +9,7 @@ import com.correx.core.validation.model.ValidationContext
import com.correx.core.validation.transition.TransitionValidator
import com.correx.testing.fixtures.CycleFixtures
import com.correx.testing.fixtures.WorkflowFixtures
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@@ -18,7 +19,7 @@ class GraphValidatorTest {
private val validator = GraphValidator()
@Test
fun `should detect dangling transition`() {
fun `should detect dangling transition`(): Unit = runBlocking {
val graph = WorkflowFixtures.simpleGraph().copy(
stages = mapOf(StageId("A") to StageConfig()) // B removed → dangling
@@ -39,7 +40,7 @@ class GraphValidatorTest {
}
@Test
fun `should expose cycles as informational only`() {
fun `should expose cycles as informational only`(): Unit = runBlocking {
val graph = WorkflowFixtures.simpleGraph()
val cycles = listOf(CycleFixtures.simpleCycle())
@@ -66,7 +67,7 @@ class GraphValidatorTest {
)
@Test
fun `should be deterministic`() {
fun `should be deterministic`(): Unit = runBlocking {
val graph = WorkflowFixtures.simpleGraph()
@@ -82,7 +83,7 @@ class GraphValidatorTest {
}
@Test
fun `should flag non-deterministic order when two edges share same from and to`() {
fun `should flag non-deterministic order when two edges share same from and to`(): Unit = runBlocking {
val a = StageId("A")
val b = StageId("B")
@@ -6,6 +6,7 @@ import com.correx.core.validation.pipeline.ValidationOutcome
import com.correx.core.validation.pipeline.ValidationPipeline
import com.correx.core.validation.pipeline.Validator
import com.correx.testing.fixtures.WorkflowFixtures
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Test
@@ -22,7 +23,7 @@ class ValidationPipelineShortCircuitTest {
}
@Test
fun `pipeline stops after first validator with errors and report contains only that section`() {
fun `pipeline stops after first validator with errors and report contains only that section`(): Unit = runBlocking {
var secondCalled = false
val trackingValidator = Validator { _ ->
secondCalled = true
@@ -38,7 +39,7 @@ class ValidationPipelineShortCircuitTest {
}
@Test
fun `warnings do not short-circuit the pipeline`() {
fun `warnings do not short-circuit the pipeline`(): Unit = runBlocking {
var secondCalled = false
val warningValidator = Validator { _ ->
@@ -14,6 +14,7 @@ import com.correx.core.validation.semantic.rules.CyclePolicyBindingRule
import com.correx.core.validation.transition.TransitionValidator
import com.correx.testing.fixtures.CycleFixtures
import com.correx.testing.fixtures.WorkflowFixtures
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Test
@@ -34,7 +35,7 @@ class ValidationPipelineIntegrationTest {
)
@Test
fun `full validation pipeline returns NeedsApproval when cycles are unbound`() {
fun `full validation pipeline returns NeedsApproval when cycles are unbound`(): Unit = runBlocking {
val graph = WorkflowFixtures.simpleGraph()
@@ -52,7 +53,7 @@ class ValidationPipelineIntegrationTest {
}
@Test
fun `rejected outcome retryable is false - set by validator, not orchestrator`() {
fun `rejected outcome retryable is false - set by validator, not orchestrator`(): Unit = runBlocking {
// A dangling transition triggers GraphValidator ERROR → Rejected(retryable=false).
// Ownership rule: the validator sets retryable; the orchestrator must only read it.
val a = StageId("A")
@@ -4,13 +4,14 @@ import com.correx.core.validation.model.ValidationContext
import com.correx.core.validation.pipeline.ValidationPipeline
import com.correx.core.validation.transition.TransitionValidator
import com.correx.testing.fixtures.WorkflowFixtures
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class ValidationReplayTest {
@Test
fun `validation must be replay deterministic`() {
fun `validation must be replay deterministic`(): Unit = runBlocking {
val graph = WorkflowFixtures.simpleGraph()