fix(apps): send workflowId not path from CLI; wire sandboxRoot and systemPromptPath in server; update prompts and fixtures
This commit is contained in:
@@ -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(
|
private suspend fun startSession(
|
||||||
client: HttpClient,
|
client: HttpClient,
|
||||||
host: String,
|
host: String,
|
||||||
@@ -105,7 +108,7 @@ class RunCommand : CliktCommand(name = "run") {
|
|||||||
): String? = runCatching {
|
): String? = runCatching {
|
||||||
val resp = client.post("http://$host:$portInt/sessions") {
|
val resp = client.post("http://$host:$portInt/sessions") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(StartSessionRequest(workflowId = workflow, sessionId = sessionId))
|
setBody(StartSessionRequest(workflowId = resolveWorkflowId(workflow), sessionId = sessionId))
|
||||||
}
|
}
|
||||||
resp.body<StartSessionResponse>().sessionId
|
resp.body<StartSessionResponse>().sessionId
|
||||||
}.getOrElse { e ->
|
}.getOrElse { e ->
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ dependencies {
|
|||||||
implementation project(':core:artifacts')
|
implementation project(':core:artifacts')
|
||||||
implementation project(':core:artifacts-store')
|
implementation project(':core:artifacts-store')
|
||||||
implementation project(':infrastructure')
|
implementation project(':infrastructure')
|
||||||
|
implementation project(':infrastructure:artifacts-cas')
|
||||||
implementation project(':infrastructure:workflow')
|
implementation project(':infrastructure:workflow')
|
||||||
implementation project(':infrastructure:persistence')
|
implementation project(':infrastructure:persistence')
|
||||||
implementation project(':infrastructure:inference')
|
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.sessions.projections.replay.DefaultEventReplayer
|
||||||
import com.correx.core.transitions.evaluation.PromptResolver
|
import com.correx.core.transitions.evaluation.PromptResolver
|
||||||
import com.correx.core.transitions.resolution.DefaultTransitionResolver
|
import com.correx.core.transitions.resolution.DefaultTransitionResolver
|
||||||
|
import com.correx.core.validation.artifact.ArtifactPayloadValidator
|
||||||
import com.correx.core.validation.pipeline.ValidationPipeline
|
import com.correx.core.validation.pipeline.ValidationPipeline
|
||||||
import com.correx.apps.server.logging.LoggingEventStore
|
import com.correx.apps.server.logging.LoggingEventStore
|
||||||
import com.correx.infrastructure.InfrastructureModule
|
import com.correx.infrastructure.InfrastructureModule
|
||||||
@@ -70,7 +71,7 @@ fun main() {
|
|||||||
transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) },
|
transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) },
|
||||||
contextPackBuilder = DefaultContextPackBuilder(DefaultContextCompressor()),
|
contextPackBuilder = DefaultContextPackBuilder(DefaultContextCompressor()),
|
||||||
inferenceRouter = inferenceRouter,
|
inferenceRouter = inferenceRouter,
|
||||||
validationPipeline = ValidationPipeline(validators = emptyList()),
|
validationPipeline = ValidationPipeline(validators = listOf(ArtifactPayloadValidator(artifactStore))),
|
||||||
approvalEngine = DefaultApprovalEngine(),
|
approvalEngine = DefaultApprovalEngine(),
|
||||||
riskAssessor = DefaultRiskAssessor(),
|
riskAssessor = DefaultRiskAssessor(),
|
||||||
promptResolver = promptResolver,
|
promptResolver = promptResolver,
|
||||||
|
|||||||
@@ -67,8 +67,11 @@ private fun Route.startSessionRoute(module: ServerModule) {
|
|||||||
val graph = module.workflowRegistry.find(body.workflowId)
|
val graph = module.workflowRegistry.find(body.workflowId)
|
||||||
?: return@post call.respond(HttpStatusCode.BadRequest, "Unknown workflowId: ${body.workflowId}")
|
?: return@post call.respond(HttpStatusCode.BadRequest, "Unknown workflowId: ${body.workflowId}")
|
||||||
val sessionId: SessionId = TypeId(UUID.randomUUID().toString())
|
val sessionId: SessionId = TypeId(UUID.randomUUID().toString())
|
||||||
|
val config = OrchestrationConfig(
|
||||||
|
defaultSystemPromptPath = "~/.config/correx/prompts/default_system.md",
|
||||||
|
)
|
||||||
call.application.launch {
|
call.application.launch {
|
||||||
runCatching { module.orchestrator.run(sessionId, graph, OrchestrationConfig()) }
|
runCatching { module.orchestrator.run(sessionId, graph, config) }
|
||||||
.onFailure { ex ->
|
.onFailure { ex ->
|
||||||
log.error("run failed for session={}: {}", sessionId.value, ex.message, ex)
|
log.error("run failed for session={}: {}", sessionId.value, ex.message, ex)
|
||||||
module.eventStore.append(
|
module.eventStore.append(
|
||||||
|
|||||||
@@ -103,7 +103,10 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
val sessionId: SessionId = TypeId(UUID.randomUUID().toString())
|
val sessionId: SessionId = TypeId(UUID.randomUUID().toString())
|
||||||
log.info("starting session={} workflow={}", sessionId.value, msg.workflowId)
|
log.info("starting session={} workflow={}", sessionId.value, msg.workflowId)
|
||||||
session.launch {
|
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 ->
|
.onFailure { ex ->
|
||||||
log.error("run failed for session={}: {}", sessionId.value, ex.message, ex)
|
log.error("run failed for session={}: {}", sessionId.value, ex.message, ex)
|
||||||
module.eventStore.append(
|
module.eventStore.append(
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
# healthcheck_write.md
|
Write a bash healthcheck script that reports:
|
||||||
|
|
||||||
Write a bash script to `scripts/healthcheck.sh` that reports:
|
|
||||||
- RAM usage %
|
- RAM usage %
|
||||||
- CPU usage %
|
- CPU usage %
|
||||||
- GPU usage % and VRAM usage % if an NVIDIA or AMD GPU is detected
|
- GPU usage % and VRAM usage % if an NVIDIA or AMD GPU is detected
|
||||||
|
|
||||||
Use standard tools only (free, top/vmstat, nvidia-smi, rocm-smi).
|
Use standard tools only (free, top/vmstat, nvidia-smi, rocm-smi).
|
||||||
If no GPU is detected, skip that section gracefully.
|
If no GPU is detected, skip that section gracefully.
|
||||||
Make the script executable.
|
Output path: scripts/healthcheck.sh
|
||||||
+2
-1
@@ -4,13 +4,14 @@ import com.correx.core.validation.graph.GraphValidator
|
|||||||
import com.correx.core.validation.model.ValidationContext
|
import com.correx.core.validation.model.ValidationContext
|
||||||
import com.correx.core.validation.pipeline.ValidationPipeline
|
import com.correx.core.validation.pipeline.ValidationPipeline
|
||||||
import com.correx.testing.fixtures.WorkflowFixtures
|
import com.correx.testing.fixtures.WorkflowFixtures
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class ValidationReportContractTest {
|
class ValidationReportContractTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `report must be deterministic`() {
|
fun `report must be deterministic`(): Unit = runBlocking {
|
||||||
|
|
||||||
val graph = WorkflowFixtures.simpleGraph()
|
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.core.validation.transition.TransitionValidator
|
||||||
import com.correx.testing.fixtures.CycleFixtures
|
import com.correx.testing.fixtures.CycleFixtures
|
||||||
import com.correx.testing.fixtures.WorkflowFixtures
|
import com.correx.testing.fixtures.WorkflowFixtures
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -18,7 +19,7 @@ class GraphValidatorTest {
|
|||||||
private val validator = GraphValidator()
|
private val validator = GraphValidator()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should detect dangling transition`() {
|
fun `should detect dangling transition`(): Unit = runBlocking {
|
||||||
|
|
||||||
val graph = WorkflowFixtures.simpleGraph().copy(
|
val graph = WorkflowFixtures.simpleGraph().copy(
|
||||||
stages = mapOf(StageId("A") to StageConfig()) // B removed → dangling
|
stages = mapOf(StageId("A") to StageConfig()) // B removed → dangling
|
||||||
@@ -39,7 +40,7 @@ class GraphValidatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should expose cycles as informational only`() {
|
fun `should expose cycles as informational only`(): Unit = runBlocking {
|
||||||
|
|
||||||
val graph = WorkflowFixtures.simpleGraph()
|
val graph = WorkflowFixtures.simpleGraph()
|
||||||
val cycles = listOf(CycleFixtures.simpleCycle())
|
val cycles = listOf(CycleFixtures.simpleCycle())
|
||||||
@@ -66,7 +67,7 @@ class GraphValidatorTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should be deterministic`() {
|
fun `should be deterministic`(): Unit = runBlocking {
|
||||||
|
|
||||||
val graph = WorkflowFixtures.simpleGraph()
|
val graph = WorkflowFixtures.simpleGraph()
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ class GraphValidatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 a = StageId("A")
|
||||||
val b = StageId("B")
|
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.ValidationPipeline
|
||||||
import com.correx.core.validation.pipeline.Validator
|
import com.correx.core.validation.pipeline.Validator
|
||||||
import com.correx.testing.fixtures.WorkflowFixtures
|
import com.correx.testing.fixtures.WorkflowFixtures
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -22,7 +23,7 @@ class ValidationPipelineShortCircuitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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
|
var secondCalled = false
|
||||||
val trackingValidator = Validator { _ ->
|
val trackingValidator = Validator { _ ->
|
||||||
secondCalled = true
|
secondCalled = true
|
||||||
@@ -38,7 +39,7 @@ class ValidationPipelineShortCircuitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `warnings do not short-circuit the pipeline`() {
|
fun `warnings do not short-circuit the pipeline`(): Unit = runBlocking {
|
||||||
var secondCalled = false
|
var secondCalled = false
|
||||||
|
|
||||||
val warningValidator = Validator { _ ->
|
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.core.validation.transition.TransitionValidator
|
||||||
import com.correx.testing.fixtures.CycleFixtures
|
import com.correx.testing.fixtures.CycleFixtures
|
||||||
import com.correx.testing.fixtures.WorkflowFixtures
|
import com.correx.testing.fixtures.WorkflowFixtures
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertInstanceOf
|
import org.junit.jupiter.api.Assertions.assertInstanceOf
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -34,7 +35,7 @@ class ValidationPipelineIntegrationTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@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()
|
val graph = WorkflowFixtures.simpleGraph()
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ class ValidationPipelineIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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).
|
// A dangling transition triggers GraphValidator ERROR → Rejected(retryable=false).
|
||||||
// Ownership rule: the validator sets retryable; the orchestrator must only read it.
|
// Ownership rule: the validator sets retryable; the orchestrator must only read it.
|
||||||
val a = StageId("A")
|
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.pipeline.ValidationPipeline
|
||||||
import com.correx.core.validation.transition.TransitionValidator
|
import com.correx.core.validation.transition.TransitionValidator
|
||||||
import com.correx.testing.fixtures.WorkflowFixtures
|
import com.correx.testing.fixtures.WorkflowFixtures
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class ValidationReplayTest {
|
class ValidationReplayTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `validation must be replay deterministic`() {
|
fun `validation must be replay deterministic`(): Unit = runBlocking {
|
||||||
|
|
||||||
val graph = WorkflowFixtures.simpleGraph()
|
val graph = WorkflowFixtures.simpleGraph()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user