feat(cas): content-addressed artifact store (steps 1–8)

New core:artifacts-store interface + infrastructure/artifacts-cas
implementation: segment files + SQLite index, Blake3 hashing,
group-commit fsync via flushBefore, recovery tail-scan, manual
compactor, and oldest-first disk-cap evictor.

Inference events now carry promptArtifactId / responseArtifactId;
orchestrators put bytes before emitting. SqliteEventStore wraps
its txn in artifactStore.flushBefore so segment data is fsynced
before the event commit, making the crash window non-corrupting
(TailScanner re-indexes orphan tail records on reopen).

Compactor and evictor are mutually exclusive via maintenanceMutex.
Step 9 (cloud sync) deferred to a later epic.

See docs/reviews/2026-05-18-cas-steps-1-8-review.md for the final
review.
This commit is contained in:
2026-05-18 12:22:38 +04:00
parent bbff73108e
commit 219e2c762e
60 changed files with 2042 additions and 165 deletions
@@ -1,6 +1,7 @@
package com.correx.infrastructure
import com.correx.core.approvals.domain.ApprovalEngine
import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.events.EventDispatcher
import com.correx.core.events.stores.EventStore
import com.correx.core.inference.DefaultInferenceRouter
@@ -17,6 +18,10 @@ import com.correx.infrastructure.inference.commons.ModelManager
import com.correx.infrastructure.inference.commons.ResidencyMode
import com.correx.infrastructure.inference.llama.cpp.DefaultModelManager
import com.correx.infrastructure.inference.llama.cpp.LlamaCppInferenceProvider
import com.correx.infrastructure.artifactscas.CasArtifactStore
import kotlinx.coroutines.runBlocking
import com.correx.infrastructure.artifactscas.config.CasConfig
import com.correx.infrastructure.artifactscas.index.SqliteArtifactIndex
import com.correx.infrastructure.persistence.SqliteEventStore
import com.correx.infrastructure.tools.DefaultToolRegistry
import com.correx.infrastructure.tools.DispatchingToolExecutor
@@ -31,17 +36,32 @@ import com.correx.infrastructure.persistence.artifact.LiveArtifactRepository
import com.correx.core.artifacts.repository.ArtifactRepository
import com.correx.core.artifacts.DefaultArtifactReducer
import io.ktor.client.HttpClient
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.sql.DriverManager
@Suppress("TooManyFunctions")
object InfrastructureModule {
private val defaultDbPath: String =
"${System.getProperty("user.home")}/.config/correx/correx.db"
fun createEventStore(dbPath: String = defaultDbPath): EventStore {
private val defaultArtifactsRoot: String =
"${System.getProperty("user.home")}/.config/correx/artifacts"
fun createEventStore(artifactStore: ArtifactStore, dbPath: String = defaultDbPath): EventStore {
val dir = java.io.File(dbPath).parentFile
if (!dir.exists()) dir.mkdirs()
return SqliteEventStore(DriverManager.getConnection("jdbc:sqlite:$dbPath"))
return SqliteEventStore(
connection = DriverManager.getConnection("jdbc:sqlite:$dbPath"),
artifactStore = artifactStore,
)
}
fun createArtifactStore(rootDir: Path = Paths.get(defaultArtifactsRoot)): ArtifactStore {
Files.createDirectories(rootDir)
val index = SqliteArtifactIndex(rootDir.resolve("index.sqlite"))
return runBlocking { CasArtifactStore.open(CasConfig(rootDir), index) }
}
fun createModelManager(