From c8acb744324bf41b321eadf94b6649ff63dd20c5 Mon Sep 17 00:00:00 2001 From: windyboy Date: Sun, 20 Sep 2026 10:26:12 +0800 Subject: [PATCH] =?UTF-8?q?docs(acm2):=20=E5=AF=B9=E9=BD=90=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E4=B8=8E=E5=8F=82=E8=80=83=E8=A1=A8=E2=80=94=E2=80=94?= =?UTF-8?q?C-1/Q9=20=E4=BF=A1=E7=AE=B1=E6=B8=85=E7=90=86=E5=8F=A3=E5=BE=84?= =?UTF-8?q?=E3=80=81REQ=5FTRACK=20=E5=BC=80=E6=94=BE=E9=94=AE=E4=B8=A4?= =?UTF-8?q?=E7=B1=BB=E5=90=84=E4=B8=80=E6=9D=A1=E3=80=81FLOP=20=E7=8E=B0?= =?UTF-8?q?=E8=A1=8C=E5=9F=BA=E7=BA=BF=207=20=E7=B1=BB=E3=80=81EROR=20?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E3=80=81C-7=20=E5=BC=95=E7=94=A8=E3=80=81PRO?= =?UTF-8?q?TOCOL=20=E5=BD=92=E4=BD=8D=E3=80=81=E5=8F=82=E8=80=83=E8=A1=A8?= =?UTF-8?q?=E5=8E=BB=20Q11/Q23=20=E8=AF=AF=E5=BC=95=E5=B9=B6=E8=A1=A5=20OP?= =?UTF-8?q?S-2=20=E7=BC=BA=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/implementation.md | 67 +++++++++++++++++++++--------------------- docs/reference.md | 19 ++++++------ 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/docs/implementation.md b/docs/implementation.md index f599f0a..3766672 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -26,8 +26,8 @@ |---|---|---| | `PROC_STATE` | 入站消息的处理状态、身份、尝试次数、错误原因与回填事实 | `MSG_ID = CMINMSGS_ID` 主键防重复入队;`IDENTITY_KEY` 唯一约束防业务重复;按最小未完成 `MSG_ID` 取队头;`BACKFILL_NEXT_AT` 非空 = 还欠一次回填,`BACKFILL_AT` 非空 = 标记已确认,`BACKFILL_ABANDONED_AT/REASON` 非空 = 已停止自动重试(**不等于**标记已确认);`RECEIVED_AT` 复制自信箱接收时间、**可能为 NULL**、仅用于对账与展示;`ENQUEUED_AT` 是本地入队时间、非空、是超期判据的唯一依据。 | | `MSG_EVENT` | 等待投递的事件(outbox) | `EVENT_ID` 对 `KAFKA:msg` 是稳定事件身份并决定投递顺序;对 `KAFKA:schd` 是每次接受 upsert 时替换的写代次。`TARGET` 区分 `KAFKA:msg` / `KAFKA:schd`;`PARTITION_KEY` 当前取 `FLID`,`msg` 单分区下不参与路由(`CLM-3`);`EVENT_TYPE` 区分 UPSERT 与 TOMBSTONE。`KAFKA:schd` 按 `FLID` 单行 upsert,只保留最新 `STATE_VERSION`;`SENT_AT` 在投递确认的同一条 UPDATE 内写入,是保留期判定的唯一基准。 | -| `REQ_TRACK` | 上游请求及应答关联 | 状态 `PENDING / SENT / DONE / EXPIRED`;保存请求类型、覆盖运营日、发送方、出站信箱 ID 与发送/完成时间;**「同类只允许一个开放请求」的唯一键 = `(请求类型, 覆盖运营日, 发送方)`,且仅对开放状态生效**。 | -| `REF_MASTER` | SIS 消息提供的静态参考数据与资源状态(目标表) | `(RTYPE, RKEY)` 唯一;`RTYPE` 类别、合并语义与资源状态见「静态参考数据」;取数路径见 [requirements.md](requirements.md) `US-13`。 | +| `REQ_TRACK` | 上游请求及应答关联 | 状态 `PENDING / SENT / DONE / EXPIRED / FAILED`;保存请求类型、覆盖运营日、出站信箱 ID 与发送/完成时间。发送方恒为 `OMMS`(`C-4`),不进开放唯一键。开放唯一键 = **`RQRD` \| `RQFD` 两类各一条**,仅开放态(`PENDING`/`SENT`)生效(`US-09` AC1);**不是** 14 类参考数据 `STYP` 各一条。同一子类型发新请求时旧请求作废,属流程,不是唯一键。`FAILED`/`DONE`/`EXPIRED` 关闭开放槽。 | +| `REF_MASTER` | SIS 消息提供的静态参考数据与资源状态的逻辑视图(物理为独立数据表组) | `(RTYPE, RKEY)` 唯一;`RTYPE` 类别、合并语义与资源状态见「静态参考数据」;取数路径见 [requirements.md](requirements.md) `US-13`。 | | `FLIGHT_SCHD` | 航班标量及单值异常字段 | `FLID` 主键;运营日与版本、最近消息 ID 用于追踪。变长集合存于资源明细表与 `FLIGHT_ROUTE_POINT`,规则见「航班域」。 | | `SCHD_SNAP_LOG` | 日计划处理留痕 | 只追加、可重建,不参与状态决策;保留期见 [reference.md](reference.md)。 | 字段与索引以 `src/main/resources/db/migration/` 的迁移链为准(Oracle 11g 目录为占位,未接入 Flyway)。报文原文仍从共享信箱读取;信箱清理见 `C-1`。 @@ -35,13 +35,13 @@ ## 2. 消息、身份与决策 -`XmlCodec`(实装 `JacksonXmlCodec`)把 XML 解码为 `DecodedMessage`,包含 `SNDR / TYPE / STYP / SEQN / DTTM` 元数据、`MsgKind` 与业务载荷。解码失败区分 `MALFORMED`(报文非法,不重试)与可随 codec 修复的 `CODEC_ERROR`。`MsgKind` 是一等分派键:`Schd(RESP/DNLD/ADFT)`、`Flop`、`Fdel`、`Unsupported`。 +`XmlCodec`(实装 `JacksonXmlCodec`)把 XML 解码为 `DecodedMessage`,包含 `SNDR / TYPE / STYP / SEQN / DTTM` 元数据、`MsgKind` 与业务载荷。解码失败区分 `MALFORMED`(报文非法,不重试)与可随 codec 修复的 `CODEC_ERROR`。`MsgKind` 是一等分派键:`Schd(RESP/DNLD/ADFT)`、`Flop`、`Fdel`、`RefData`(`SIS:3.1`~`SIS:3.15`)、`Unsupported`。 业务身份统一由 `Identity.of` 生成:`SNDR | TYPE | STYP | SEQN`。接收时只按信箱 ID 去重,解码后才首次绑定业务身份;重试保留原有绑定,因此自身重试不会被判为重复。身份被另一条记录占用时,当前消息转 `SKIPPED`,记录 `duplicate-of:`。是否加入日期边界取决于上游 `SEQN` 重置周期(见 `C-3`/`Q10` 与 `PARAM:msgx.identity.include-day-boundary`);上线后不能随意更换身份算法。 **身份绑定是独立的幂等单语句**(`WHERE IDENTITY_KEY IS NULL`),不参与业务事务。它的前提是「报文不可变」(`Q4`):同一身份的重发不会被比对内容,若上游改发正文会被判为重复并跳过(`Q4`)。 -分派与落库由 `MessageProcessor` 协调:按 `MsgKind` 把已绑定身份的队头消息交给对应事务协调器(SCHD-DNLD/RESP → `ScheduleProcessor`,ADFT → `AdftProcessor`,FLOP → `FlopProcessor`,FDEL → `FdelProcessor`,`SIS:3.1`~`SIS:3.14` 的静态参考数据消息 → `ReferenceDataProcessor`,其余 → `SKIPPED(unsupported)`)。这些处理器在 `PIPELINE_LOCK` 事务内读取当前完整态,调用纯领域决策逻辑得到下一完整态与待发事件,提交该事务后写 Redis 投影,再在另一事务中登记处理终态与回填意图(`INV-3`、`INV-10`);它们不直接触碰 Kafka。领域决策逻辑不执行 I/O。处理步骤的锁跨越 Redis 写,使与航班历史清理的协作覆盖整个步骤(`US-14` AC4)。 +分派与落库由 `MessageProcessor` 协调:按 `MsgKind` 把已绑定身份的队头消息交给对应事务协调器(SCHD-DNLD/RESP → `ScheduleProcessor`,ADFT → `AdftProcessor`,FLOP → `FlopProcessor`,FDEL → `FdelProcessor`,`SIS:3.1`~`SIS:3.14` 落库与 `SIS:3.15` 应答配对 → `ReferenceDataProcessor`(`3.15` 是 RESP 配对路径,不是第 15 个 `RTYPE`),其余 → `SKIPPED(unsupported)`)。这些处理器在 `PIPELINE_LOCK` 事务内读取当前完整态,调用纯领域决策逻辑得到下一完整态与待发事件,提交该事务后写 Redis 投影,再在另一事务中登记处理终态与回填意图(`INV-3`、`INV-10`);它们不直接触碰 Kafka。领域决策逻辑不执行 I/O。处理步骤的锁跨越 Redis 写,使与航班历史清理的协作覆盖整个步骤(`US-14` AC4)。 合法但本系统不支持的消息类型:跳过留档、按已处理写回标记(`US-03` AC2),不重试。`REGN` / `RSTA` 是静态参考数据消息,必须分派给 `US-13`,不得跳过。 @@ -81,7 +81,7 @@ ### 4.3 兼容 HTTP 入口 -`POST /cminmsgs/send` 把报文写入共享信箱(处理时间为空),效果与上游投递一致:由收报扫描发现、登记、处理。客户端失败重试可能再次写信箱,业务身份去重仍然必需。响应语义见 `C-8`。 +`POST /cminmsgs/send` 把报文写入共享信箱(处理时间为空),效果与上游投递一致:由收报扫描发现、登记、处理。客户端失败重试可能再次写信箱,业务身份去重仍然必需。响应语义见 `C-7`。 ### 4.4 单实例 @@ -118,6 +118,7 @@ processOne(head): SCHD-DNLD / SCHD-RESP → ScheduleProcessor(快照事务) SCHD-ADFT → AdftProcessor(单航班事务) FLOP / FDEL → Flop / FdelProcessor(单航班事务) + RefData(SIS:3.1~3.14 落库;SIS:3.15 闭合 REQ_TRACK) → ReferenceDataProcessor Unsupported → SKIPPED(unsupported)(跳过留档,按已处理写回标记) 载荷缺失 → DEAD(MALFORMED) 整包协议拒绝 → DEAD(PROTOCOL),不落半包 @@ -155,7 +156,7 @@ processOne(head): - 不加速、不分流、不走旁路:不允许并行队头,也不允许实时消息跳过积压。 - 尝试上限与退避对积压同样生效,不因积压而放宽。 - 不再处理的行置 `SKIPPED` 并记录原因,到达终态后走回填通道;不存在「整段 DELETE」的快速通道。 -- 消化期间的可观测项见 [reference.md](reference.md);完成时限不作对外承诺(`CLM-5`),现场一般为即时处理。 +- 消化期间的可观测项见 [reference.md](reference.md);完成时限不作对外承诺(`CLM-5`)。 ## 6. 回填 @@ -174,7 +175,7 @@ ORDER BY BACKFILL_ATTEMPTS ASC, MSG_ID ASC -- 公平轮转,永久失败 LIMIT PARAM:msgx.pipeline.backfill-batch ``` -超期判据使用**本地入队时间**(`ENQUEUED_AT`),不使用信箱的 `RECEIVED_AT`:后者来自外部时钟,前偏会在「打标即清除」语义下造成提前清除。 +超期判据使用**本地入队时间**(`ENQUEUED_AT`),不使用信箱的 `RECEIVED_AT`:后者来自外部时钟,前偏会扭曲超期窗口;本系统清理信箱行以 `C-1` 为准(回填了结且超保留期),不以库方时钟为准。 ### 6.2 四种结果与放弃 @@ -185,7 +186,7 @@ LIMIT PARAM:msgx.pipeline.backfill-batch | 信箱行不存在 | 写入 0 行且信箱行不存在 | **立即放弃自动重试**(原因 `MISSING_ROW`)并告警。终态行存在而信箱行不存在,只可能是该行在入队后被删除(永久空洞 ID 从不入队,不会进入本扫描) | | 暂时性故障持续超期 | 超时 / 连接失败持续到 `R` 仍未打标 | **停止自动重试**(原因 `TRANSIENT_DEADLINE`)并告警;`R` 之前只退避重试,**不按尝试次数放弃**;保留人工恢复能力 | -**放弃 ≠ 标记已确认**:放弃行不写 `BACKFILL_AT`,处理标记仍为空;放弃清单能否作为清除判定依据,属未确认的清除协议(`C-1`;见 `Q9`)。 +**放弃 ≠ 标记已确认**:放弃行不写 `BACKFILL_AT`,处理标记仍为空;按 `C-1`,回填未了结的行不删;放弃行因此不进入共享信箱清理。 ### 6.3 `R` 的作用 @@ -201,11 +202,7 @@ LIMIT PARAM:msgx.pipeline.backfill-batch 1. 退避重试(`R` 之前不放弃); 2. 到 `R` 仍失败则停止自动重试、告警,保留人工恢复(`reopen`)。 -第三段「库方以放弃清单作为清除判定」未确认(`C-1`;见 `Q9`)。 - -两个边界要说清:`MISSING_ROW`(信箱行不存在)是**确定性结论**,立即放弃,不受 `R` 保护;`R` 只要求 `R ≤ R_keep`,原文保留期的唯一约束来源是 `C-1`。 - -库方的清除语义未确认(`C-1`;见 `Q9`):若为「打标即可清除」,`C-1` 的清理前提不成立,必须与库方另定;增大 `R` 无效。 +两个边界要说清:`MISSING_ROW`(信箱行不存在)是**确定性结论**,立即放弃,不受 `R` 保护;`R` 只要求 `R ≤ R_keep`。入站原文由本系统按 `C-1` 清理:回填已了结且超过保留期(约 1 个月,`Q9`)后删;放弃行无 `BACKFILL_AT`,不删。 ## 7. 日计划快照与请求匹配 @@ -228,10 +225,12 @@ LIMIT PARAM:msgx.pipeline.backfill-batch ```text PENDING → SENT → DONE - └──→ EXPIRED + ├──→ EXPIRED + └──→ FAILED(收到 EROR,US-09 AC3) ``` -- 注册同类新请求前使旧开放请求过期;只有 `COUTMSGS` 写入确认后才标记 `SENT` 并关联出站记录;写信箱成功但本地未确认的情况需要补偿与去重,不能无条件重新发送。 +- 开放槽按 `RQRD` 与 `RQFD` 两类各一条(`US-09` AC1);注册同子类型新请求前使该类下旧开放请求过期,新请求待在途结案后再落信。只有 `COUTMSGS` 写入确认后才标记 `SENT` 并关联出站记录;写信箱成功但本地未确认的情况需要补偿与去重,不能无条件重新发送。 +- 收到 EROR:定位本系统发出的开放请求,标 `FAILED` 并告警(`US-09` AC3);`FAILED` 关闭开放槽。 - 应答按报文类型匹配等待中的开放请求(`US-09` AC2);降级匹配的跨代误配风险必须明确接受并审计。 - 时间比较统一时区与单位,判据一律用本地时钟(入队、发送时间),不引入库方或对方时钟。 - 参考应答写入自有 `REF_MASTER`,日计划应答走快照流程;请求完成必须在相应数据处理成功之后,超时和迟到应答不能修改已关闭请求对应的状态。 @@ -255,7 +254,7 @@ PENDING → SENT → DONE `KAFKA:schd` 只提供最新状态通知,不保留每次中间变化,因此 outbox 按 `FLID` 单行 upsert:同一 `FLID` 只保留最新 `STATE_VERSION` 的事件与投递状态。两条写规则: -`KAFKA:schd` 行的 `EVENT_ID` 不是跨代次稳定的事件句柄:每次接受更新都从全局序列取得新值并替换原主键,用作条件确认的写代次。重放和人工处置只能针对当前 `(TARGET, PARTITION_KEY, EVENT_ID)`;旧代次被替换后不再能按旧 ID 寻址。升级时若已有重复行,按 `STATE_VERSION DESC, EVENT_ID DESC` 保留一行,使迁移与运行时只进不退规则一致。 +`KAFKA:schd` 行的 `EVENT_ID` 不是跨代次稳定的事件句柄:每次接受更新都从全局序列取得新值并替换原主键,用作条件确认的写代次。重放和人工处置只能针对当前 `(TARGET, PARTITION_KEY, EVENT_ID)`;旧代次被替换后不再能按旧 ID 寻址。 - **只进不退**:仅当新事件的 `STATE_VERSION ≥` 行内现有版本才覆盖,防止迟到的旧事件把新状态压回去。该合并规则以 `FLID` 在保留期内不复用(设计前提)为前提。 - **条件标记**:发送成功后按**读取时刻的版本**做条件标记(`WHERE STATE_VERSION = <本批版本>`);该行若期间已被更新的版本覆盖,则不标记,留待下一轮重发。 @@ -302,7 +301,7 @@ PENDING → SENT → DONE **通则**(对本系统所有持久对象适用) -- **时间不构成清除依据**:到期只是必要条件,**终局证据才是充分条件**(航班见 `US-14` AC3、`D1`;共享库的清除依据属未确认的清除协议,见 `C-1` 与 `Q9`)。 +- **时间不构成清除依据**:到期只是必要条件,**终局证据才是充分条件**(航班见 `US-14` AC3、`D1`;共享信箱见 `C-1`:回填已了结且超保留期)。 - **证据不随清除消失**:回填失败与放弃的记录在其覆盖的信箱行被清除前保持可查(`US-10` AC2)。 - **证据缺失或结果不明时按最保守处置**:航班清理为删 0 条(`US-14` AC3、`D1`)。 @@ -310,7 +309,7 @@ PENDING → SENT → DONE | 对象 | 终局判据 | 归档目标 | 清除证据 | 执行方 | 偏差 | |---|---|---|---|---|---| -| 共享信箱 `CMINMSGS` 原文 | 处理标记 | — | 待确认(`Q9`) | 库方 | 契约未确认(`Q7`/`Q9`) | +| 共享信箱 `CMINMSGS` 原文 | 回填了结(`BACKFILL_AT` 非空)且超保留期(约 1 个月,`C-1`/`Q9`) | — | 回填已了结 + 超保留期;放弃行无 `BACKFILL_AT`,不删 | 我们 | — | | `FLIGHT_SCHD` + 资源明细 | 判史规则 | 历史存储 | 历史写入确认 + 版本复查 | 我们 | — | | 航班历史存储 | 保留期 | — | — | 我们 | `G-FLIGHT-HIST-RETENTION` | | `SCHD_SNAP_LOG` | 保留期 | 无(本地可重建) | 无 | 我们 | — | @@ -371,7 +370,7 @@ PENDING → SENT → DONE ### 11.2 航班身份与运营日 -`FLID` 是主键。`OPERATION_DAY` 从 SCHD 记录的 `SODT` 按配置的机场时区和切日规则推导;它不是消息接收日或落库日。尚未由日计划收录的航班可以为 `NULL`;这不表示该航班没有运营日,只表示当前模型无法为它确定归属日。运营日冲突如何处置见 `Q23`(`INV-4` 只管整份校验不过时本地不改)。 +`FLID` 是主键。`OPERATION_DAY` 从 SCHD 记录的 `SODT` 按配置的机场时区和切日规则推导;它不是消息接收日或落库日。尚未由日计划收录的航班可以为 `NULL`;这不表示该航班没有运营日,只表示当前模型无法为它确定归属日。运营日冲突按 [reference.md](reference.md) 错误分类 **`PROTOCOL`** 处置:立即 `DEAD`,整包不落地(`INV-4` 只管整份校验不过时本地不改)。 ### 11.3 字段与集合 @@ -398,7 +397,7 @@ PENDING → SENT → DONE | `CHOT` | `CSNO`、`CHTM`、`CHID`、`CHST` | 99 | `SIS:3.22` | | `DELY` | `CODE`、`STRT`、`DURA`、文本 | — | [XSD](legacy/unisysaodbsis.xsd)「FLOP 元素」 | | `ABTM` | `ASNO`、`ABDG`、`ABOP`、`AOTM` | 99 | [XSD](legacy/unisysaodbsis.xsd)「FLOP 元素」 | -| `ROUT` / `ERUT` | `RTNO`、`APCD`、`SCAT`、`SCDT` | 6 / 7(`ROUT` 只取 4) | `SIS:3.40` | +| `ROUT` / `ERUT` | `RTNO`、`APCD`、`SCAT`、`SCDT` | SIS 报文容量 6 / 7;本地各自最多保留 4 条(`US-05` AC3) | `SIS:3.40` | | `SRVT` | `OPER`、`SRTC`、`SRQT`、`SRST`、`SRET`、`SRPR`、`SANR`、`SARR` | 无界 | `G-SRVT-VIPF` | | `VIPF` | `OPER`、`VPCD`、`VFES`、`VIPT/OPER`、`VIPT/VSCD`、`VIPT/VTQY`、`VIPT/VTST`、`VIPT/VTET` | 无界 | `G-SRVT-VIPF` | @@ -406,7 +405,7 @@ PENDING → SENT → DONE - `ORDINAL` 是持久化顺序,从 1 开始;`SOURCE_SEQ` 是上游序号,允许为空或重复。 - 相同资源号不代表同一条分配,禁止按资源号去重。 -- 每次持久化完整航班状态时,明细表按该 `FLID` 先删后插,以完整合并结果为准(`US-04` AC2)。 +- 每次持久化完整航班状态时,明细表按该 `FLID` 先删后插,以完整合并结果为准。 - ROUT 与 ERUT 是两类独立集合,不能因相同序号覆盖彼此。 - `CHDT` 的类字段固定为 `CCLS`/`CTYP`;当前 wire DTO 与持久化列误写成 `CHCLS`/`CHTYP`,见 `G-FLOP-UNMAPPED`。 - 主/共享关系以主表的 `MAID` 为事实来源:`MAID` 是共享航班指向主航班 `FLID` 的引用(非共享航班为 `NULL`);`MAFL` 只在读取和事件投影时从子航班事实派生,不按入站标量解析或保存。 @@ -420,7 +419,7 @@ PENDING → SENT → DONE - 同一写代次下投影逐字节稳定,与到达顺序及 `FLNO` 变更无关;重发与消费端比对才有意义。 - `MAID = FLID` 的自引用行不进入任何 `MAFL`;`MAID` 指向不存在主航班的悬挂引用不阻断该子航班自身处理,只是不产生投影。 - 子航班集合变化的传播见 `US-06` AC2,事件类型为 `KAFKA:msg` + `KAFKA:schd`;否则整态投影的只进不退写入会丢弃它(见「`schd` 聚合」)。共享航班自身不单独发通知。 -- 派生主航班投影与产生它的状态写入必须同一事务或一致读快照;按 `MAID` 取子航班要求该列有索引(`INV-3`、`US-06` AC2)。 +- 派生主航班投影与产生它的状态写入必须同一事务或一致读快照;按 `MAID` 取子航班要求该列有索引(`US-06` AC2)。 ## 12. 航班域:合并、删除与生命周期 @@ -432,16 +431,20 @@ SCHD DNLD/RESP 在整包校验通过后,分批将报文携带的航班写入 日计划里某航班没携带的字段,视为 AODB 已删除该值,本地同步清除(`C-6`)。每个成功写入的航班推进 `STATE_VERSION`,并在同一事务登记 `KAFKA:schd` 与 `KAFKA:msg` 事件。 -消息重复处理由 `PROC_STATE` 的消息 ID 与 `IDENTITY_KEY` 控制;已成功提交的消息不得再次写入或重复登记事件。整包校验失败时整包不落地(`INV-4`);运营日冲突见 `Q23`。 +消息重复处理由 `PROC_STATE` 的消息 ID 与 `IDENTITY_KEY` 控制;已成功提交的消息不得再次写入或重复登记事件。整包校验失败时整包不落地(`INV-4`);运营日冲突按 [reference.md](reference.md) 错误分类 **`PROTOCOL`**。 ### 12.2 动态运行事件(FLOP) -FLOP 只修改报文表达的字段或集合,其余状态保持不变;目标形态与合并规则见「字段与集合」。`STYP` 必须命中下表白名单,未知值按不支持类型跳过留档(`US-03` AC2),不得进入通用合并。已确认的动态更新在同一事务推进 `STATE_VERSION` 并登记 `KAFKA:msg` 与 `KAFKA:schd`(`INV-3`);处理终态与回填意图在 Redis 投影写成功后的另一事务中提交(`INV-10`)。同一消息不产生两次效果见 `US-03`;逐类规则未补齐见 `G-FLOP-IDEMPOTENT`。 +FLOP 只修改报文表达的字段或集合,其余状态保持不变;目标形态与合并规则见「字段与集合」。`STYP` 必须命中现行白名单(基线见下段),未知值按不支持类型跳过留档(`US-03` AC2),不得进入通用合并。事务边界见「主泵调度与单条处理」、`INV-3`、`INV-10`。同一消息不产生两次效果见 `US-03`;逐类规则未补齐见 `G-FLOP-IDEMPOTENT`。 + +`US-05` AC1 **现行基线仅 7 类**:`ABTM`、`DELY`、`PSDT`、`CKDT`、`CLDT`、`CHDT`、`GTDT`。下表其余行是 `Q3`/`G-FLOP-SEMANTICS` 的闭合目标,**不是现行白名单**。`Q3` 只管 SIS 未记载者的形态与逐类终态;`ABTM`/`DELY` **是否处理已定案**。 逐类语义以 SIS 的字段表、空标签规则与 Processing Exceptions 为准;下表每一行都必须有一条回归用例钉住「输入与前态 → 目标状态 → 终态与事件」。 | SIS | STYP | 目标 | 空标签 / 缺失语义 | |---|---|---|---| +| XSD FLOP | `ABTM` | `ABTM` 集合 | 现行基线须处理(`US-05` AC1);形态与逐类终态待 `Q3` | +| XSD FLOP | `DELY` | `DELY` 集合 | 现行基线须处理(`US-05` AC1);形态与逐类终态待 `Q3` | | `SIS:3.19` | `ACFT` | `ACFT` 标量 | 字段为 M,未写明空语义 | | `SIS:3.20` | `ACTT` | `ACTT` 标量 | 空 = 清除已有实际时间 | | `SIS:3.21` | `BOTM` | `BOTM` 标量;非空时设置登机状态 | 空 = 清除登机时间并重置登机状态 | @@ -463,14 +466,14 @@ FLOP 只修改报文表达的字段或集合,其余状态保持不变;目标 | `SIS:3.37` | `HNAG` | `FHAG`/`PHAG`/`MHAG` 标量 | `FHAG` 空 = 删除该代理;`MHAG` 可缺席 | | `SIS:3.38` | `PSDT` | `PSDT` 集合 | 空 `` = 现无机位分配;AODB 实际会发,照常接收处理(`US-05` AC3) | | `SIS:3.39` | `RENO` | `RENO` 标量 | 空 = 清除注册号 | -| `SIS:3.40` | `ROUT` | `ROUT` 集合 | `SCAT`/`SCDT` 分别对起点/终点缺席;RMS 只保留 4 条(离港取前 4、到港取后 4)且不保存 `SCAT`/`SCDT` | +| `SIS:3.40` | `ROUT` | `ROUT` 集合 | `SCAT`/`SCDT` 分别对起点/终点缺席。SIS 报文容量 6/7;本地 `ROUT`/`ERUT` **各自最多保留 4 条**(`US-05` AC3),不保存 `SCAT`/`SCDT` | | `SIS:3.41` | `TAOP` | `TAOP`/`TAFL`/`TAID` 标量 | 任一为空 = 该到达航班的经停连接断开 | | `SIS:3.42` | `TRML` | `TRML` 标量 | 空 = 删除航站楼 | | `SIS:3.43` | `VIPP` | `VIPP`/`VIPR` 标量 | 空 = 删除;SIS 另要求 RMS 忽略 `VIPP`(忽略事件还是忽略字段,SIS 未写明,须以真实报文确认,见 `G-FLOP-SEMANTICS`) | `CKDT`、`CLDT`、`CHDT`、`GTDT`、`PSDT` 等 FLOP 子类型是否入站、如何合并,见 `US-05` 与本章「动态运行事件」;SIS 中的 RMS→AODB 方向说明不约束本系统(OMMS 只收不发)。 -**SIS 未定义但 legacy 处理的子类型**:`ABTM`、`DELY`、`PADT`、`FTSS`、`STND`、`BDPB`、`REMC`。它们在 `XSD` 的 `FLOP` 段里是普通字段或集合,没有独立事件节;legacy 按 `STYP` 命中它们处理,说明真实流量可能存在 SIS 未记载的子类型。其报文形态与是否存在必须以真实报文确认(`Q3`),不得据 legacy 行为直接定案。 +**SIS 未定义但 legacy 处理的子类型**:`ABTM`、`DELY`(现行基线已要求处理,`US-05` AC1)、`PADT`、`FTSS`、`STND`、`BDPB`、`REMC`。它们在 `XSD` 的 `FLOP` 段里是普通字段或集合,没有独立事件节。`ABTM`/`DELY` 以外者的报文形态与是否存在必须以真实报文确认(`Q3`),不得据 legacy 行为直接定案。 **SIS 定义但 legacy 无处理器**:`CHDT`、`FINT`、`MAXP`。 @@ -483,17 +486,15 @@ FLOP 只修改报文表达的字段或集合,其余状态保持不变;目标 FDEL 是业务删除入口:仅在 `ACTIVE → DELETED` 时推进版本、保留明细并与 tombstone 同事务登记;重复 FDEL 或不存在的航班按幂等成功处理。 物理删除仅由独立历史清理在历史写入成功后执行(`US-14` AC3、`D1`)。快照里没有的航班删除(`INV-7`);FDEL 仍是最先的删除入口。清理前需要登记一次 tombstone;已经 FDEL 的记录不重复发出。 -ADFT 的字段缺失语义尚待上游确认。在确认前采用保守的 Set-only 规则:出现字段可更新,缺失字段不清空;不得把它当成日计划或动态全量替换。新建 ADFT 若带可解析的 `SODT`,按同一运营日规则计算 `OPERATION_DAY`;否则保留为 `NULL`。 +ADFT 按 `US-04` AC2:未携带的字段不清空(Set-only);出现字段可更新。不得把它当成日计划或动态全量替换。新建 ADFT 若带可解析的 `SODT`,按同一运营日规则计算 `OPERATION_DAY`;否则保留为 `NULL`。 主/共享航班级联:删除共享航班时重算主航班 `MAFL`(见「主/共享投影」)并向主航班通知;删除主航班时级联删除其子共享关联并发出删除通知;主/共享关系必须一次原子变更,不出现主已删、子残留的半状态。共享航班增量通常只更新并通知主航班,不直接发共享通知。这些语义同样约束 FDEL 之外的生命周期清理。主/共享关联的增删按 `FLID` 做值比较,不使用引用比较。 -本章的原子级联不回发 EROR(`C-5`、`Q12` 已定案)。 - ### 12.4 Kafka 与读取 `KAFKA:schd` 是按 `FLID` 的完整状态投影。Dispatcher 可以合并同一 `FLID` 尚未投递的中间版本,只发最新状态;消费端用 `(FLID, STATE_VERSION, UPDATED_AT)` 防止旧投影覆盖新状态。 -`KAFKA:msg` 只通知变化,不承载权威状态;两个 topic 不承诺顺序。FDEL 和必要的生命周期清理使用 tombstone:键为 `FLID`,删除记录以 null 值投递,通知下游移除旧状态。 +`KAFKA:msg` 只通知变化,不承载权威状态;两个 topic 不承诺顺序。删除航班不进 `schd`,只由 `msg` 发删除通知(`C-9`:不设消息键)。删除通知的 value 与变更/删除区分方式待 `Q5` 定案。 读取完整航班必须在明确的一致性读边界内批量加载主表和全部明细。 @@ -503,7 +504,7 @@ ADFT 的字段缺失语义尚待上游确认。在确认前采用保守的 Set-o ## 13. 静态参考数据 -本章定义由入站 SIS 消息获得的航班基础数据(静态参考数据)的目标形态与合并语义。字段的值域、长度与业务含义以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 对应节为准,本章只定义类别、结构与落库口径;取数路径与刷新责任见 [requirements.md](requirements.md) `US-13`。**静态参考数据不得从 admin-api 拉取、补全或合并;admin-api 只读取本网关已经提交到业务数据库的数据。** +本章定义由入站 SIS 消息获得的航班基础数据(静态参考数据)的目标形态与合并语义。字段的值域、长度与业务含义以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 对应节为准,本章只定义类别、结构与落库口径;取数路径与刷新责任见 [requirements.md](requirements.md) `US-13`。下游读取边界见「admin-api 下游读取边界」。 ### 13.1 来源 @@ -536,7 +537,7 @@ ADFT 的字段缺失语义尚待上游确认。在确认前采用保守的 Set-o ### 13.3 结构与合并语义 -`REF_MASTER` 是对业务暴露的有效参考数据视图,逻辑身份为 `(RTYPE, RKEY)`;普通类别的 `RKEY` 见上表,`RSTA` 的身份必须同时包含 `RTYP` 与 `RSID`。记录保存消息来源、消息批次、刷新时间与按 SIS 标签名组织的字段载荷;重复字段保留输入顺序并表示为有序数组。参考数据保存在独立的数据表中,admin-api 直接读取(`C-10`)。 +`REF_MASTER` 是对业务暴露的有效参考数据视图,逻辑身份为 `(RTYPE, RKEY)`;普通类别的 `RKEY` 见上表,`RSTA` 的身份必须同时包含 `RTYP` 与 `RSID`。记录保存消息来源、消息批次、刷新时间与按 SIS 标签名组织的字段载荷;重复字段保留输入顺序并表示为有序数组。参考数据物理为独立数据表组。 - **13 类参考数据**:`DNLD`/`RESP` 是类别全量,整批校验通过后原子发布;`ADD`/`UPD`/`DEL` 是单条全字段增量,按 `(RTYPE, RKEY)` 处理。全量替换只作用于消息指定的同一 `RTYPE`。 - **资源状态**:`RSTA-DNLD` 是单条状态更新,按 (`RTYP`, `RSID`) 覆盖;`RSTA-RESP` 是请求返回的多条记录。两者都不以“本包未出现”为理由删除其他资源状态。 @@ -544,7 +545,7 @@ ADFT 的字段缺失语义尚待上游确认。在确认前采用保守的 Set-o - **请求配对**:参考应答统一为 `STYP=RESP`,以响应 `TYPE` 对应原 `RQRD.STYP`;`RSTA` 的请求范围还要校验 `RTYP`,不能只按 `RESP` 或消息到达时间匹配。 - `SRVT`、`VIPF` 与参考数据无关;参考数据不参与航班状态推进,也不进入 `MSG_EVENT`。 -SIS 声明的上游忽略与截断口径(`SIS:3.1`/`SIS:3.2`/`SIS:3.4`/`SIS:3.5`/`SIS:3.6`/`SIS:3.7`/`SIS:3.8`/`SIS:3.10`/`SIS:3.11`/`SIS:3.12`):国家代码忽略 `COUN` 且只保留第一个 `REGC`;机场代码忽略 `ANAM`/`ACTY`/`ICCD`/`BDIS`/`ATYP`/`HAUL`;机型忽略 `DESC`/`CHAP`/`MFWT`/`MTWT`/`MHTM`/`MABR`;注册号忽略 `OWID`/`MFWT`/`MTWT`;机构忽略 `ONAM`,且 `OCAT` 只保留旅客/维护/地服代理;航班类型忽略 `FDES`/`FCML`/`CTYP`;航站楼忽略 `TNAM`/`TCAT`/`TPAX`;机位忽略 `STNM`/`MABS`/`FEPU` 并把 `STGP` 截断到 8 字符;值机柜台忽略 `CTNM`/`CCAT`;转盘忽略 `BTNM`/`BCAT`。这些字段在未确认本地用途前不进入参考数据模型,需要时由 `US-13` 增补并说明用途。 +SIS 声明的上游忽略与截断口径见 `SIS:3.1`、`SIS:3.2`、`SIS:3.4`、`SIS:3.5`、`SIS:3.6`、`SIS:3.7`、`SIS:3.8`、`SIS:3.10`、`SIS:3.11`、`SIS:3.12`。未确认本地用途前不入模型;`US-13` 增补须说明用途。 ### 13.4 资源状态 diff --git a/docs/reference.md b/docs/reference.md index fb35663..a57f453 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -17,7 +17,7 @@ | `msgx.pipeline.max-attempts` | `5` | 次 | 假定 | 处理与投递共用;达到即转 `DEAD(EXHAUSTED)` | | `msgx.pipeline.backoff-ms` | `[1000,2000,4000,8000]` | ms / 档 | 假定 | **档位数必须 = `max-attempts − 1`**,启动自检拦截错位 | | `msgx.pipeline.backoff-cap-ms` | `60000` | ms | 假定 | 单档封顶;默认表内无档触及 | -| `msgx.pipeline.max-commit-delay` | `5m` | Duration | **假定(无依据)** | **待退役**:水位空洞老化阈值,随收报改谓词扫描(`G-SCAN-PREDICATE`)删除;依据 `C-2` 已作废 | +| `msgx.pipeline.max-commit-delay` | `5m` | Duration | **假定(无依据)** | **待退役**:水位空洞老化阈值,随收报改谓词扫描(`G-SCAN-PREDICATE`)删除 | | `msgx.pipeline.overdue-backfill` | `30d` | Duration | 契约(`R ≤ R_keep`) | 即 `R`:进入强补写窗口、**取消退避**的阈值;**不是兜底保证** | | `msgx.pipeline.backfill-batch` | `100` | 条 | 假定 | 回填扫描单批条数 | | `msgx.pipeline.backfill-scan-period` | `30s`(代码常量,无配置键) | Duration | 现役 | 回填扫描作业周期 | @@ -44,17 +44,18 @@ | `msgx.identity.include-day-boundary` | `false` | 安全默认 | 身份是否加日期边界;影响去重语义,不能当调优项切换(`Q10`) | | `msgx.health.backlog-cache-ttl-ms` | `30000` | 假定 | 积压快照缓存窗口;`0` = 不缓存;`/health` 与 `/metrics` 共用同一快照 | | `msgx.history.history-store-enabled` | `false` | 安全默认 | 历史存储门控;关闭时历史清理删 0 条 | -| `msgx.history.cancelled-hours` | `1` | 假定 | 历史清理的取消态判据窗口 | -| `msgx.history.terminal-hours` | `48` | 假定 | 历史清理的终态判据窗口 | -| `msgx.history.deleted-hours` | `48` | 假定 | 历史清理的已删除判据窗口 | -| `msgx.history.idle-hours` | `168` | 假定 | 历史清理的静默期判据 | +| `msgx.history.cancelled-hours` | `1` | 契约(`US-14` AC2) | 取消态判据窗口:CNCL 早于当前超过 1 小时 | +| `msgx.history.terminal-hours` | `48` | **与 `US-14` AC2 冲突** | 终态判据窗口;AC2 判史条件(计划超 3 天、离港/到港成套条件)不含 48h 终态窗口,取值须按 AC2 重评 | +| `msgx.history.deleted-hours` | `48` | 实现 | 已删除判据窗口;`US-14` AC2 之外的兜底判据 | +| `msgx.history.idle-hours` | `168` | 实现 | AC2 五条件之外的静默期兜底;与 AC2 的关系须在实现定案时说明 | +| `msgx.history.sweep-time` | `03:30`(代码常量,无配置键) | 契约(`US-14` AC1) | 历史清理作业每日执行时刻 | | `msgx.history.snap-log-retention-days` | `90` | 假定 | `SCHD_SNAP_LOG` 保留天数 | ### 1.3 信箱与外部依赖(成组登记) | 参数组 | 默认 | 依据 | 说明 | |---|---|---|---| -| `mailbox.processed-value` | `PROCESSED` | 契约(`C-5`/`Q8`) | 处理标记写入值;仅限库方认可值集 | +| `mailbox.processed-value` | `PROCESSED` | 契约(`Q8`) | 处理标记写入值;仅限库方认可值集;写入值形态(完成时刻 vs 常量)与 spec「处理标记=写完成时刻」存在张力,须核对旧系统真实写入内容后定案 | | `mailbox.shared-mysql.enabled` | `false` | 安全默认 | 真实信箱接线门控 | | `mailbox.shared-mysql.connect-timeout-ms` | `3000` | 假定 | 有界外部调用;Connector/J 默认无限等待,必须显式;同组 `socket-timeout-ms=30000` | | `mailbox.shared-mysql.pool-connection-timeout-ms` | `5000` | 假定 | 池级连接超时;同组 `pool-validation-timeout-ms=3000` | @@ -82,9 +83,9 @@ | `msgx.pipeline.job.ticks.total` | 作业 tick 完成次数 | 不增长 → 作业停摆 | | `msgx.pipeline.job.failures.total` | 作业 tick 抛错次数 | 增长 → 扫描/历史作业异常 | | `msgx.pipeline.job.last_sweep_selected` | 上一轮回填扫描选中的待办条数(扫描积压) | 持续顶到批次上限 → 扫描吃不消 | -| `msgx.pipeline.codec.srvt_seen.total` | 入站记录中出现 `SRVT` 段的条数(`G-SRVT-VIPF`) | > 0 → 真实流量确有该段,按真实报文定案 `Q11` | +| `msgx.pipeline.codec.srvt_seen.total` | 入站记录中出现 `SRVT` 段的条数(`G-SRVT-VIPF`) | > 0 → 真实流量确有该段,按真实报文闭合 `G-SRVT-VIPF` | | `msgx.pipeline.codec.vipf_seen.total` | 入站记录中出现 `VIPF` 段的条数(`G-SRVT-VIPF`) | 同上 | -| `msgx.pipeline.processing.ignored.total` | 命中 `US-04` 忽略清单的报文条数 | 增长是正常流量;归零反而需确认配置是否丢失 | +| `msgx.pipeline.processing.ignored.total` | 命中 IgnoreRules 基线忽略清单(`LDM-*`/`REGN-*`/`RSTA-*`/`EROR-*`,见 `processing/IgnoreRules.kt`)的报文条数 | 增长是现行流量;但清单与目标设计冲突——`REGN`/`RSTA` 按设计必须分派 `US-13`,`EROR` 按 `US-09` AC3 必须定位请求标失败;该清单无 requirements 依据,处置待闭合 | 取数规则:统一走 `BacklogSnapshotProvider`(`PARAM:msgx.health.backlog-cache-ttl-ms`),`/health` 与 `/metrics` 共用同一快照——`backlog()` 是 `PROC_STATE` 的全表聚合,不能被高频抓取打穿;**无法取数上报 `NaN`,无可比记录的年龄/滞后类仪表上报 `-1`,都不伪造 0**。日志出口故障不得阻塞业务线程。进程内计数类指标不经快照,重启归零。 @@ -100,7 +101,7 @@ | 回填与投递 | `processing/BackfillService.kt`、`delivery/Dispatcher.kt`、`infra/kafka/KafkaDeliveryPort.kt` | | 维护作业 | `jobs/JobRunner.kt`、`jobs/HistorySweepJob.kt`、`jobs/EventCleanupJob.kt`、`infra/persistence/SnapshotLogPurge.kt` | | 持久化与恢复 | `infra/persistence/`(含 `jdbc/JdbcPgRepositories.kt`、`jdbc/JdbcCminmsgInboxRepository.kt`)、`infra/retry/`(`ProcFailure` / `ReplayService` / `FailureScheduler`) | -| 启停与配置 | `PipelineLifecycle.kt`、`config/PipelineProps.kt`、`config/HistoryProps.kt`、`config/OperationDayProps.kt`(含运营日时区启动自检)、`config/MailboxProps.kt`、`config/KafkaD3Check.kt`(当前 Kafka 生产配置自检) | +| 启停与配置 | `PipelineLifecycle.kt`、`config/PipelineProps.kt`、`config/HistoryProps.kt`、`config/OperationDayProps.kt`(含运营日时区启动自检)、`config/MailboxProps.kt`、`config/KafkaD3Check.kt`(Kafka 生产配置自检,语义见 `D2`;类名沿用历史命名) | | 指标与健康 | `infra/metrics/PipelineMetrics.kt`、`infra/metrics/JobActivity.kt`、`infra/health/BacklogSnapshotProvider.kt`、`infra/health/JobRunnerHealthIndicator.kt` | | 迁移 | `src/main/resources/db/migration/`(单基线 `V1__flight_state_baseline.sql`;`oracle11g/` 为占位) |