feat(processing): 快照流程锁内复核收敛 + 异常显式清除语义 + 语义矩阵定稿 (ACM2-29 P2-5/M3)
- SnapshotFlow 收敛到 v2 §5 事务顺序:锁外仅解析校验;取得 PIPELINE_LOCK 后 锁内复核快照身份(last_message_id 重放短路)→ 锁内读代与当前航班态 → 计算 nextState → 写入 → CAS → 事件 → SUCCEEDED。锁内 CAS 失败属数据异常, 一律回滚 FAILED(INFRA),删除「版本号相同即视为重放」的推断路径(评审 F7/F8 收口) - M3/F4:异常对象(FDIV/FRET/FLAB)显式清除语义——null 字面量/空对象/空串 翻译为 Clear 命令;FlightNextState.clearedKeys 携带本次清除键;增量路径按 映射把前缀标量列置 NULL;库内清除后读视图无该键(线格式保持键缺失,显式 清除表达留待阶段 2 按下游契约定) - StubFlightSchd 增量分支改为 nextState 权威整体替换(引擎已合并当前态), 天然承载 Clear;flattenScalarsOnly 去除冗余循环(L2) - docs/flight-state-semantics.md:16 类结构语义矩阵定稿(P0-A 交付物落库) - 新测试:引擎 0 标记/异常清除/混排 fail-fast 单测;JDBC 异常清除回环用例 验证:MSGX_PG_PORT=5433 真实 PG ./gradlew test --rerun-tasks 105 用例 0 失败 0 跳过
This commit is contained in:
@@ -142,4 +142,40 @@ class FlightStateEngineTest {
|
||||
val next = FlightStateEngine.apply(current, commands, "m2", bumpVersion = true)
|
||||
assertEquals(emptyList<Map<String, String>>(), next.collections["GTDT"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exception null payload translates to explicit clear and apply records cleared key`() {
|
||||
val current = FlightNextState(
|
||||
"F1",
|
||||
mapOf("FLNO" to "CA100", "FRET" to """{"REID":"R1","RSN":"diverted"}"""),
|
||||
emptyMap(),
|
||||
3L,
|
||||
"m1",
|
||||
)
|
||||
val commands = FlightStateEngine.commandsFromFields("F1", mapOf("FRET" to "null"), snapshotReplace = false)
|
||||
assertEquals(ScalarCommand.Clear, commands.scalars["FRET"])
|
||||
|
||||
val next = FlightStateEngine.apply(current, commands, "m2", bumpVersion = true)
|
||||
assertFalse(next.scalars.containsKey("FRET"))
|
||||
assertTrue(next.clearedKeys.contains("FRET"))
|
||||
assertEquals("CA100", next.scalars["FLNO"])
|
||||
|
||||
// 非空载荷仍为 Set,且清空后的下一跳 Set 恢复键(clearedKeys 不跨消息携带)
|
||||
val restored = FlightStateEngine.apply(
|
||||
next,
|
||||
FlightStateEngine.commandsFromFields("F1", mapOf("FRET" to """{"REID":"R2"}"""), snapshotReplace = false),
|
||||
"m3",
|
||||
bumpVersion = true,
|
||||
)
|
||||
assertEquals("""{"REID":"R2"}""", restored.scalars["FRET"])
|
||||
assertFalse(restored.clearedKeys.contains("FRET"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exception clear accepts null literal empty object and empty string`() {
|
||||
for (payload in listOf("null", "{}", "")) {
|
||||
val commands = FlightStateEngine.commandsFromFields("F1", mapOf("FDIV" to payload), snapshotReplace = false)
|
||||
assertEquals(ScalarCommand.Clear, commands.scalars["FDIV"], "payload=[$payload] must be Clear")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+45
@@ -693,6 +693,51 @@ class FlightSchdJdbcPgTest {
|
||||
assertEquals(Triple("Z1", null, 1), after)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PG dialect - exception object explicit clear writes null columns and omits wire key`() {
|
||||
val day = "2026-09-07"
|
||||
val flid = "TEST_EXC_CLEAR"
|
||||
val engine = com.gzzn.omms.msgexchange.domain.flight.FlightStateEngine
|
||||
|
||||
// 1. 快照写入返航异常对象 → 主表前缀标量列
|
||||
repo.persistNextStates(
|
||||
day,
|
||||
listOf(
|
||||
engine.apply(
|
||||
null,
|
||||
engine.commandsFromFields(flid, mapOf("FRET" to """{"REID":"CA108","RSN":"diverted"}"""), snapshotReplace = true),
|
||||
"msg-exc-1", bumpVersion = true,
|
||||
),
|
||||
),
|
||||
snapshotReplace = true,
|
||||
)
|
||||
val stored1 = ds.queryOne(
|
||||
"SELECT fret_reid, fret_rsn FROM flight_schd WHERE flid = ?",
|
||||
{ ps -> ps.setString(1, flid) },
|
||||
) { rs -> listOf(rs.getString("fret_reid"), rs.getString("fret_rsn")) }
|
||||
assertEquals(listOf("CA108", "diverted"), stored1)
|
||||
|
||||
// 2. 增量显式清除(FRET="null")→ 列置 NULL,读视图无 FRET 键(评审 F4 修复闭环)
|
||||
repo.persistNextStates(
|
||||
null,
|
||||
listOf(
|
||||
engine.apply(
|
||||
repo.findNextStateByFlid(flid),
|
||||
engine.commandsFromFields(flid, mapOf("FRET" to "null"), snapshotReplace = false),
|
||||
"msg-exc-2", bumpVersion = true,
|
||||
),
|
||||
),
|
||||
snapshotReplace = false,
|
||||
)
|
||||
val stored2 = ds.queryOne(
|
||||
"SELECT fret_reid, fret_rsn FROM flight_schd WHERE flid = ?",
|
||||
{ ps -> ps.setString(1, flid) },
|
||||
) { rs -> listOf(rs.getString("fret_reid"), rs.getString("fret_rsn")) }
|
||||
assertEquals(listOf(null, null), stored2)
|
||||
assertNull(repo.findByFlid(flid)!!["FRET"])
|
||||
assertNull(repo.findNextStateByFlid(flid)!!.scalars["FRET"])
|
||||
}
|
||||
|
||||
private fun assertLegacyCollectionColumnsAllNull(flid: String) {
|
||||
val columnList = com.gzzn.omms.msgexchange.support.LEGACY_COLLECTION_STORAGE_COLUMNS.joinToString(", ")
|
||||
ds.queryOne(
|
||||
|
||||
Reference in New Issue
Block a user