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:
windyboy
2026-09-08 15:44:48 +08:00
parent 48a27b26dd
commit 20aafb9ab0
8 changed files with 223 additions and 46 deletions
@@ -9,6 +9,8 @@ data class FlightNextState(
val collections: Map<String, List<Map<String, String>>>,
val stateVersion: Long,
val lastMessageId: String,
/** 本次迁移中被显式清除的标量/异常/文本键(仓储据此写 NULL 列;快照全量替换下无意义)。 */
val clearedKeys: Set<String> = emptySet(),
) {
fun toFlightFields(mapper: ObjectMapper = ObjectMapper()): FlightFields {
val out = linkedMapOf<String, String>()
@@ -14,6 +14,17 @@ object FlightStateEngine {
"GTDT", "CKDT", "CLDT", "PSDT", "CHDT", "DELY", "ABTM", "CHOT", "ROUT", "ERUT",
)
/** 1:0..1 单值异常结构:主表前缀标量列承载;显式 null 载荷 = 清除(v2 §3.2/§9.1)。 */
val EXCEPTION_KEYS = setOf("FDIV", "FRET", "FLAB")
/** 异常/单值载荷的显式清除形态:空串、null 字面量、空对象(Oracle 空串即 NULL 语义的显式来源)。 */
private fun isClearPayload(value: String): Boolean {
val trimmed = value.trim()
if (trimmed.isEmpty() || trimmed == "null") return true
val node = runCatching { mapper.readTree(trimmed) }.getOrNull() ?: return false
return node.isNull || (node.isObject && node.size() == 0)
}
private val SEQ_ATTR = mapOf(
"GTDT" to "GTNO",
"CKDT" to "CKNO",
@@ -46,6 +57,7 @@ object FlightStateEngine {
)
}
}
key in EXCEPTION_KEYS && isClearPayload(value) -> scalars[key] = ScalarCommand.Clear
else -> scalars[key] = ScalarCommand.Set(value)
}
}
@@ -62,12 +74,19 @@ object FlightStateEngine {
val baseCollections = current?.collections?.mapValues { it.value.toMutableList() }
?.toMutableMap() ?: mutableMapOf()
val baseVersion = current?.stateVersion ?: 0L
val cleared = mutableSetOf<String>()
commands.scalars.forEach { (key, cmd) ->
when (cmd) {
ScalarCommand.Unchanged -> Unit
is ScalarCommand.Set -> baseScalars[key] = cmd.value
ScalarCommand.Clear -> baseScalars.remove(key)
is ScalarCommand.Set -> {
baseScalars[key] = cmd.value
cleared.remove(key)
}
ScalarCommand.Clear -> {
baseScalars.remove(key)
cleared += key
}
}
}
@@ -96,6 +115,7 @@ object FlightStateEngine {
collections = baseCollections.mapValues { it.value.toList() },
stateVersion = nextVersion,
lastMessageId = messageId,
clearedKeys = cleared.toSet(),
)
}