Files
msgexchange-v2/docs/flight-state.md
T
windyboy 4449ee5dcc docs(flight-state): 按 2026-09-09 二轮评审修正设计文档,开放项锚定 ACM2-31 (ACM2-31)
依据 ACM2-31 二轮外部评审逐条修正,待协议确认项已对照 SIS_AODB_RMS V0.1 佐证:

- 标注约定:区分规范要求与现状/未决披露,杜绝混作依据
- 航班身份:FLID≠航班号,FDEL+ADFT 换名时 FLID 可能不同(规范 §3.18),代码共享独立事件经 MAID/TAID 关联
- FDAY 三日期概念区分(业务日期/快照覆盖日期/名单归属日期);跨日归属回迁列为未决(§3.3 约束 4)
- 规则 M 补成立条件标注(快照生成时点 + 投递顺序前提未逐条核实)
- 差删完整性前提引协议口径:单包不分段、RECS 记录数、完整快照注释(§3.16.1)
- 动态线集合全量/增量语义待逐类确认(CHOT CSNO 实为机位序号 §3.22.2),Replace 全集仅对快照线定案
- tombstone 澄清为自定义删除事件(非 Kafka 原生墓碑),删除版本/世代缺口开放
- 行锁表述纠正(跨实例同样生效,但不实现认领/选主/切换/FIFO);Oracle 空串=NULL 为既定行为,验证目标改为字段语义保真
- ACTT 实际时间口径(进港 ATA/出港 ATD §3.20),修正示例字段名;FLOP 29 类改为规范 §3.19–3.43 实数;99 上限标注协议出处
- 语言修订:主泵/数据一致性比对/JSON 数组/整态替换措辞等;错误矩阵每类错误单一默认路径
- 开放项(回迁防护/动态集合语义/删除世代/幂等键/FIFO 参数/一致性读/TAOP 语义核对等)归集 ACM2-31
2026-09-09 10:47:03 +08:00

48 KiB
Raw Blame History

航班运行数据接入与当前状态管理设计

本文是航班状态接入与当前态存储的唯一设计与架构规范文档:统一界定系统职责、存储模型、字段语义、状态流转与跨模块不变量。已定决策与演进红线以正文为准;未完成项与开放风险在各节显式标注。

标注约定:正文未加标注的段落是规范要求;凡标注「开放项 / 未决 / 已知偏差 / 待补 / 验收目标 / 现状」的段落,是对当前实现或未定设计的如实披露,不是已交付事实,两类内容不得混作依据。

阅读速览:专用数据与设计逻辑

专用数据与术语

数据 / 术语 含义与用途
SIS / AODB SIS 是 AODB(机场运行数据库,上游业务权威)向各子系统分发数据的接口规范(SIS_AODB_RMS V0.1);本系统从共享 MySQL 信箱接收 AODB 报文。共享信箱是接入边界,本地数据库是本模块对外提供当前状态的唯一读取源,不是高于 AODB 的业务权威。
FLID AODB 分配的航班唯一 IDNumber(1-12),规范 §3.16.2);主表一行对应一个 FLID,明细、状态事件及删除通知均按它归属航班。FLID ≠ 航班号(FLNO 是标量列):关键键字段(航司代码、航班号、航班日期、航班时间、进离港标识)变更时,AODB 以「删除事件 FDEL + 新航班事件 ADFT」重新指名,两个事件的 FLID 可能不同(规范 §3.18);主航班与代码共享航班各有 FLID、事件分别发送,经 MAID/TAID 关联。
FLIGHT_SCHD + 9 张明细表 一个航班的完整权威态:主表保存标量、异常对象前缀列及文本字段;明细表保存 10 类变长集合,其中 ROUT/ERUT 共用路线表。展示视图只是只读投影。
FDAY(名单归属运营日) 当前负责该航班成员管理与差删的日计划快照所覆盖的运营日(三个日期概念中的「名单归属日期」,区分见 §3.2),不是报文接收日或落库日,也不承诺等同于业务运营日。NULL 表示尚未被任何日计划代收录,不表示航班没有运行日;跨日归属由整包快照更新,动态事件不修改。
SCHD_GEN / SCHD_GEN_FLID 每个运营日的快照账本与当前成员名单。成员资格以名单为准;差删先比较新旧名单,再用当前 FDAY 防止误删已迁往其他运营日的航班。
VERSION / STATE_VERSION 前者是同一运营日的快照版本,每次新快照推进;后者是单个航班的状态版本。运营日、快照版本、航班状态版本是不同维度。
identity_key / LAST_MESSAGE_ID 前者按“发送方|类型|子类型|序号”识别重复业务消息;后者追踪最近一次写入消息,其中快照账本的该字段用于最近一条快照的重放短路,不能拦住任意历史快照。
ORDINAL / SOURCE_SEQ 前者是明细输入顺序(从 1 起),后者保存报文中的源序号。源序号可空、不唯一,资源号也不是去重依据,不能据此把多条分配记录合并为一条。
SCHDDNLD / RESP / ADFT 日计划相关报文。DNLD、RESP 按整包快照换代并差删;ADFT 是单航班临时增量,不换代、不差删、不完成请求。当前仅 DNLD 接入,RESP/ADFT 及请求闭环仍有未决项。
FLOP / GTDT FLOP 是字段级航班运行事件;规范 §3.19–3.43 定义了 25 类航班级事件(AODB→RMS 事件总计 44 类,§3.13.44),GTDT(§3.34)是其中的登机门更新。当前仅接入 GTDT;动态事件只表达所携带字段的变化。
REQ_TRACK / COUTMSGS 前者登记请求及其应答、超时状态,后者承担请求出站 outbox;出站发送与超时重发尚未实现。
PROC_STATE / BACKFILL_TODO 前者记录消息处理状态,后者记录共享信箱回填补偿。回填意图与业务终态同事务持久化仍是待补齐目标。
KAFKA_SCHD / KAFKA_MSG / tombstone 分别表示航班整态投影、变更通知、删除通知。整态投递可合并同航班的中间版本,消费端按整态替换处理、不得按补丁合并。本文 tombstone 指自定义删除事件:载荷仅含 FLIDDELETED 标记、value 非 null不是 Kafka 日志压缩语义的原生墓碑消息,Kafka 不会对其执行压缩删除。

集合与异常、文本字段的逐项映射见第 6 节;表结构职责与代际记账见第 3 节;一个运营日的数据全景、历史版本不留存决策与跨运营日清理(开放项)见 §3.5/§3.6。

