feat: scaffold Micronaut+Kotlin service per ACMA-8 v4 architecture

- 4 modules (ingress/processing/delivery/reference) + codec + infra + jobs
- Flyway V2.0.0: six aux tables (PROC_STATE/MSG_EVENT/REF_DATA/REQ_TRACK/PUMP_JOB/FLIGHT_STATE)
- Lua: snapshot_replace (atomic cover+gen-diff-delete), batch_delete (3:30 sweep)
- config: env-externalized secrets, phase A/B switch, ACMA-8 param-table defaults
- logback: logstash TCP JSON + traceId MDC
- pure-logic tests: identity (I3), schd aggregation max-by-EVENT_ID (FIX #7)
- build verified: gradle compile+test green on JDK 25 (Micronaut 5.1 requires JVM 25+)

Migrated from legacy repo subdirectory per repo-strategy decision (separate repo
per service, org convention). Refs: ACMA-9, ACMA-8 v4, ACMA-6
This commit is contained in:
windyboy
2026-09-06 16:13:50 +08:00
commit b2f3b896cc
33 changed files with 1414 additions and 0 deletions
@@ -0,0 +1,56 @@
package com.gzzn.omms.msgexchange.nextgen.delivery
import com.gzzn.omms.msgexchange.nextgen.domain.EventStatus
import com.gzzn.omms.msgexchange.nextgen.domain.MsgEvent
import com.gzzn.omms.msgexchange.nextgen.domain.Targets
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
/**
* ACMA-8 流程 3 / 兼容矩阵 #7(FIX):同一 FLID 多次变更只发最新一态;
* v4 显式“组内取 EVENT_ID 最大”,不依赖集合遍历序。
*/
class SchdAggregationTest {
private fun ev(flid: String, eventId: Long, payload: String) = MsgEvent(
eventId = eventId,
target = Targets.KAFKA_SCHD,
partitionKey = flid,
payloadJson = payload,
state = EventStatus.PENDING,
)
@Test
fun `same flight collapsed to max EVENT_ID`() {
val pending = listOf(
ev("F1", 1, """{"FLID":"F1","v":"old"}"""),
ev("F2", 2, """{"FLID":"F2","v":"only"}"""),
ev("F1", 3, """{"FLID":"F1","v":"new"}"""),
)
val latest = SchdAggregation.latestPerFlight(pending)
assertEquals(2, latest.size)
assertEquals("""{"FLID":"F1","v":"new"}""", latest.first { it.contains("F1") })
assertEquals("""{"FLID":"F2","v":"only"}""", latest.first { it.contains("F2") })
}
@Test
fun `order-independent grouping yields same latest per flight`() {
val a = listOf(
ev("F1", 1, "old"),
ev("F1", 2, "new"),
)
val b = a.reversed()
assertEquals(
SchdAggregation.latestPerFlight(a).toSet(),
SchdAggregation.latestPerFlight(b).toSet(),
)
}
@Test
fun `wire shape is FLTR JSON array`() {
val payload = SchdAggregation
.latestPerFlight(listOf(ev("F1", 1, "a"), ev("F2", 2, "b")))
.joinToString(",", "[", "]")
assertEquals("[a,b]", payload)
}
}
@@ -0,0 +1,45 @@
package com.gzzn.omms.msgexchange.nextgen.processing
import com.gzzn.omms.msgexchange.nextgen.config.PipelineProps
import com.gzzn.omms.msgexchange.nextgen.domain.DecodedMessage
import com.gzzn.omms.msgexchange.nextgen.domain.MetaFields
import com.gzzn.omms.msgexchange.nextgen.domain.MsgKind
import org.junit.jupiter.api.Test
import java.time.LocalDate
import kotlin.test.assertEquals
/**
* ACMA-8 I3identity = SNDR|TYPE|STYP|SEQN;日边界含否集中可配(CONFIRM 矩阵 #11,默认关)。
*/
class IdentityTest {
private fun msg(seqn: Long, dttm: Long = 20260906120000) = DecodedMessage(
meta = MetaFields(sndr = "AODB", type = "FLOP", styp = "DELY", seqn = seqn, dttm = dttm),
kind = MsgKind.Flop("DELY"),
rawXml = "<MSG/>",
)
@Test
fun `identity is pipe-joined SNDR TYPE STYP SEQN`() {
val identity = Identity.of(msg(42), includeDayBoundary = false)
assertEquals("AODB|FLOP|DELY|42", identity)
}
@Test
fun `day boundary included only when configured`() {
val day = LocalDate.of(2026, 9, 6)
assertEquals(
"AODB|FLOP|DELY|42|2026-09-06",
Identity.of(msg(42), includeDayBoundary = true, day = day),
)
assertEquals(
"AODB|FLOP|DELY|42",
Identity.of(msg(42), includeDayBoundary = false, day = day),
)
}
@Test
fun `props default keeps day boundary off`() {
assertEquals(false, PipelineProps.Identity().includeDayBoundary)
}
}