From a96c05e22845ad334e11b3d5d848db6a382f1812 Mon Sep 17 00:00:00 2001 From: kami Date: Wed, 24 Jun 2026 16:16:03 +0000 Subject: [PATCH] test(tasks): pin the task-tool wiring in the shipped workflows Nothing asserted that the example workflows actually grant the task tools, so a future edit could silently drop one and stay green. Pin allowed_tools per stage for role_pipeline (analyst/implementer/reviewer), freestyle_planning (analyst), and the shipped review_loop (implement/review). The existing review_loop test loaded an inline, now-stale toml rather than the shipped file, so add one that loads the real file. Co-Authored-By: Claude Opus 4.8 --- .../workflow/FreestylePlanningWorkflowTest.kt | 6 ++++ .../workflow/ReviewLoopWorkflowTest.kt | 31 +++++++++++++++++++ .../workflow/RolePipelineWorkflowTest.kt | 24 ++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/FreestylePlanningWorkflowTest.kt b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/FreestylePlanningWorkflowTest.kt index bd238835..4dd07652 100644 --- a/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/FreestylePlanningWorkflowTest.kt +++ b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/FreestylePlanningWorkflowTest.kt @@ -69,5 +69,11 @@ class FreestylePlanningWorkflowTest { assertTrue(architectToDone.condition is ArtifactValidated) assertEquals(2, graph.transitions.size) + + // analyst frames the work: search + open a task (the architect threads it into the plan). + assertEquals( + setOf("file_read", "ShellTool", "task_search", "task_context", "task_create"), + graph.stages[StageId("analyst")]!!.allowedTools, + ) } } diff --git a/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt index 0172cc3e..b2d4c577 100644 --- a/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt +++ b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt @@ -7,8 +7,11 @@ import com.correx.core.artifacts.kind.JsonSchemaProperty import com.correx.core.events.types.StageId import com.correx.core.transitions.conditions.ArtifactFieldEquals import com.correx.core.transitions.conditions.FieldOperator +import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.Test import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.exists import kotlin.io.path.writeText import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -93,4 +96,32 @@ class ReviewLoopWorkflowTest { val changes = graph.transitions.first { it.to == StageId("implement") }.condition as ArtifactFieldEquals assertEquals(FieldOperator.NEQ, changes.operator) } + + private fun repoFile(rel: String): Path? { + var dir: Path? = Path.of(System.getProperty("user.dir")).toAbsolutePath() + while (dir != null) { + val candidate = dir.resolve(rel) + if (candidate.exists()) return candidate + dir = dir.parent + } + return null + } + + @Test + fun `shipped review_loop grants the task tools to both stages`() { + val path = repoFile("examples/workflows/review_loop.toml") + assumeTrue(path != null, "review_loop.toml not found from ${System.getProperty("user.dir")}") + val graph = loader.load(path!!) + + // implement claims/submits and may create a task, alongside its file write. + assertEquals( + setOf("file_write", "task_create", "task_update", "task_context", "task_search"), + graph.stages[StageId("implement")]!!.allowedTools, + ) + // review reads the task and completes it on an approved verdict. + assertEquals( + setOf("task_context", "task_update"), + graph.stages[StageId("review")]!!.allowedTools, + ) + } } diff --git a/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/RolePipelineWorkflowTest.kt b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/RolePipelineWorkflowTest.kt index f0eead46..cab71f82 100644 --- a/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/RolePipelineWorkflowTest.kt +++ b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/RolePipelineWorkflowTest.kt @@ -81,4 +81,28 @@ class RolePipelineWorkflowTest { .condition as ArtifactFieldEquals assertEquals(FieldOperator.NEQ, loop.operator) } + + @Test + fun `task tools are granted to the right stages`() { + val path = repoFile("examples/workflows/role_pipeline.toml") + assumeTrue(path != null, "role_pipeline.toml not found from ${System.getProperty("user.dir")}") + val graph = TomlWorkflowLoader(registry).load(path!!) + + // analyst opens + searches (read-only on files); creation is approval-gated (T2). + assertEquals( + setOf("file_read", "ShellTool", "task_search", "task_context", "task_create"), + graph.stages[StageId("analyst")]!!.allowedTools, + ) + // implementer owns the lifecycle: claim/submit + the full file set. + assertEquals( + setOf("file_read", "file_write", "file_edit", "ShellTool") + + setOf("task_create", "task_update", "task_context", "task_search"), + graph.stages[StageId("implementer")]!!.allowedTools, + ) + // reviewer reads the task and completes it on an approved verdict. + assertEquals( + setOf("task_context", "task_update"), + graph.stages[StageId("reviewer")]!!.allowedTools, + ) + } }