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:
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user