feat(artifacts): config-driven custom artifact kinds with schema validation
Users declare artifact kinds in [[artifacts]] (id + schema_path to a JSON-schema file + llm_emitted); ConfigArtifactKind registers them at startup via createWorkflowLoader(extraKinds). New JsonSchemaValidator validates any non-built-in kind generically against its declared deriveJsonSchema(), so an LLM-emitted custom kind is checked against its shape, never trusted. Removed the dead payloadSerializer from the ArtifactKind contract. Schema source is path-to-file (not inline TOML — the hand-rolled parser can't nest).
This commit is contained in:
@@ -4,6 +4,9 @@ import com.correx.apps.server.logging.LoggingEventStore
|
||||
import com.correx.apps.server.registry.FileSystemWorkflowRegistry
|
||||
import com.correx.apps.server.registry.ProviderRegistry
|
||||
import com.correx.core.approvals.domain.DefaultApprovalEngine
|
||||
import com.correx.core.artifacts.kind.ArtifactKind
|
||||
import com.correx.core.artifacts.kind.ConfigArtifactKind
|
||||
import com.correx.core.artifacts.kind.JsonSchema
|
||||
import com.correx.core.artifactstore.ArtifactStore
|
||||
import com.correx.core.config.ConfigLoader
|
||||
import com.correx.core.config.CorrexConfig
|
||||
@@ -62,7 +65,9 @@ import com.correx.infrastructure.tools.ToolConfig
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
@@ -244,7 +249,9 @@ fun main() {
|
||||
eventStore = eventStore,
|
||||
artifactStore = artifactStore,
|
||||
sessionRepository = repositories.sessionRepository,
|
||||
workflowRegistry = FileSystemWorkflowRegistry(InfrastructureModule.createWorkflowLoader()),
|
||||
workflowRegistry = FileSystemWorkflowRegistry(
|
||||
InfrastructureModule.createWorkflowLoader(loadConfigArtifactKinds(correxConfig)),
|
||||
),
|
||||
providerRegistry = infraRegistry.asServerRegistry(),
|
||||
defaultOrchestrationConfig = defaultOrchestrationConfig,
|
||||
routerFacade = routerFacade,
|
||||
@@ -294,6 +301,39 @@ private fun logStoresInfo(
|
||||
log.info(" artifact store: {}", artifactStore::class.simpleName)
|
||||
}
|
||||
|
||||
// Build user-declared artifact kinds from config. Each [[artifacts]] entry points to a JSON
|
||||
// schema file; we parse it into a JsonSchema and wrap it in a ConfigArtifactKind. A schema that
|
||||
// can't be loaded is skipped with a warning — any workflow that references the kind then fails
|
||||
// loudly at load time ("Unknown artifact kind"), rather than running with an unvalidated kind.
|
||||
private fun loadConfigArtifactKinds(config: CorrexConfig): List<ArtifactKind> {
|
||||
val configDir = ConfigLoader.configPath().parent
|
||||
return config.artifacts.mapNotNull { decl ->
|
||||
val schemaPath = resolveConfigRelativePath(decl.schemaPath, configDir)
|
||||
val schema = runCatching {
|
||||
Json.decodeFromString(JsonSchema.serializer(), Files.readString(schemaPath))
|
||||
}.getOrElse { e ->
|
||||
System.err.println(
|
||||
"Warning: artifact kind '${decl.id}' schema '$schemaPath' failed to load: ${e.message}",
|
||||
)
|
||||
return@mapNotNull null
|
||||
}
|
||||
ConfigArtifactKind(id = decl.id, schema = schema, llmEmitted = decl.llmEmitted)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveConfigRelativePath(raw: String, configDir: Path?): Path {
|
||||
val expanded = if (raw.startsWith("~/")) {
|
||||
Paths.get(System.getProperty("user.home"), raw.removePrefix("~/"))
|
||||
} else {
|
||||
Path.of(raw)
|
||||
}
|
||||
return when {
|
||||
expanded.isAbsolute -> expanded
|
||||
configDir != null -> configDir.resolve(expanded)
|
||||
else -> expanded
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildProviders(config: CorrexConfig): List<LlamaCppInferenceProvider> {
|
||||
return if (config.providers.isNotEmpty()) {
|
||||
log.info("Loading {} provider(s) from config", config.providers.size)
|
||||
|
||||
Reference in New Issue
Block a user