feat: plane-2 tool-call intent validation (path containment slice)

Implements the full vertical slice for invariant #9 tool-call assessment:
- core:toolintent — new module with ToolCallRule seam, ToolCallAssessor,
  WorldProbe/FileSystemWorldProbe, WorkspacePolicy, PathContainmentRule,
  and RiskMapping (assessment → RiskSummary / AssessedIssue)
- core:tools — ToolCallAssessmentRecord + ToolInvocationRecord.assessment
  field; DefaultToolReducer handles ToolCallAssessedEvent (replay proof)
- core:config — ToolsConfig gains workspaceRoot + privilegedLocations;
  ConfigLoader parses both; DEFAULT_PRIVILEGED_LOCATIONS built-in
- core:kernel — OrchestratorEngines gains toolCallAssessor/workspacePolicy/
  worldProbe fields; SessionOrchestrator.dispatchToolCalls runs
  runPlane2Assessment before the tier gate: BLOCK → hard-reject without
  executing; PROMPT_USER → elevates tier into approval path with plane2Risk
  in the ApprovalRequestedEvent
- apps/server — constructs PathContainmentRule + ToolCallAssessor +
  WorkspacePolicy from config and wires them into OrchestratorEngines

Assessment is recorded as ToolCallAssessedEvent (environment observed once,
facts stored, replay reads events — invariant #9). Assessor and WorldProbe
are only invoked on the live orchestrator path, never in replay.
This commit is contained in:
2026-05-31 04:22:58 +04:00
parent 2964849bb2
commit 545068d222
24 changed files with 931 additions and 3 deletions
@@ -299,6 +299,9 @@ object ConfigLoader {
fileReadEnabled = fileReadEnabled,
fileWriteEnabled = fileWriteEnabled,
fileEditEnabled = fileEditEnabled,
workspaceRoot = asString(toolsSection["workspace_root"], ""),
privilegedLocations = asStringList(toolsSection["privileged_locations"])
.ifEmpty { ToolsConfig.DEFAULT_PRIVILEGED_LOCATIONS },
)
val providers = providersList.mapNotNull { providerMap ->
@@ -39,7 +39,16 @@ data class ToolsConfig(
val fileReadEnabled: Boolean = true,
val fileWriteEnabled: Boolean = true,
val fileEditEnabled: Boolean = true,
)
val workspaceRoot: String = "",
val privilegedLocations: List<String> = DEFAULT_PRIVILEGED_LOCATIONS,
) {
companion object {
val DEFAULT_PRIVILEGED_LOCATIONS: List<String> = listOf(
"/etc", "/usr", "/bin", "/sbin", "/boot", "/lib", "/lib64",
"/sys", "/proc", "/dev", "/root",
)
}
}
@Serializable
data class ProviderConfig(
@@ -0,0 +1,55 @@
package com.correx.core.config
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class WorkspaceConfigTest {
@Test
fun `defaults when keys absent`() {
val tools = ToolsConfig()
assertEquals("", tools.workspaceRoot)
assertTrue(tools.privilegedLocations.contains("/etc"))
assertTrue(tools.privilegedLocations.contains("/usr"))
}
@Test
fun `default privileged locations includes system paths`() {
val defaults = ToolsConfig.DEFAULT_PRIVILEGED_LOCATIONS
assertTrue(defaults.contains("/etc"))
assertTrue(defaults.contains("/usr"))
assertTrue(defaults.contains("/bin"))
assertTrue(defaults.contains("/root"))
}
@Test
fun `parseToml parses workspace_root from tools section`() {
val toml = """
[tools]
workspace_root = "/my/workspace"
privileged_locations = ["/etc", "/usr", "/custom"]
""".trimIndent()
val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
parseTomlMethod.isAccessible = true
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
assertEquals("/my/workspace", result.tools.workspaceRoot)
assertEquals(listOf("/etc", "/usr", "/custom"), result.tools.privilegedLocations)
}
@Test
fun `parseToml uses default privileged locations when key absent`() {
val toml = """
[tools]
workspace_root = "/x"
""".trimIndent()
val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
parseTomlMethod.isAccessible = true
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
assertEquals(ToolsConfig.DEFAULT_PRIVILEGED_LOCATIONS, result.tools.privilegedLocations)
}
}