epic-12: after epic audit and init commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:events"))
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.correx.core.artifacts
|
||||
|
||||
import com.correx.core.events.events.StoredEvent
|
||||
import com.correx.core.sessions.projections.Projection
|
||||
import java.util.logging.Logger
|
||||
|
||||
class ArtifactProjector(private val reducer: ArtifactReducer) : Projection<ArtifactState> {
|
||||
|
||||
private val logger: Logger = Logger.getLogger(ArtifactProjector::class.java.name)
|
||||
|
||||
override fun initial(): ArtifactState = ArtifactState()
|
||||
|
||||
// Invalid lifecycle transitions must not crash the replayer. Log and leave state unchanged
|
||||
// so replay continues; the event log remains the source of truth.
|
||||
override fun apply(state: ArtifactState, event: StoredEvent): ArtifactState =
|
||||
reducer.reduce(state, event).getOrElse { exception ->
|
||||
if (exception !is IllegalStateException) throw exception
|
||||
logger.warning(
|
||||
"Invalid artifact lifecycle transition (event payload=${event.payload::class.simpleName}): " +
|
||||
"${exception.message}. State unchanged."
|
||||
)
|
||||
state
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.correx.core.artifacts
|
||||
|
||||
import com.correx.core.artifacts.model.ArtifactRelationship
|
||||
import com.correx.core.events.events.ArtifactArchivedEvent
|
||||
import com.correx.core.events.events.ArtifactCreatedEvent
|
||||
import com.correx.core.events.events.ArtifactRejectedEvent
|
||||
import com.correx.core.events.events.ArtifactRelationshipAddedEvent
|
||||
import com.correx.core.events.events.ArtifactSupersededEvent
|
||||
import com.correx.core.events.events.ArtifactValidatedEvent
|
||||
import com.correx.core.events.events.ArtifactValidatingEvent
|
||||
import com.correx.core.events.events.StoredEvent
|
||||
import com.correx.core.events.types.ArtifactLifecyclePhase
|
||||
|
||||
interface ArtifactReducer {
|
||||
fun reduce(state: ArtifactState, event: StoredEvent): Result<ArtifactState>
|
||||
}
|
||||
|
||||
class DefaultArtifactReducer : ArtifactReducer {
|
||||
|
||||
override fun reduce(state: ArtifactState, event: StoredEvent): Result<ArtifactState> =
|
||||
when (val p = event.payload) {
|
||||
is ArtifactCreatedEvent ->
|
||||
Result.success(state.copy(phase = ArtifactLifecyclePhase.CREATED))
|
||||
is ArtifactValidatingEvent ->
|
||||
transition(state, ArtifactLifecyclePhase.VALIDATING, ArtifactLifecyclePhase.CREATED)
|
||||
is ArtifactValidatedEvent ->
|
||||
transition(state, ArtifactLifecyclePhase.VALIDATED, ArtifactLifecyclePhase.VALIDATING)
|
||||
is ArtifactRejectedEvent ->
|
||||
transition(state, ArtifactLifecyclePhase.REJECTED, ArtifactLifecyclePhase.VALIDATING)
|
||||
is ArtifactSupersededEvent ->
|
||||
transition(state, ArtifactLifecyclePhase.SUPERSEDED, ArtifactLifecyclePhase.VALIDATED)
|
||||
is ArtifactArchivedEvent ->
|
||||
transitionFromAny(
|
||||
state,
|
||||
ArtifactLifecyclePhase.ARCHIVED,
|
||||
ArtifactLifecyclePhase.VALIDATED,
|
||||
ArtifactLifecyclePhase.REJECTED,
|
||||
)
|
||||
is ArtifactRelationshipAddedEvent -> {
|
||||
val rel = ArtifactRelationship(p.sourceId, p.targetId, p.relationshipType)
|
||||
Result.success(
|
||||
state.copy(lineage = state.lineage.copy(relationships = state.lineage.relationships + rel))
|
||||
)
|
||||
}
|
||||
else -> Result.success(state)
|
||||
}
|
||||
|
||||
private fun transition(
|
||||
state: ArtifactState,
|
||||
to: ArtifactLifecyclePhase,
|
||||
from: ArtifactLifecyclePhase,
|
||||
): Result<ArtifactState> =
|
||||
if (state.phase == from) {
|
||||
Result.success(state.copy(phase = to))
|
||||
} else {
|
||||
Result.failure(
|
||||
IllegalStateException(
|
||||
"Invalid artifact transition: ${state.phase} → $to (expected $from)"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun transitionFromAny(
|
||||
state: ArtifactState,
|
||||
to: ArtifactLifecyclePhase,
|
||||
vararg validFrom: ArtifactLifecyclePhase,
|
||||
): Result<ArtifactState> =
|
||||
if (state.phase in validFrom) {
|
||||
Result.success(state.copy(phase = to))
|
||||
} else {
|
||||
Result.failure(
|
||||
IllegalStateException(
|
||||
"Invalid artifact transition: ${state.phase} → $to (expected one of ${validFrom.toList()})"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.correx.core.artifacts
|
||||
|
||||
import com.correx.core.artifacts.model.ArtifactLineage
|
||||
import com.correx.core.events.types.ArtifactLifecyclePhase
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ArtifactState(
|
||||
val phase: ArtifactLifecyclePhase = ArtifactLifecyclePhase.CREATED,
|
||||
val lineage: ArtifactLineage = ArtifactLineage()
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.correx.core.artifacts
|
||||
|
||||
object Module
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.correx.core.artifacts.model
|
||||
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.events.types.ArtifactLifecyclePhase
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed interface Artifact {
|
||||
val id: ArtifactId
|
||||
val sessionId: SessionId
|
||||
val stageId: StageId
|
||||
val schemaVersion: Int
|
||||
val createdAt: Instant
|
||||
val lifecyclePhase: ArtifactLifecyclePhase
|
||||
val lineage: ArtifactLineage
|
||||
val metadata: Map<String, String>
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.correx.core.artifacts.model
|
||||
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.events.types.ValidationReportId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ArtifactLineage(
|
||||
val parentIds: List<ArtifactId> = emptyList(),
|
||||
val relationships: List<ArtifactRelationship> = emptyList(),
|
||||
val validationReportIds: List<ValidationReportId> = emptyList()
|
||||
)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.correx.core.artifacts.model
|
||||
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.events.types.ArtifactRelationshipType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ArtifactRelationship(
|
||||
val sourceId: ArtifactId,
|
||||
val targetId: ArtifactId,
|
||||
val type: ArtifactRelationshipType
|
||||
)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.correx.core.artifacts.serialization
|
||||
|
||||
import com.correx.core.artifacts.model.Artifact
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
|
||||
val artifactModule: SerializersModule = SerializersModule {
|
||||
polymorphic(Artifact::class) {
|
||||
// concrete artifact subclasses registered here in future epics
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user