设计逻辑

  • 提交:单活动主泵(即消息处理主循环,下同)按 FIFO 推进;航班状态、快照账本、outbox 与处理终态同事务提交,回填和投递在提交后独立重试。
  • 合并:快照与动态事件均保留未出现的字段,显式清除才清空;同日快照覆盖其携带字段的已有值。集合按整组替换,保留全部条目及顺序。
  • 记账SCHD_GEN 每运营日一行,记录最近成功提交的快照版本和消息 ID;SCHD_GEN_FLID 每运营日保存该版快照的完整航班名单,新版提交时替换旧名单。
  • 差删:从旧名单中找出新快照不再包含的航班,仅删除当前 FLIGHT_SCHD.FDAY 仍等于该运营日的记录。未入代航班和已迁往其他日的航班保留,见 §3.3 示例。
  • 投递KAFKA_SCHD 按航班聚合为最新整态,删除发 tombstone;KAFKA_MSG 仅通知变化,两个主题不保证先后顺序。

1. 系统定位与核心不变量

本系统作为消息交换与航班运行状态汇聚核心:从共享 MySQL 信箱接入 SIS(AODB)报文,按报文语义把报文合并进本地权威的"航班当前状态",再把状态变化以标准化领域事件发往 Kafka。

系统坚持三条架构底线:

  • 单一权威状态(Single Source of Truth)。 本地数据库是本模块对外提供当前状态的唯一读取源;共享 MySQL 信箱与 Kafka 均为本地提交之后的外围边界,各自独立重试,任何外围故障都不会让已提交的状态回滚或重复计算。它不是高于 AODB 的业务权威——上游业务事实以 AODB 为源,本模块是其在本系统范围内的当前态投影。
  • 单活动主泵(Single Active Pump)。 生产部署约束为同一时刻仅一个实例执行信箱消费与状态推进。事务行锁可以串行化所有遵守该锁协议的数据库事务(跨进程、跨实例同样生效),但行锁本身不能实现消息认领、主实例选举、故障切换或全链路 FIFO——跨实例安全由「单活动实例」部署约束保证,而非行锁。
  • 单提交事务(Single Commit Transaction)。 状态、outbox 事件、处理终态(PROC_STATE)在同一个本地事务内落库,全部成功或整体回滚。

职责边界。 本模块只同步上游报文并维护当前态,不推导业务状态:取消、延误、备降、返航等运行状态由上游报文给定(对应 FDEL/CNCL/FDIV/FRET 等事件,规范 §3.27–3.33),本模块不做状态机推导;登机、登机结束等旅客服务状态不在接入范围。实际时间取 SIS §3.20 口径——进港为 ATA、出港为 ATD 的单值时间(本地列 ACTT),不区分 A-CDM 建模的落地/入位/离位/起飞等多节点;进出港过站关联按到港记录携带的 TAOP/TAFL/TAID 原样保存(见 §3.2)。

处理按信箱 FIFO 严格有序推进,按业务身份幂等去重,失败按指数退避重试。

数据库口径:现场目标库为 Oracle 11g;当前实际接通并作为验证基准的只有 PostgreSQL,本文描述 PostgreSQL 实现细节。

2. 关键架构决策

架构决策 理由与边界
状态、快照代、处理终态与 outbox 同库提交 消除 Redis 状态与数据库终态跨库双写窗口;Redis 等缓存不参与动态状态权威。
当前验证基准为 PostgreSQL,现场目标库为 Oracle 11g 单次部署只绑定一个内部权威库;Oracle 尚未完成全链路适配,不可作为运行时可用配置开关。
航班标量主表 + 9 张关系明细表 完整保留 10 类变长集合的输入顺序、源序号及多条资源/操作记录,不按展示槽位截断。
异常对象保留主表前缀列;SRVT/VIPF/MAFL 保留文本列 既有载体可承载时不新增抽象或关系表;其读写语义需以回归证明。
单活动主泵,事务内 PIPELINE_LOCK 行锁 行锁串行化遵守锁协议的数据库事务(跨实例同样生效),但不实现消息认领、FIFO 或跨实例协调;跨实例安全依赖单活动实例部署约束。
外部回填与事件投递按幂等重试处理 不使用跨库分布式事务;回填意图与业务终态同事务持久化是待补齐目标(见第 8 节)。
展示视图仅为只读投影 仅展示前序资源(前两个登机门/柜台等),不作为全量权威状态或数据一致性比对输入。

废弃方案与架构约束。 系统明确放弃:固定 GATE1/GATE2 式槽位作为权威、"超界数据静默丢弃"、"零子表为架构约束"、"存储模型决定零方言迁移成本"、以及脱离全量备份前提的 RPO=0 承诺。历史决策论证与撤销记录见 决策历史

3. 存储:标量主表 + 9 张明细表

3.1 为何不采用固定槽位

航班资源本质是变长集合,数量因航班、机型与保障安排而异。SIS 规范 §2 资源限制表给出协议上限:登机门、值机柜台、行李转盘每航班各 99,计划机位 9、当前机位 1,实际机位移动数量不限;规范未给出数量下限,本设计不做数量假设。ACM2-29 曾采用"全标量宽表、零子表"GATE1/GATE2 式固定槽位),当日即被推翻——槽位会截断数据,而设计约束要求保留重复资源的全部属性、输入顺序与源序号。结论:标量进主表,集合进明细表。

3.2 表清单

职责
FLIGHT_SCHD 主表,一行一 FLID:标量字段、异常前缀列、SRVT/VIPF/MAFL_TEXTFDAYSTATE_VERSIONLAST_MESSAGE_ID 与时间戳。
8 张集合明细表 flight_gate/flight_checkin/flight_belt/flight_stand_plan/flight_chute/flight_delay/flight_bridge_op/flight_chock_op;当前主键 (FLID, ORDINAL)
FLIGHT_ROUTE_POINT 第 9 张明细表;ROUT/ERUT 两类路线共用,以 ROUTE_KIND 区分。
SCHD_GEN / SCHD_GEN_FLID 快照代际记账,见 3.3。
PIPELINE_LOCK 单写者行锁:每个处理事务先锁 FLIGHT_SCHD_WRITER 行;缺失时两事务并发读写同一航班会互相覆盖。
BACKFILL_TODO 回填补偿待办,见第 8 节。
FLIGHT_SCHD_DISPLAY PG 展示视图(前两个登机门/柜台、首个转盘/延误、资源数);仅为投影,非状态来源。

