feat(workspace): WorkspaceContext + SessionWorkspaceBoundEvent foundation

Add a WorkspaceContext value type and a nullable OrchestrationConfig.workspace,
plus SessionWorkspaceBoundEvent registered in eventModule. Purely additive; no
behavior change when workspace is null. (Axis 2 Phase A, tasks 1-2.)
This commit is contained in:
2026-06-02 19:56:49 +04:00
parent d1dc9e2f5c
commit d405f9d2c1
5 changed files with 59 additions and 0 deletions
@@ -9,3 +9,11 @@ import kotlinx.serialization.Serializable
data class ChatSessionStartedEvent(
val sessionId: SessionId,
) : EventPayload
@Serializable
@SerialName("SessionWorkspaceBound")
data class SessionWorkspaceBoundEvent(
val sessionId: SessionId,
val workspaceRoot: String,
val allowedPaths: List<String>,
) : EventPayload
@@ -9,6 +9,7 @@ import com.correx.core.events.events.ArtifactValidatedEvent
import com.correx.core.events.events.ArtifactValidatingEvent
import com.correx.core.events.events.ChatSessionStartedEvent
import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.SessionWorkspaceBoundEvent
import com.correx.core.events.events.ContextTruncatedEvent
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.FileWrittenEvent
@@ -79,6 +80,7 @@ val eventModule = SerializersModule {
subclass(RiskAssessedEvent::class)
subclass(ChatSessionStartedEvent::class)
subclass(ChatTurnEvent::class)
subclass(SessionWorkspaceBoundEvent::class)
subclass(L3MemoryRetrievedEvent::class)
subclass(ContextTruncatedEvent::class)
}
@@ -0,0 +1,38 @@
package com.correx.core.events.serialization
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.SessionWorkspaceBoundEvent
import com.correx.core.events.types.SessionId
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SessionWorkspaceBoundEventSerializationTest {
private val sample = SessionWorkspaceBoundEvent(
sessionId = SessionId("sess-1"),
workspaceRoot = "/home/user/project",
allowedPaths = listOf("/home/user/project", "/home/user/project/src"),
)
@Test
fun `round-trips as polymorphic EventPayload`() {
val encoded = eventJson.encodeToString(EventPayload.serializer(), sample)
assertTrue(encoded.contains("\"type\":\"SessionWorkspaceBound\""), "SerialName must be present: $encoded")
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
assertEquals(sample, decoded)
}
@Test
fun `decodes hand-written SessionWorkspaceBound JSON`() {
val json = """
{"type":"SessionWorkspaceBound","sessionId":"s-42","workspaceRoot":"/ws","allowedPaths":["/ws","/ws/src"]}
""".trimIndent()
val decoded = eventJson.decodeFromString(EventPayload.serializer(), json)
assertTrue(decoded is SessionWorkspaceBoundEvent)
decoded as SessionWorkspaceBoundEvent
assertEquals("s-42", decoded.sessionId.value)
assertEquals("/ws", decoded.workspaceRoot)
assertEquals(listOf("/ws", "/ws/src"), decoded.allowedPaths)
}
}
@@ -10,4 +10,5 @@ data class OrchestrationConfig(
val stageTimeoutMs: Long = 60_000L,
val sandboxRoot: Path = Path.of(System.getProperty("java.io.tmpdir"), "correx-sandbox"),
val defaultSystemPromptPath: String? = null,
val workspace: WorkspaceContext? = null,
)
@@ -0,0 +1,10 @@
package com.correx.core.kernel.orchestration
import java.nio.file.Path
data class WorkspaceContext(
val workspaceRoot: Path,
val workingDir: Path,
val allowedPaths: Set<Path>,
val privilegedLocations: List<Path> = emptyList(),
)