feat: FileWrittenEvent + reduce file mutations onto ToolState

This commit is contained in:
2026-05-31 10:28:01 +04:00
parent 7ad29c3e01
commit 0e688f3daf
7 changed files with 202 additions and 0 deletions
@@ -0,0 +1,27 @@
package com.correx.core.events.events
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.ToolInvocationId
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* First-class record of a file mutation performed by a tool. Makes file writes
* reversible from the log + CAS (invariants #1/#5): [preImageHash]/[postImageHash]
* are content-addressed blobs in the ArtifactStore. A newly-created file has
* `preExisted = false` and a null [preImageHash]; a delete has a null
* [postImageHash]. Undo restores the pre-image blob (or deletes) through the
* same atomic write seam.
*/
@Serializable
@SerialName("FileWritten")
data class FileWrittenEvent(
val invocationId: ToolInvocationId,
val sessionId: SessionId,
val path: String,
val preImageHash: String? = null,
val postImageHash: String? = null,
val preExisted: Boolean,
val mode: String = "0644",
val timestampMs: Long,
) : EventPayload
@@ -11,6 +11,7 @@ import com.correx.core.events.events.ChatSessionStartedEvent
import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.ContextTruncatedEvent
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.FileWrittenEvent
import com.correx.core.events.events.L3MemoryRetrievedEvent
import com.correx.core.events.events.InferenceCompletedEvent
import com.correx.core.events.events.InferenceFailedEvent
@@ -51,6 +52,7 @@ val eventModule = SerializersModule {
subclass(ToolExecutionFailedEvent::class)
subclass(ToolExecutionRejectedEvent::class)
subclass(ToolCallAssessedEvent::class)
subclass(FileWrittenEvent::class)
subclass(SteeringNoteAddedEvent::class)
subclass(StageFailedEvent::class)
subclass(StageCompletedEvent::class)
@@ -0,0 +1,45 @@
package com.correx.core.events.serialization
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.FileWrittenEvent
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.ToolInvocationId
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class FileWrittenEventSerializationTest {
private val sample = FileWrittenEvent(
invocationId = ToolInvocationId("inv-1"),
sessionId = SessionId("sess-1"),
path = "src/Main.kt",
preImageHash = "aaaa",
postImageHash = "bbbb",
preExisted = true,
mode = "0644",
timestampMs = 7L,
)
@Test
fun `round-trips as polymorphic EventPayload`() {
val encoded = eventJson.encodeToString(EventPayload.serializer(), sample)
assertTrue(encoded.contains("\"type\":\"FileWritten\""), "SerialName must be present: $encoded")
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
assertEquals(sample, decoded)
}
@Test
fun `decodes hand-written FileWritten JSON for a new file`() {
val json = """
{"type":"FileWritten","invocationId":"inv-9","sessionId":"s","path":"new.txt",
"postImageHash":"cccc","preExisted":false,"timestampMs":1}
""".trimIndent()
val decoded = eventJson.decodeFromString(EventPayload.serializer(), json)
assertTrue(decoded is FileWrittenEvent)
decoded as FileWrittenEvent
assertEquals(false, decoded.preExisted)
assertEquals(null, decoded.preImageHash)
assertEquals("cccc", decoded.postImageHash)
}
}