refactor(events): record tool capabilities on the invocation event
Capability is the canonical signal the plane-2 gates dispatch on, but ToolInvocationRequestedEvent only carried the tool name — so ReadFilesProjection classified reads by a hardcoded "file_read" string, bypassing the abstraction and (worse) re-deriving a semantic fact at replay from whatever the name meant. That violates the event-sourcing invariant that replay reads recorded facts, not live state. Now the tool's requiredCapabilities are recorded on the event at emission, and the projection classifies by ToolCapability.FILE_READ. To let the lowest module carry it, ToolCapability moves from core:tools into core:events keeping its package, so every existing import resolves unchanged and core:tools imports it from below. The field is defaulted, so pre-existing events deserialize as empty. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import com.correx.core.approvals.Tier
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.events.types.ToolInvocationId
|
||||
import com.correx.core.tools.contract.ToolCapability
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -52,6 +53,10 @@ data class ToolInvocationRequestedEvent(
|
||||
val toolName: String,
|
||||
val tier: Tier,
|
||||
val request: ToolRequest,
|
||||
// The tool's declared capabilities, recorded at emission so replay classifies the call by what
|
||||
// it could actually do (e.g. FILE_READ/FILE_WRITE) without re-deriving from the live registry.
|
||||
// Defaulted for backward compatibility: events written before this field deserialize as empty.
|
||||
val capabilities: Set<ToolCapability> = emptySet(),
|
||||
) : EventPayload
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.correx.core.tools.contract
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* What a tool is allowed to do, independent of its name. Plane-2 (`core:toolintent`) rules dispatch
|
||||
* on these — never on tool names — and they are recorded on
|
||||
* [com.correx.core.events.events.ToolInvocationRequestedEvent] at emission, so replay classifies a
|
||||
* call by the capability it actually had rather than re-deriving it from the live registry.
|
||||
*
|
||||
* Lives in `core:events` (the lowest module) so events can carry it; `core:tools` and everything
|
||||
* above import it from here — the package name is unchanged, so the move is import-transparent.
|
||||
*/
|
||||
@Serializable
|
||||
enum class ToolCapability {
|
||||
FILE_READ,
|
||||
FILE_WRITE,
|
||||
NETWORK_ACCESS,
|
||||
SHELL_EXEC,
|
||||
PROCESS_SPAWN,
|
||||
}
|
||||
Reference in New Issue
Block a user