feat(core:artifacts): ArtifactKind interface, registry, built-in kinds, and MaterializingArtifactWriter
This commit is contained in:
@@ -6,4 +6,5 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":core:events"))
|
implementation(project(":core:events"))
|
||||||
|
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -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,
|
||||||
|
)
|
||||||
+12
@@ -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,
|
||||||
|
)
|
||||||
+39
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user