9 张明细表承载 10 类集合——路线表一张覆盖两类。

FDAY 的语义。 涉及「日期」的三个概念必须区分:航班业务日期(该航班实例业务上归属哪一天、以什么口径确定——模型不保存,ADFT/动态新增行 FDAY=NULL 正是因为模型不为未上计划的行保存业务日期)、快照覆盖日期(本份快照查询/发布的运营日 D)、名单归属日期(当前由哪一天的快照负责其成员管理与差删)。FLIGHT_SCHD.FDAY 保存的是第三者,由整包快照赋值,与接收日、落库日无关。三者在本协议下通常同值,但那是快照接管规则的结果,不是定义使然。动态事件保留 FDAY;动态新增或 ADFT 新增航班尚未被收录时为 NULL,后续被快照收录再赋值。NULL 不表示航班没有运行日,其清理规则见第 7 节。

过站关联与报文排序(协议事实)。 到港记录携带 TAOP/TAFL/TAID——离开当前机场的过站连接航班的承运人代码/航班号/AODB ID,仅到港有效(§3.16.2);SCHD 报文内离港航班先于到港航班、代码共享航班跟在主航班之后(§3.16.1 排序注释)。注意:V1.1 迁移注释把 TAOP/TAFL/TAID 标注为「实际承运」,与规范的「过站连接」语义不符,待核对修正(ACM2-31)。

3.3 快照记账:每运营日一份版本记录、一份成员名单

记账单位是运营日,同一天可以多次更新。 例如 9 月 9 日的日计划先后收到三份快照,对应同一运营日的版本 1、2、3。“换代”指替换该日的当前版本及名单,不是进入下一个自然日。两张账本表记录当前版,不保存各历史版本的完整航班状态。

三份数据各记什么

数据 记录粒度 内容 用途
SCHD_GEN 每运营日一行 FDAYVERSIONLAST_MESSAGE_ID 记录该日最近成功提交的快照版本及消息;版本用于 CAS,消息 ID 用于最近一条快照的重放判断。
SCHD_GEN_FLID 每运营日、每成员航班一条记录 运营日与 FLID 的对应关系 保存该日当前版快照包含的完整名单,作为下一次比较的旧名单;新版提交时整体替换。
FLIGHT_SCHD FLID 一行 航班当前状态及 FDAY 保存快照与动态事件合并后的状态;FDAY 表示当前归属,差删时据此排除已跨日迁移的航班。

SCHD_GEN.VERSION 是日计划快照版本;FLIGHT_SCHD.STATE_VERSION 是单航班状态版本。动态事件可以推进后者,但不更新快照版本或成员名单。

一份新快照如何更新账本

设快照覆盖运营日为 D,消息 ID 为 M,航班名单为 N。以下动作与航班状态、outbox、处理终态在同一事务内完成:

  1. 锁内读取 D 的账本和旧名单 O。首次快照没有旧名单,按空集比较,成功提交后版本为 1。
  2. 若 M 等于账本的 LAST_MESSAGE_ID,按最近快照重放短路:不改航班状态、不替换名单、不推进版本、不新增事件;处理成功并执行提交后回填。
  3. 计算候选差删名单 O N。逐项检查当前航班主行,只保留 FLIGHT_SCHD.FDAY = D 的航班作为实际删除对象。
  4. 合并写入 N 中各航班的状态,将它们的 FDAY 设为 D;删除实际差删对象的主行、全部明细,并登记 tombstone。
  5. 将 D 的 SCHD_GEN_FLID 名单整体替换为 N;以旧版本为条件执行 CAS,将 VERSION 加 1,并把 LAST_MESSAGE_ID 更新为 M。版本不符则整个事务回滚。
  6. 登记状态事件和处理成功状态,提交。任一步失败,旧版本、旧名单和旧航班状态一并保留,不产生半份快照。

因此,版本计数对应成功提交的新快照,不是接收次数;失败重试和最近快照重放不额外加版本。

示例:同日替换与跨日保护

下表以 A、B、C 表示三个 FLID,日期均为 2026 年。

顺序 输入 SCHD_GEN 提交后 SCHD_GEN_FLID 提交后 航班状态变化
1 M19/9 快照 {A, B} 9/9:版本 1,消息 M1 9/9{A, B} A、B 写入,FDAY=9/9
2 M29/9 快照 {A, C} 9/9:版本 2,消息 M2 9/9{A, C} 候选差删 {B};B 仍属 9/9,删除 B 并登记 tombstone;A 更新,C 新增。
3 M39/10 快照 {C} 9/10:版本 1,消息 M39/9 不变 9/10{C}9/9 仍为 {A, C} C 被 9/10 快照收录,主行 FDAY 改为 9/10。
4 M49/9 快照 {A} 9/9:版本 3,消息 M4 9/9{A}9/10 仍为 {C} 候选差删 {C};C 当前属 9/10,保留主行和明细,不发删除事件。
5 M4 再次进入快照重放检查 不变 不变 命中 9/9 的最近消息 ID,状态和事件不重复写入。

第 3 步说明两者为何都需要:旧日名单记着上次快照包含 C,主行 FDAY 记着 C 已归属新日。 名单负责找出消失者,主行归属负责判断能否删除。仅凭旧名单会误删跨日航班;仅扫描主行 FDAY 则丢失了“上次快照包含谁”的依据。

重放的三种去向。 同一条报文重新到达有三种情形,去重与重放守卫各管一层:

  • 入口去重:同一消息首次处理时已绑定 identity_key(见 4.1),再次从信箱到达按重复 SKIPPED
  • 同一消息重试:事务内失败退回重试时,消息未提交过,正常走全流程;提交成功后因入口去重不会重算。
  • 人工重放:历史快照报文(其消息 ID ≠ 当前 LAST_MESSAGE_ID)不会命中重放短路;若已清空入口身份记录而被人工重放,本设计不承诺旧状态不被覆盖——人工重放须携带明确的版本/业务日期策略方可放行(见第 7 节)。

