feat(flight): UNMAPPED_FIELD、SRVT/VIPF 明细与白名单内未映射留档(ACM2-89/99/100)

V5 迁移;集合段出现才写;白名单 7 类内未落态写入 UNMAPPED_FIELD,不扩白名单。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
windyboy
2026-09-21 15:54:55 +08:00
co-authored by Cursor
parent 54b383fa9d
commit 02b0b2f189
14 changed files with 464 additions and 13 deletions
@@ -45,6 +45,13 @@ class FlopProcessor(
val change = MergeChange(flid = payload.flid, scalars = payload.scalars, collections = payload.collections)
val next = FlightStateEngine.mergedState(current, change)
flightState.persistFullState(next, msgId = head.msgId, now = clock.instant())
flightState.persistAuxiliaryFlightFields(
head.msgId,
payload.flid,
payload.collections,
payload.unmapped,
clock.instant(),
)
msgEvents.insertAll(eventsFor(next, head.msgId, mapper, clock.instant()))
DomainOutcome(ApplyResult.Succeeded, listOf(projectionOf(next, mapper)))
}
@@ -117,6 +124,7 @@ class AdftProcessor(
if (current != null) {
val next = FlightStateEngine.mergedState(current, setOnly(record))
flightState.persistFullState(next, msgId = head.msgId, now = clock.instant())
flightState.persistSrvtVipfIfPresent(record.flid, record.collections, clock.instant())
msgEvents.insertAll(eventsFor(next, head.msgId, mapper, clock.instant()))
revived = next
}
@@ -148,6 +156,7 @@ class AdftProcessor(
FlightStateEngine.mergedState(current, setOnly(record))
}
val outcome = flightState.persistFullState(next, msgId = head.msgId, now = clock.instant())
flightState.persistSrvtVipfIfPresent(record.flid, record.collections, clock.instant())
if (outcome == PersistOutcome.DAY_GUARD_VIOLATION) {
// 运营日冲突是**协议级**问题:与 SCHD 同分类 —— 整笔回滚、不重试、交人工。
// 若按 INFRA 抛出去,会被当成暂时性故障白白重试到耗尽,并给出误导的错误类别。
@@ -0,0 +1,17 @@
package com.gzzn.omms.msgexchange.processing
import com.gzzn.omms.msgexchange.domain.flight.UnmappedFieldEntry
import com.gzzn.omms.msgexchange.infra.persistence.FlightStateRepository
import java.time.Instant
/** `SRVT`/`VIPF` 与未映射字段的落库辅助(与航班主事务同批调用)。 */
internal fun FlightStateRepository.persistAuxiliaryFlightFields(
msgId: Long,
flid: String,
collections: Map<String, List<Map<String, String>>>,
unmapped: List<UnmappedFieldEntry>,
now: Instant,
) {
persistSrvtVipfIfPresent(flid, collections, now)
replaceUnmappedForMessage(msgId, flid, unmapped, now)
}
@@ -167,12 +167,10 @@ class MessageProcessor(
}
}
// [G-SRVT-VIPF]SRVT/VIPF 段只保留在解码载荷里,尚未落明细表(清空语义待 Q2)。
// 计数 + 告警替代此前的静默丢弃;出现即证明真实报文携带该段,可作为定案依据。
// [G-SRVT-VIPF]段出现计数,供真实流量观测;明细落库见 `G-SRVT-VIPF`(缺席是否清除待 Q2)。
val unpersisted = unpersistedCollectionHits(decoded.body)
if (unpersisted.isNotEmpty()) {
counters.unpersistedCollectionSeenAdd(unpersisted)
log.warn("unpersisted collection(s) {} present msgId={} [G-SRVT-VIPF]", unpersisted, head.msgId)
}
// I3identity 仅首次绑定(head.identityKey == null);FAILED 重试不重绑
@@ -141,6 +141,7 @@ class ScheduleProcessor(
throw ProtocolViolation("operation-day guard violated flid=$flid")
else -> batchWritten++
}
flightState.persistSrvtVipfIfPresent(flid, record.collections, clock.instant())
events += eventsFor(next, head.msgId, mapper, clock.instant())
projections += projectionOf(next, mapper)
}