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 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 16:16:03 +00:00
parent d12d64e4fe
commit a96c05e228
3 changed files with 61 additions and 0 deletions
@@ -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,
)
}
}
@@ -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,
)
}
}
@@ -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,
)
}
}