需明确的实现约束:

  1. 明细表不声明外键。 清理动作由仓储显式执行,不依赖数据库级联。SOURCE_SEQ 可空且不唯一,仅为记录性质——逐条定位更新的 Apply 协议本期不实现(无生产调用方,且定位键不可靠)。
  2. 差删按代圈定范围,不扫 FDAY 目标运营日 = 本份快照覆盖的运营日。最终删除集合 =(旧名单 − 新名单)∩ 当前仍 FDAY=目标运营日的航班;仅对该集合显式删除明细行与主行。FDAY=NULL、已归属其他运营日、未在旧名单中的航班均不受本次差删影响;删除与名单替换同事务提交。
  3. 演进纪律。 不为尚无消费者的查询新增类型化时间列、摘要、额外载荷表或索引;时间类字段先保留原始字符串,字段与长度以已应用迁移为准。
  4. 跨日归属回迁(未决)。 上述流程把新名单 N 中各航班的 FDAY 一律置为 D:若某航班已被更晚运营日的快照接管(FDAY=D'),一份迟到的旧日快照再次收录它时会把 FDAY 改回 D,并以旧值覆盖新日的合并态(示例:C 已属 9/10,迟到的 9/9 快照仍含 C → C 被改回 9/9)。现行「跨日保护」(约束 2)只拦截差删删除,不拦截归属回写与字段回退。归属迁移的时序判断是开放项(ACM2-31);定案前本设计不承诺 FDAY 单向迁移。

3.4 已知待修

路线表当前主键为 (FLID, ORDINAL):同一航班 ROUT 与 ERUT 各自从序号 1 起写,二者共存即冲突。计划通过新迁移改为 (FLID, ROUTE_KIND, ORDINAL)(见 ACM2-30)。

3.5 一个运营日的数据全景

运营日 D 的快照提交后,库内构成如下整体视图:

数据 当天形态
SCHD_GEN 一行:D 的最新快照版本与 LAST_MESSAGE_ID,同日内被各次快照顺序覆盖。
SCHD_GEN_FLID 当前版快照的完整名单 N,每次新快照整体替换。
FLIGHT_SCHD 被 D 收录的航班各一行(FDAY=D),入库后可继续被动态事件推进;跨日被新快照收录时 FDAY 改写。
9 张明细表 FDAY=D 航班的集合行,随每次写入整组替换。
tombstone / outbox 差删航班与整态投递事件,与上述状态同事务落账。

历史版本不留存(已定决策;下列为四项独立决策,不可互相推论)。不保存状态历史:账本只记当前版,同日快照版本 1→2→3 的中间完整状态不保存;② 不逐条投递中间版本KAFKA_SCHD 只按当前整态输出;③ 不提供历史查询:无此接口;④ 不保存原始报文:由共享信箱侧保留策略决定,不是本模块承诺。①② 的理由:权威态唯一,差删与 CAS 只依赖当前名单与版本,消费端按 (FLID, STATE_VERSION, UPDATED_AT) 去重也不依赖中间版本。后果:当日被覆盖的中间态不可从库中恢复;「可凭重放原始报文重建」依赖信箱保留期限、处理顺序与解析器版本,只是理论可能性,不作为恢复承诺。需要中间态历史线或审计留存时须另行设计,本设计不承担。

3.6 跨运营日生命周期

  • 差删是历史航班出场的唯一常规路径:旧名单 − 新名单 ∩ 仍属本日的航班,同事务删除主行、明细并登记 tombstone。
  • 被新运营日快照接管的航班,其集合经整组替换,仅保留最新值、不保存被替换记录(同 3.5 决策)。
  • FDAY=NULL 未归属行的清理见第 7 节;已归属历史日的数据没有对应的清理规则。
  • 开放项(未决):旧运营日的 SCHD_GEN / SCHD_GEN_FLID 行随日期无限累积,当前无定期清理或归档设计;快照长期停发时历史日留存航班也无兜底清理。清理策略须待真实保留期限需求定案后补齐,清理动作须经 FLIGHT_SCHD 与账本的一致性校验,不得破坏差删依赖的"旧名单 + 主行 FDAY"双依据。跨日归属回迁的时序防护同样未决(§3.3 约束 4)。

4. 状态的两条来路

状态仅有两条来源,各成一条处理线:

  • 请求/下载线(基准)。 建立指定运营日航班的基准全量。本系统向 AODB 发送日计划请求,AODB 返回 SCHD 报文——该运营日的全部航班——快照整体落库;该运营日未再出现的航班被差删。
  • 动态线(运行事件)。 跟踪运行中的持续变化——实际时间、登机门开关、值机柜台、行李转盘、延误、靠撤桥、轮挡等。FLOP 字段级事件在规范 §3.193.43 共 25 类(AODB→RMS 事件总计 44 类),当前仅接入 GTDT 一类;单条报文只变更一个航班的一类资源,其余字段不受影响。

两条线共用同一入口与同一套事务/失败规则:进入事务后统一为"锁内读态 → 合并 → 写库 → 事件入 outbox → 终态 → 提交 → 回填"。

信箱队头 → 解码 → 绑定身份(去重)→ 按 Handler 路由
    ├─ SCHD-DNLD(已接通)────────→ 请求/下载线
    ├─ SCHD-RESP / ADFT(未接通)─→ 请求/下载线
    └─ FLOP 事件(已接 GTDT)────→ 动态线

图中「已接通/未接通」为适用范围标注,不是完成度承诺:「已接通」仅表示该类型已进入本设计的路由与事务链,解析、出站与应答闭环等环节是否齐备以 §4.2 各处未决标注为准。

4.1 公共入口(所有消息必经)

  1. 排队。 信箱消息与定时任务共用一个 FIFO 队列,主泵只处理队头;队头失败按退避等待,重试超限或队头滞留超过时限 → DEAD。一条异常报文会阻塞其后所有航班与定时任务:最大阻塞时限、隔离/旁路规则与告警阈值是运维未决参数(ACM2-31)。
  2. 解码。 报文非法 → DEAD(不重试);解码器缺陷类错误 → FAILED(退避重试)。
  3. 绑定身份。 幂等键 = 发送方|类型|子类型|序号,仅首次处理时绑定;该键已被其他消息占用 → SKIPPED(重复),自身照常回填共享信箱。协议中 SEQN 为发送方设置的 6 位序号(1–999999,规范 §2 元数据),其唯一范围与重置/回绕策略规范未定义;上游序号重置、重启后重复,以及同身份不同载荷时的处理均为未决项,当前实现仅按键判重(ACM2-31)。
  4. 路由。 按报文类型定位 Handler;无注册 Handler → FAILED(可重放,不写终态)。

4.2 请求/下载线:基准全量的建立

SIS 循环:本系统向 AODB 发送日计划请求 → AODB 返回 SCHD。三个子类型进入本流程时的规则:

子类型 FDAY 来源 是否换代 是否参与差删 可完成请求
DNLD 快照覆盖的运营日 是(整包成员差) 是(运营日匹配时)
RESP 快照覆盖的运营日 是(整包成员差) 是(且要求对应 REQ_TRACK 存在)
ADFT 无(保持/置为 NULL

ADFT 为单条临时航班增量:只增改本条 FLID(整行整集合写入),不触发换代与整包差删,也不算完成任何请求。ADFT 报文不携带运营日,本行也不单独承载它:FDAY=NULL 仅表示该航班未被任何日计划代收录(见 §3.2)——临时航班在现实中有运行日,但模型不为未上计划的行保存该日期。同 FLID 后续被整包快照收录时由快照接管(见 §3.2 FDAY 语义)。

请求登记于 REQ_TRACK(同类并发=1,新请求把旧请求强制置 EXPIRED),出站经 COUTMSGS outbox——出站发送与超时重发尚未实现。

收到下载后的落库步骤:

  1. 解析整包并校验,单包上限 10000 个航班(超限 → DEAD;协议口径:SCHD 单包不分段、不超过 CIIMS 10MB 上限,RECS 为 1–4 位记录数,§3.16.1)。差删的完整性前提:「本包 = 该运营日完整名单」由协议保证——规范注释明确日计划是「AODB 最新数据的完整快照」(§3.16.1 注释 2)、单包不分段、RECS 给出包内记录数(RECS=0 即空名单);RECS 与实收航班数的核对、同包重复 FLID 的处理为待补校验(ACM2-31)。
  2. 进入事务(先锁 PIPELINE_LOCK 行):
    • 重放短路:该消息已提交过(与 SCHD_GEN.LAST_MESSAGE_ID 相同,记账见 3.3)→ 直接 SUCCEEDED,不加版本、不发事件。
    • 算成员差:新快照相对上代消失的航班进入差删名单。
    • 合并写入:锁内逐航班读当前态 → 合并 → 整集合替换,FDAY 置为该快照覆盖的运营日(不是报文接收或落库日期)。
    • 差删:删除差删名单内航班的明细与主行(规则见 3.3);每个被删航班同事务登记一条 tombstone 事件入 outbox(载荷见第 8 节)。
    • CAS 推进日代版本:版本不符则整体回滚 → FAILED(INFRA) 退避。
    • 应答匹配:与 REQ_TRACK 同事务联动——关联键 =(运营日, 发送方, 请求类型),完成该组合下最新一条 PENDING 请求(协议无请求关联号:RQFD 请求仅含时间范围筛选字段,RESP 也不回指请求报文,此关联键是规范约束下的替代方案,与「同类并发=1、新请求强制 EXPIRED 旧请求」共同收敛错配风险);迟到(已 EXPIRED)或重复应答照常执行快照落库(数据仍权威),但不重复置完成、不报错;DNLD 仅在运营日匹配时完成请求,不按类型隐式完成。
    • 每个航班一条 KAFKA_SCHD 事件入 outboxPROC_STATESUCCEEDED;提交。
  3. 提交后回填共享信箱(失败落 BACKFILL_TODO,见第 8 节);重放短路同样执行回填。

同日重快照的权威规则(M)。 整包快照是同日最高权威:两次快照之间已应用的动态运行更新(实际时间、登机门、柜台等),在重叠字段上以快照值覆盖(含快照显式清空);动态报文更新当前状态,其与快照的冲突按本规则处理。快照省略的字段按「整行替换混合规则」(第 5、7 节)保留合并后当前值,不属于覆盖例外的组成部分。

规则 M 的成立条件(未决)。 本规则隐含两个上游前提:① 快照内容是 AODB 生成/发送时点的完整最新数据(§3.16.1 注释 2 支持生成时点口径);② 两条线的投递顺序与上游发送顺序一致(共享信箱 FIFO + 同一发送方 SEQN 单调)。若前提 ② 不成立(快照生成后延迟投递、SEQN 按类型分段编号等),重叠字段会被旧值回退——例如 10:00 生成的快照 10:03 才入队,会把 10:02 已处理的登机门更新改回旧值。前提 ② 尚未从协议条款逐条核实,把规则 M 作为对外承诺前须经设计评审确认(ACM2-31)。

示例:一次 DNLD 快照的落库。 示例中的 CA100、MU201、CZ300 均为 AODB 分配的 FLID 值(写成形似航班号仅为可读;FLNO 航班号是主表标量列,与 FLID 无恒定关系)。报文为 AODB 下发的运营日 2026-09-09 的整包快照,含 CA100、MU201 两个航班(FDAY 取快照覆盖的运营日 2026-09-09,与该报文何时接收、何时落库无关);库内该运营日的代(SCHD_GEN)当前成员为 CA100、CZ300。进入事务后依次发生:

步骤 动作 结果
重放检查 该消息 ID 等于日代 LAST_MESSAGE_ID 否,继续
算成员差 上代成员 {CA100, CZ300} 新快照 {CA100, MU201},∩ 当前仍 FDAY=2026-09-09 差删名单 = {CZ300}
合并写入 CA100 已存在 → 锁内读态、整集合替换,FDAY=2026-09-09;MU201 新航班 → 先插主行再写明细 两航班状态就位
差删 删 CZ300 主行 + 全部明细行 CZ300 消失
CAS 推版本 SCHD_GEN 版本 7 → 8;不符则整体回滚 FAILED(INFRA) 版本推进
事件 CA100、MU201 各一条 KAFKA_SCHD 入 outbox
终态 PROC_STATESUCCEEDED,提交 提交后回填共享信箱

此后 Dispatcher 按 FLID 聚合这批 KAFKA_SCHD 发出(见第 8 节)。

覆盖范围与未决。 本流程的设计范围当前仅覆盖 DNLD 子类型;RESP 与 ADFT 的 Handler、「请求 → 应答 → 标记完成」闭环为未决设计项,未覆盖类型的处理按 FAILED(UNSUPPORTED) 对齐。参考数据请求(RQRD;参考数据事件见规范 §3.1–3.15)同样登记于 REQ_TRACK,但应答落参考库、由 RequestCoordinator 处理,与航班状态无关。

4.3 动态线:运行事件的落库(以 GTDT 为例)

  1. Handler 纯函数决策。 输入 = 受影响航班当前态 + 报文,输出 = 字段变更与待发事件;此步不算账、不落库。每类资源的语义在 Handler 内固定,如 GTDT 为登机门集合整体替换(GTNO=0 表示清除)。
  2. 进入事务(先锁 PIPELINE_LOCK 行):
    • 每个受影响航班:锁内重读最新态 → 合并字段变更 → 整集合替换写入,保留既有 FDAY(保留该行所属的运营日/代;动态事件不携带也不推断运营日,跨运营日的归属更正只由请求/下载线的快照接管完成,见 §3.2)。
    • 事件(KAFKA_MSG 变更通知 + KAFKA_SCHD 状态推送)入 outboxPROC_STATESUCCEEDED;提交。
  3. 提交后回填共享信箱(同上)。

示例:一条 GTDT 报文如何变更登机门。 报文(FLOP 增量,仅携带登机门,其余字段未出现):

META: SNDR=AODB, TYPE=FLOP, STYP=GTDT, SEQN=42
FLOP: FLID=CA100
  GTDT GTNO=1  GATE=A1  GTYP=D
  GTDT GTNO=3  GATE=B2  GTYP=I

库内 CA100 当前登机门为 G28(序号 1)、G33(序号 2)。处理过程:

  1. 解析字段集GTDT = [(GTNO=1, GATE=A1, GTYP=D), (GTNO=3, GATE=B2, GTYP=I)]。其余键未出现 = 不动——延误、机位、时间一概不碰。
  2. 翻译命令GTDT 出现 → Replace,整组替换为这 2 条。
  3. 锁内合并:读当前态 → 整组替换 → 版本 +1、记 LAST_MESSAGE_ID
  4. 写库flight_gate 中 CA100 旧 2 行删除,按输入顺序插入 2 行(ORDINAL=1,2SOURCE_SEQ=1,3);GTDT 无标量列,主表仅推进版本/消息/更新时间。
  5. 事件KAFKA_MSG 变更通知;KAFKA_SCHD 载荷 "GTDT":[{"GATE":"A1","GTNO":"1","GTYP":"D"},{"GATE":"B2","GTNO":"3","GTYP":"I"}]——JSON 数组、不做二次字符串编码,键按字典序。
  6. 提交后:回填共享信箱。

读回时按 ORDINAL 组装主行与 flight_gate 2 行为权威态;展示视图取前两个登机门 A1、B2。

清除与拒绝语义示例:

  • <GTDT GTNO="0"/> 单条且全 0 → Clearflight_gate 行删空,KAFKA_SCHD 中 GTDT 键消失。
  • [{"GTNO":"1","GATE":"A1"},{"GTNO":"0"}]:0 与正常条目混排 → 非法结构,整条消息失败,不猜测、不截断。
  • 字段未出现 ≠ 清除:仅收到一条 DELY 报文时登机门不受影响。
  • 异常键的空值才构成 Clear:FDIV 出现为 {} → Clear → FDIV_* 列全部置 NULLSRVT 为空串/null 目前不算 Clear(见第 6 节),只有显式 Clear 命令能清列。

5. 状态变更实现(两条线共用)

所有状态变更统一经加工链:报文 → 命令 → 合并出新状态 → 写库

5.1 数据统一形态

内存中一个航班的状态即一个模型(FlightNextState):一组标量键值 + 10 类集合(每类为一组条目,每条目一组键值)+ 版本/消息追踪字段。主表存标量、明细表存集合,列映射固定(如 GTDT 的 GATE/GOTM/GCTM → flight_gate 对应列);读回时按 ORDINAL 组装集合,数据库权威态 = 主行 + 明细行。

5.2 第一步:报文到命令

当前 Handler 返回字段集,commandsFromFields 将其翻译为命令;仓储仍会解析异常/文本 JSON,显式命令尚未贯通全部边界。

命令 标量 集合
Unchanged(未出现) 不修改 不修改
Set(value)(出现) 覆盖
Clear 删键,物理列置 NULL 删除全部明细行
Replace(list) 同事务 DELETE + 批量 INSERT 整集合替换
Apply(item, sourceSeq) 引擎内预留:按首个 SOURCE_SEQ 匹配更新,否则追加;无生产调用方,尚非已确认协议

规则:

  • 字段未出现 = 不动。 增量报文只携带变化部分,其余不受影响。
  • 标量出现 = Set(覆盖)。 异常键(FDIV/FRET/FLAB)载荷为空串、null、空对象时 = Clear。
  • 集合出现 = Replace。 整组替换为报文给定条目。条目序号属性(如 GTNO)为 "0" 是协议显式清除标记:仅当集合内全部条目均为 0 时翻译为 Clear;与正常条目混排属非法结构,fail fast 拒绝。
  • 空数组 [] = Replace(空集),明细行清空——区别于字段缺失的 Unchanged。
  • DELY 无协议序号属性,不支持逐条 Apply;清除走空数组替换。

验收目标。 非法 JSON、未知形状、超容输入不得在截断后成功。当前 parseCollection 未校验数组元素必须是对象,仓储会忽略未知属性;该目标尚未完成。

5.3 第二步:命令合并进当前态(纯函数)

FlightStateEngine.apply 从当前态拷贝一份并逐条应用命令:Set 覆盖键值;Clear 删键并记入 clearedKeys(被清除的标量键清单,供第三步写库时据此将物理列置 NULL);Replace 整组替换。随后版本 +1、记 LAST_MESSAGE_ID,产出 FlightNextState。纯函数:不碰库、不发事件,同样输入恒得同样输出。

5.4 第三步:新状态写库(唯一写入口 persistNextStates

  • 写库写的是合并后完整态,不是报文字段。 主表与集合写入的输入是锁内合并后的完整 FlightNextState;「未提字段一律不动」发生在引擎合并层(5.3),不在写库层。快照线整行替换(FDAY=该快照覆盖的运营日)写出的同样是合并后状态——快照省略的字段以合并流程结束时的当前值持久化。例:航班当前态含 ESTT=0900(预计时间)与登机门 {G28},快照给出 STDACTT=1012(实际时间,进港语义 ATA/出港 ATD)与登机门 {A1},省略 ESTT → 输出 = STD/ACTT/登机门改为快照值,ESTT 保留 0900。若快照显式给空(Clear/空数组)才构成清空。FDAY 由快照 Set 显式推进,是保留规则唯一的结构性例外。
  • 动态线仅 UPDATE 本次出现的列,clearedKeys 对应物理列显式置 NULL;新航班先插一行 FDAY=NULL——动态消息不携带运营日、不触发换代,新行尚未被任何日计划代收录(并非"无运营日"),待后续被某运营日整包快照收录时由快照赋值(清理见第 7 节「未归属行」)。
  • 明细集合:每次写航班,9 张明细表对该 FLID 先 DELETE 再按合并后集合整组 INSERT——即使某集合本次未变也重写,以保证库内与内存状态严格一致。此即"整集合替换"的落地:ORDINAL 按输入顺序 1..NSOURCE_SEQ 存协议序号,RECORD_VERSION 记写入时版本。
  • last_message_id / state_version / updated_at 一并更新。

5.5 事件由状态序列化而来

KAFKA_SCHD 载荷 = FlightNextState 序列化(FlightFieldsJson):键按字典序输出保证同状态字节级稳定;集合键输出为 JSON 数组/对象,不做二次字符串编码。快照线的事件与落库共用同一份事务内状态;动态线的事件目前来自 Handler 的锁外预览(见 5.6),偏差收敛前动态事件载荷不承诺与提交状态逐字节一致——对外状态的最终一致性由投递聚合按当前整态投影兜底(第 8 节)。

5.6 已知偏差(待收敛)

GTDT Handler 在事务外先算一遍预览并构造事件,主泵在事务内再算一遍;该偏差收敛前,不能宣称动态事件载荷与提交状态一致(见 5.5,投递侧以整态投影兜底)。目标状态为 Handler 只表达业务变化,事务内产出唯一一份 nextState,落库与 KAFKA_SCHD 共用——尚未实现,列为可靠性验收项。

6. 字段语义矩阵

6.1 16 类结构映射

集合键 存储载体 序号属性 出现语义 显式清除 Apply
GTDT 登机门 flight_gate GTNO Replace GTNO=0 全清 引擎预留(未启用;GTNO 定位)
CKDT 值机柜台 flight_checkin CKNO Replace CKNO=0 全清 引擎预留(未启用);同 CHKC 多舱位分配按 CKNO 区分,不按资源号去重
CLDT 行李转盘 flight_belt CLNO Replace CLNO=0 全清 引擎预留(未启用)
PSDT 计划机位 flight_stand_plan PSNO Replace PSNO=0 全清 引擎预留(未启用)
CHDT 行李滑槽 flight_chute CHNO Replace CHNO=0 全清 引擎预留(未启用)
DELY 延误 flight_delay Replace(仅保存本次集合,不追加历史) [] 清空 不支持(协议无定位键)
ABTM 靠撤桥 flight_bridge_op ASNO Replace(操作全集) ASNO=0 全清 引擎预留(未启用;ASNO 定位,多次靠/撤桥各占一行)
CHOT 轮挡 flight_chock_op CSNO Replace CSNO=0 全清 引擎预留(未启用;CHID=ON/OFF 为业务属性,非序号)
ROUT 计划航路 flight_route_point(route_kind=ROUT) RTNO Replace RTNO=0 全清 引擎预留(未启用)
ERUT 扩展航路 flight_route_point(route_kind=ERUT) RTNO Replace RTNO=0 全清 引擎预留(未启用)
FDIV 备降 主表前缀列 FDIV_* Set(对象) "null"/{}/空串 → Clear
FRET 返航 主表前缀列 FRET_* Set 同上
FLAB 中止 主表前缀列 FLAB_* Set 同上
SRVT 服务 主表 SRVT_TEXT Set 当前字段转换器不将 null/{}/空串转 Clear;显式 ScalarCommand.Clear 可清列
VIPF 贵宾 主表 VIPF_TEXT Set 同 SRVT
MAFL 共享航班 主表 MAFL_TEXT Set 同 SRVT 无查询需求时维持现有载体

公共明细列:FLID(逻辑归属,当前无数据库外键)、ORDINAL(输入顺序,1 起)、SOURCE_SEQ(报文序号属性,如 GTNO=3;仅记录,不参与定位、不唯一)、RECORD_VERSIONCREATED_AT/UPDATED_AT(整组替换为物理重写:CREATED_AT 反映最近一次重建时刻,不是业务记录时间);当前主键 (FLID, ORDINAL)。路线表尚需把 ROUTE_KIND 纳入主键以避免 ROUT/ERUT 冲突。相同资源号不代表同一条分配记录,禁止按资源号去重。表中"引擎预留(未启用)"的 Apply 仅指引擎分支存在,生产只生成 Replace/Clear。

集合全集语义:协议依据与待确认项。 快照线(SCHD)可按全集理解:规范注释明确日计划是「AODB 最新数据的完整快照」,可选字段/段未出现即 AODB 无此信息、子系统应删除本地已有值(§3.16.1 注释 4),DELY 亦明确「该标签对所有已记录延误重复出现」。动态线各事件每次携带全量集合还是仅本次变更,规范未逐条写明——CHOT 的 CSNO 实为机位序号(非轮挡序号),每段记录一次上/下轮挡移动(§3.22.2),若事件仅携带本次移动,Replace 会丢失既有操作记录;各集合「序号=0 全清」的协议出处也需逐类核实。动态线集合语义确认前,Replace 全集语义仅对快照线可视为定案(ACM2-31 未决)。

6.2 清除的库内与线上表达

  • 库内:标量/异常/文本键 Clear → 对应列置 NULL;集合键 Clear/Replace(空集) → 明细行删除。
  • 线上(KAFKA_SCHD):清空的表达定为键缺失——Clear 与 Replace(空集) 事件同语义,KAFKA_SCHD 载荷不输出该集合键;下游不得把「键缺失」与「[]」作两种解释。库内读回(无键)与 wire 表达按此收敛,黄金样例按此口径固定(投递聚合与 tombstone 见第 8 节)。
  • 两个方向的「缺失」语义相反,消费契约必须写明:上游输入报文中字段缺失 = Unchanged(保留原值);下游 KAFKA_SCHD 整态投影中字段/键缺失 = 当前态无该数据,消费端应移除本地旧值。下游接收的是整态替换,不得按补丁合并。
  • 异常对象 Set 在增量 SQL 中仅写出现的属性;旧对象被省略的属性可能残留,与对象整体替换目标不一致,待回归修正。

6.3 时区与空值

  • FDAY 只记录日期(该行所属代覆盖的运营日,见 §3.2),其来源与日界都按「快照覆盖的运营日」口径:由日计划快照决定,不由报文接收/落库时刻推算——跨午夜接收不改变归属;动态事件不更新 FDAY
  • SIS 时间串(DDMONYYHHMM,机场当地时)原值保存;类型化提升按实际查询需要另行定案。
  • PG 当前保留字符串空值,集合 JSON null 属性会被解析为空串。Oracle 11g 对零长度字符值按 NULL 处理是官方文档明确的既定行为,不再是待确认的数据库事实;待验证的是本模块在该行为下能否维持字段语义——空串往返、SRVT/VIPF/MAFL「空串不算 Clear」的判断、显式命令的持久化保真,均列为 Oracle 适配的交付门槛验证项。

7. 失败规则与不变量(两条线共用)

事务内任一步失败 → 整体回滚,消息记 FAILED 退避重试。已提交的成功不受后续失败影响:回填与投递失败仅落补偿待办,不降级 SUCCEEDED,不重放业务。

不变量:

  • 状态、事件、终态同事务:库内可见的必是完整提交结果。
  • 计算是合并不是覆盖;写库是一致不是增量。 报文未提字段一律不动(加工链见第 5 节);写入库的永远是锁内合并后的完整 FlightNextState,其中包括快照线整行替换写出的合并后状态(混合规则与例见 5.4)。快照线更新 FDAY(置为该快照覆盖的运营日),动态线保留 FDAY(不改变归属、不推断运营日),动态/ADFT 新行 FDAY=NULL(尚未被任何运营日代收录,不等于无运营日)。
  • 快照是同日最高权威:重叠字段上快照覆盖动态更新(见 4.2 规则 M 及其成立条件标注);动态报文更新当前状态,与快照的冲突按规则 M 处理。
  • 快照重放仅一道防线:凭 schd_gen.last_message_id(见 3.3)识别最近一次快照的重放;消息去重凭 identity_key;版本号只是顺序令牌。更早历史快照的重入是否安全,依赖人工重放的版本/业务日期策略(见 3.3 重放三种去向)。
  • 未归属行(FDAY=NULL)的生命周期:动态线先于快照到达或从未上日计划的 FLIDFDAY=NULL 建行,不隶属任何代、差删不触达,不引用 SCHD_GEN 名单。其清理由定期历史化作业执行:条件 = 最后更新时间超过 N 天(默认 7,可配置)且未被任何待答请求/名单引用;N 的默认值须有真实消费方复核后启用。已归属历史日的账本行与航班清理为另一独立未决项(见 §3.6),不适用本规则。

错误分类与队列行为(目标矩阵):

错误类别 示例 终态 重试 队头行为
协议结构非法(解码乱码/0 与正常条目混排/未知形状/缺身份键) §5.2 非法结构 DEAD 不重试 立即释放
容量超限(单包 >10000 航班等) §4.2 超限 DEAD 不重试 立即释放
无注册 Handler 路由不到 Handler4.1 FAILED,不写终态 不自动重试,可人工重放 队头滞留至超限
未定义 SCHD 子类型 未在子类型规则表中的子类型 现状 FAILED(UNSUPPORTED);目标终态待定案(其余行为以本行现状标注为准) 不自动重试 队头滞留至超限
解码器缺陷/内部错误 事务 DB 故障 FAILED(INFRA) FAILED → 退避 超限转 DEAD 队头滞留直至超限
并发版本 CAS 冲突 日代推进版本不符 事务回滚(无终态) 同事务或下一轮重读自动重试 队头滞留直至成功
人工重放 DEAD 消息可人工重放,须携带版本/业务日期策略(见 4.2/3.3)

矩阵中「既有」:DEAD/FAILED 两类终态、CAS 自动回滚、队头失败退避;「待补校验」:超容与结构非法的显式拒绝(§5.2 验收目标)、未定义子类型的终态归属、人工重放的幂等身份策略(失败前已绑定的 identity_key 与重放身份的关系未定义)。每类错误有且只有一个默认路径,以本矩阵行为准。

字段缺失、清除、空数组、异常对象的精确行为见第 6 节。

8. 提交之后:回填与事件

提交成功后有两件外部副作用:回填共享 MySQL 信箱、异步投递 Kafka,二者各自独立重试。

回填恢复依赖一条持久待办:

  • 现状:仅在回填失败后写 BACKFILL_TODO;提交后立即崩溃的窗口内没有待办,无法恢复。
  • 目标:在业务终态同一事务内登记回填意图,提交后回填成功即删待办。崩溃后自然有账可查,无需额外"重启对账框架"。
  • 红线:登记待办后再失败,不得把已提交的 SUCCEEDED 降级为 FAILED

KAFKA_SCHD 投递语义(按 FLID 聚合,第 8 节的主体)。 Dispatcher 聚合未发出的同 FLID 事件,按该 FLID 当前整态投影输出——不是逐事件补丁流:

  • FLID 多条未发事件合并为一次按最新 STATE_VERSION 生成的整态载荷;中间版本的载荷不逐条补发。
  • 旧批次的投递重试可晚于新批次发出;消费端以 (FLID, STATE_VERSION, UPDATED_AT) 去重,晚到的旧投影不应覆盖新状态。
  • KAFKA_MSG 仅作变更通知,不承载状态;两个主题之间不承诺顺序。
  • 未归属行约定(第 7 节)与 tombstone(下)也是事件身份的一部分:FLID 不重复重发。
  • tombstone(差删的对外删除语义;自定义删除事件,非 Kafka 原生墓碑):被差删航班的最后一类 KAFKA_SCHD 事件以 tombstone 表达——载荷仅含 FLIDDELETED 标记(无状态字段;value 非 null,Kafka 不会按日志压缩语义处理它);与差删同事务登记,投递失败照常重试、不过期,下游接到后删除该 FLID 本地数据。删除通知不携带版本号:迟到删除、删除后恢复与旧更新事件的冲突消解由消费端负责;主行物理删除后 STATE_VERSION 的连续性(删除前与重建后的事件可能同号)未定义——删除事件的版本/世代标识为开放项(ACM2-31)。

9. 读取路径

当前读一个航班 = 主行 1 次 + 10 类集合各 1 次,共 11 次查询;全量读 N 个航班约 1+10N 次(N=航班数,主行批量 1 次 + 每航班 10 类集合各 1 次)。多次读不一定落在同一数据库快照,主子状态不保证一致。

后续在仓储边界按 FLID 批次加载明细,并明确一致性读事务;投递侧 Dispatcher 的整态投影读取受同一缺口影响,一并纳入验收。不引入缓存权威或通用查询框架。GET /all/flights 仍是 Controller TODO——仓储方法与展示视图存在不等于接口已交付。

KAFKA_SCHD 输出结构化 JSON,集合禁止双重编码为字符串。清空的 wire 表达已按第 6 节定案(键缺失);未知属性、异常局部替换与空值往返仍存在验收缺口(见第 6 节);单个全字段样例通过并不等于整个 SIS 协议无损。