feat(core:artifacts): ArtifactKind interface, registry, built-in kinds, and MaterializingArtifactWriter

This commit is contained in:
2026-05-18 21:38:44 +04:00
parent 219e2c762e
commit 325810934a
11 changed files with 183 additions and 0 deletions
+1
View File
@@ -6,4 +6,5 @@ plugins {
dependencies { dependencies {
implementation(project(":core:events")) implementation(project(":core:events"))
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
} }
@@ -0,0 +1,14 @@
package com.correx.core.artifacts
import com.correx.core.artifacts.kind.FileWrittenArtifact
import com.correx.core.artifacts.kind.FileWrittenPayload
import java.nio.file.Path
interface MaterializingArtifactWriter {
suspend fun materialize(payload: FileWrittenPayload, sandboxRoot: Path): MaterializationResult
}
sealed interface MaterializationResult {
data class Success(val artifact: FileWrittenArtifact, val resolvedPath: Path) : MaterializationResult
data class Failure(val reason: String) : MaterializationResult
}
@@ -0,0 +1,10 @@
package com.correx.core.artifacts.kind
import kotlinx.serialization.KSerializer
interface ArtifactKind {
val id: String
val payloadSerializer: KSerializer<*>
fun deriveJsonSchema(): JsonSchema
val llmEmitted: Boolean
}
@@ -0,0 +1,21 @@
package com.correx.core.artifacts.kind
interface ArtifactKindRegistry {
fun register(kind: ArtifactKind)
fun get(id: String): ArtifactKind?
}
class DefaultArtifactKindRegistry : ArtifactKindRegistry {
private val kinds: MutableMap<String, ArtifactKind> = mutableMapOf()
init {
register(FileWrittenKind)
register(ProcessResultKind)
}
override fun register(kind: ArtifactKind) {
kinds[kind.id] = kind
}
override fun get(id: String): ArtifactKind? = kinds[id]
}
@@ -0,0 +1,20 @@
package com.correx.core.artifacts.kind
import kotlinx.serialization.KSerializer
object FileWrittenKind : ArtifactKind {
override val id: String = "file_written"
override val payloadSerializer: KSerializer<*> = FileWrittenPayload.serializer()
override val llmEmitted: Boolean = false
override fun deriveJsonSchema(): JsonSchema = JsonSchema(
type = "object",
properties = mapOf(
"path" to JsonSchemaProperty(type = "string", description = "Relative file path, no .. components"),
"content" to JsonSchemaProperty(type = "string", description = "File content"),
"mode" to JsonSchemaProperty(type = "string", description = "Unix file mode", default = "0644"),
),
required = listOf("path", "content"),
additionalProperties = false,
)
}
@@ -0,0 +1,18 @@
package com.correx.core.artifacts.kind
import kotlinx.serialization.Serializable
@Serializable
data class FileWrittenPayload(
val path: String,
val content: String,
val mode: String = "0644",
)
@Serializable
data class FileWrittenArtifact(
val path: String,
val contentHash: String,
val size: Long,
val mode: String,
)
@@ -0,0 +1,18 @@
package com.correx.core.artifacts.kind
import kotlinx.serialization.Serializable
@Serializable
data class JsonSchema(
val type: String,
val properties: Map<String, JsonSchemaProperty> = emptyMap(),
val required: List<String> = emptyList(),
val additionalProperties: Boolean = false,
)
@Serializable
data class JsonSchemaProperty(
val type: String,
val description: String = "",
val default: String? = null,
)
@@ -0,0 +1,12 @@
package com.correx.core.artifacts.kind
import kotlinx.serialization.Serializable
@Serializable
data class ProcessResultArtifact(
val command: String,
val exitCode: Int,
val stdout: String,
val stderr: String,
val durationMs: Long,
)
@@ -0,0 +1,22 @@
package com.correx.core.artifacts.kind
import kotlinx.serialization.KSerializer
object ProcessResultKind : ArtifactKind {
override val id: String = "process_result"
override val payloadSerializer: KSerializer<*> = ProcessResultArtifact.serializer()
override val llmEmitted: Boolean = false
override fun deriveJsonSchema(): JsonSchema = JsonSchema(
type = "object",
properties = mapOf(
"command" to JsonSchemaProperty(type = "string"),
"exitCode" to JsonSchemaProperty(type = "integer"),
"stdout" to JsonSchemaProperty(type = "string"),
"stderr" to JsonSchemaProperty(type = "string"),
"durationMs" to JsonSchemaProperty(type = "integer"),
),
required = listOf("command", "exitCode", "stdout", "stderr", "durationMs"),
additionalProperties = false,
)
}
@@ -0,0 +1,8 @@
package com.correx.core.artifacts.kind
import com.correx.core.events.types.ArtifactId
data class TypedArtifactSlot(
val name: ArtifactId,
val kind: ArtifactKind,
)
@@ -0,0 +1,39 @@
package com.correx.core.artifacts.kind
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
class ArtifactKindRegistryTest {
private val registry = DefaultArtifactKindRegistry()
@Test
fun `built-in kinds are registered`() {
assertNotNull(registry.get("file_written"))
assertNotNull(registry.get("process_result"))
}
@Test
fun `get returns null for unknown kind`() {
assertNull(registry.get("nonexistent"))
}
@Test
fun `FileWrittenKind is not llmEmitted`() {
assertEquals(false, registry.get("file_written")!!.llmEmitted)
}
@Test
fun `ProcessResultKind is not llmEmitted`() {
assertEquals(false, registry.get("process_result")!!.llmEmitted)
}
@Test
fun `FileWrittenKind schema has required fields`() {
val schema = FileWrittenKind.deriveJsonSchema()
assertEquals("object", schema.type)
assert("path" in schema.required)
assert("content" in schema.required)
}
}