docs(acm2-75): 按需求与架构收口契约和规范

补齐接口契约的入站、Redis 与出站边界,规范对齐已定语义并作废过期条款;Kafka 生产端约束编号改为 D2。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
windyboy
2026-09-16 08:49:19 +08:00
co-authored by Cursor
parent 7cd17409f5
commit fd65bb24fb
12 changed files with 327 additions and 266 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
### 2. Grill with Docs Skill(文档严审与盘问) ### 2. Grill with Docs Skill(文档严审与盘问)
- **权威设计**`docs/` 是唯一设计依据(`docs/legacy/` 仅作参考)。 - **权威设计**`docs/` 是唯一设计依据(`docs/legacy/` 仅作参考)。
- 核心约束锚点:`specification.md` (C/PRE/INV/CLM/Q/G), `architecture.md` (D1D4), `implementation.md` (机制与航班域), `reference.md` (PARAM)。 - 核心约束锚点:`specification.md` (C/PRE/INV/CLM/Q/G), `architecture.md` (D1D2), `implementation.md` (机制与航班域), `reference.md` (PARAM)。
- **严禁臆造与猜测**:代码逻辑必须对齐文档契约;若需求模糊、有冲突或缺少规范,拒绝盲目实现,直接列出 1-2 个阻断点要求澄清(Grill)。 - **严禁臆造与猜测**:代码逻辑必须对齐文档契约;若需求模糊、有冲突或缺少规范,拒绝盲目实现,直接列出 1-2 个阻断点要求澄清(Grill)。
- **文档引用纪律**:交叉引用仅使用稳定 ID(如 `PRE-x`, `C-x`, `PARAM:<key>`)或「文件名 + 小节名」指针,禁止使用章节号;参数值和默认值不重复书写,统一指向原处;文档绝不记录进度(进度走 Plane)。 - **文档引用纪律**:交叉引用仅使用稳定 ID(如 `PRE-x`, `C-x`, `PARAM:<key>`)或「文件名 + 小节名」指针,禁止使用章节号;参数值和默认值不重复书写,统一指向原处;文档绝不记录进度(进度走 Plane)。
- **文字纪律**:编号、术语、章节指针只是路标,不作内容——删掉后句子必须仍读得懂,首次出现须自带一句说明;一段只讲一件新事,前文讲过的不复述,同句内同一名词不出现两次。 - **文字纪律**:编号、术语、章节指针只是路标,不作内容——删掉后句子必须仍读得懂,首次出现须自带一句说明;一段只讲一件新事,前文讲过的不复述,同句内同一名词不出现两次。
+4 -4
View File
@@ -16,7 +16,7 @@
| 方向 | 机制 | 说明 | | 方向 | 机制 | 说明 |
|---|---|---| |---|---|---|
| **入站(主路径)** | JDBC 轮询共享 `CMINMSGS` | 上游经 CIIMS 等**外部系统**写信箱;本系统按轮询间隔读取水位之后的记录(`ID > W`**不以处理标记为谓词**)→ 自有 PG 入队(`InboxPoller` | | **入站(主路径)** | JDBC 轮询共享 `CMINMSGS` | 上游经 CIIMS 等**外部系统**写信箱;本系统按轮询间隔读取「处理时间为空」的行,按编号升序、每批有上限(`C-30`)→ 自有 PG 入队(`InboxPoller` |
| **入站(compat** | HTTP `POST /cminmsgs/send` | 手工注入/影子对拍;写信箱 + PG 入队,**非**生产主拓扑 | | **入站(compat** | HTTP `POST /cminmsgs/send` | 手工注入/影子对拍;写信箱 + PG 入队,**非**生产主拓扑 |
| **处理** | 主泵 FIFO 管道 | 解码 → identity → Handler 决策 → PostgreSQL 航班当前态 / 静态参考数据 | | **处理** | 主泵 FIFO 管道 | 解码 → identity → Handler 决策 → PostgreSQL 航班当前态 / 静态参考数据 |
| **出站** | Kafka + `COUTMSGS` | 向下游推送 msg/schd;请求类报文写出站信箱 | | **出站** | Kafka + `COUTMSGS` | 向下游推送 msg/schd;请求类报文写出站信箱 |
@@ -106,7 +106,7 @@ docker compose ps
### 切流前 Kafka Broker 版本确认 ### 切流前 Kafka Broker 版本确认
生产契约以 [requirements.md](docs/requirements.md) `US-07``D3` 为准(三项生产者约束的取值见 [reference.md](docs/reference.md) 参数表),**严禁非幂等降级**。README 不提供生产降级环境变量组合。 生产契约以 [requirements.md](docs/requirements.md) `US-08``D2` 为准(三项生产者约束的取值见 [reference.md](docs/reference.md) 参数表),**严禁非幂等降级**。README 不提供生产降级环境变量组合。
旧系统 `msgexchange-api` 底层依赖 `kafka-clients:0.10.1.1`;现网 Broker 确切版本须在切流前实测确认: 旧系统 `msgexchange-api` 底层依赖 `kafka-clients:0.10.1.1`;现网 Broker 确切版本须在切流前实测确认:
@@ -114,7 +114,7 @@ docker compose ps
```bash ```bash
docker exec msgx-dev-kafka /opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server <TARGET_IP>:9092 docker exec msgx-dev-kafka /opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server <TARGET_IP>:9092
``` ```
2. **门禁判定**:若输出中 `InitProducerId(22)` 为 **可用** → 保持 `D3` 的高可靠默认。 2. **门禁判定**:若输出中 `InitProducerId(22)` 为 **可用** → 保持 `D2` 的高可靠默认。
3. **阻塞切流**:若 `InitProducerId(22)` 为 **UNSUPPORTED**Broker &lt; 0.11)→ **阻塞切流**,须升级 Broker 或经架构豁免(ACM2-1 基础设施升级门禁);降级参数仅可作为经批准的 runbook 附录,**不得**作为生产验收口径与 README 默认配置并存。 3. **阻塞切流**:若 `InitProducerId(22)` 为 **UNSUPPORTED**Broker &lt; 0.11)→ **阻塞切流**,须升级 Broker 或经架构豁免(ACM2-1 基础设施升级门禁);降级参数仅可作为经批准的 runbook 附录,**不得**作为生产验收口径与 README 默认配置并存。
## 构建 ## 构建
@@ -134,7 +134,7 @@ MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存 stub,无
`docs/` 是唯一设计依据;从 [设计文档入口](docs/README.md) 开始阅读(职责、事实归属、ID 语法与引用纪律都在那里)。顶层 6 个文件: `docs/` 是唯一设计依据;从 [设计文档入口](docs/README.md) 开始阅读(职责、事实归属、ID 语法与引用纪律都在那里)。顶层 6 个文件:
- [architecture.md](docs/architecture.md):系统边界、模块职责、存储归属、总体流程与 `D1``D4` 决策。 - [architecture.md](docs/architecture.md):系统边界、模块职责、存储归属、总体流程与 `D1``D2` 决策。
- [requirements.md](docs/requirements.md):阶段范围与非目标、`US-xx` / `OPS-x` 验收目标、需求覆盖与依赖。 - [requirements.md](docs/requirements.md):阶段范围与非目标、`US-xx` / `OPS-x` 验收目标、需求覆盖与依赖。
- [specification.md](docs/specification.md):术语、契约 `C-x`、前提 `PRE-x`、不变量 `INV-x`、声明边界 `CLM-x`、待确认 `Q`、已知偏差 `G`、验证映射。 - [specification.md](docs/specification.md):术语、契约 `C-x`、前提 `PRE-x`、不变量 `INV-x`、声明边界 `CLM-x`、待确认 `Q`、已知偏差 `G`、验证映射。
- [implementation.md](docs/implementation.md):管道机制、航班域与静态参考数据——数据模型、状态机、事务、投递、作业、恢复及合并语义。 - [implementation.md](docs/implementation.md):管道机制、航班域与静态参考数据——数据模型、状态机、事务、投递、作业、恢复及合并语义。
+2 -2
View File
@@ -8,7 +8,7 @@
|---|---| |---|---|
| [README.md](README.md) | 入口、阅读顺序、文件职责、事实归属、ID 语法与引用纪律。 | | [README.md](README.md) | 入口、阅读顺序、文件职责、事实归属、ID 语法与引用纪律。 |
| [requirements.md](requirements.md) | 阶段范围与非目标、`US-xx` / `OPS-x` 验收目标、需求覆盖与依赖。 | | [requirements.md](requirements.md) | 阶段范围与非目标、`US-xx` / `OPS-x` 验收目标、需求覆盖与依赖。 |
| [architecture.md](architecture.md) | 系统边界、模块职责、存储归属、总体流程与 `D1``D4` 决策。 | | [architecture.md](architecture.md) | 系统边界、模块职责、存储归属、总体流程与 `D1``D2` 决策。 |
| [specification.md](specification.md) | 术语、外部契约 `C-x`、前提 `PRE-x`、不变量 `INV-x`、声明边界 `CLM-x`、待确认 `Qn`、当前已知偏差 `G-NAME`、验证映射。 | | [specification.md](specification.md) | 术语、外部契约 `C-x`、前提 `PRE-x`、不变量 `INV-x`、声明边界 `CLM-x`、待确认 `Qn`、当前已知偏差 `G-NAME`、验证映射。 |
| [implementation.md](implementation.md) | 数据模型、状态机、管道机制、事务、投递、作业与恢复;航班域与静态参考数据的权威模型和合并语义。 | | [implementation.md](implementation.md) | 数据模型、状态机、管道机制、事务、投递、作业与恢复;航班域与静态参考数据的权威模型和合并语义。 |
| [reference.md](reference.md) | 参数 `PARAM:<key>`、指标与健康、模块与代码入口、错误分类。 | | [reference.md](reference.md) | 参数 `PARAM:<key>`、指标与健康、模块与代码入口、错误分类。 |
@@ -29,7 +29,7 @@
|---|---|---| |---|---|---|
| 收报扫描谓词与幂等登记 | implementation.md「收报」 | specification.md 写对库方的承诺 `C-30`reference.md 写参数 | | 收报扫描谓词与幂等登记 | implementation.md「收报」 | specification.md 写对库方的承诺 `C-30`reference.md 写参数 |
| 保留期下界 `R_keep`、清除前置条件 | specification.md「契约」 | implementation.md 只写行为约束;执行步骤在上线前另立 | | 保留期下界 `R_keep`、清除前置条件 | specification.md「契约」 | implementation.md 只写行为约束;执行步骤在上线前另立 |
| 处理标记值集与写权限 | specification.md `C-5` | implementation.md 只写行为约束「只写空标记、不回撤、不覆盖」`INV-7` | | 处理标记值集与写权限 | specification.md `C-5`(值集)与 `C-15`(只填空值、不回撤不覆盖) | implementation.md 只写行为约束(`INV-7` |
| 回填四结果、放弃语义、`R` 的作用 | implementation.md「回填」 | specification.md 记结论与可声明性 | | 回填四结果、放弃语义、`R` 的作用 | implementation.md「回填」 | specification.md 记结论与可声明性 |
| 退避 / `claim-batch` / 回填期限等取值 | reference.md「参数」 | 其余文档只引 `PARAM:<key>` | | 退避 / `claim-batch` / 回填期限等取值 | reference.md「参数」 | 其余文档只引 `PARAM:<key>` |
| 消费权排他、ID 不复位、报文不可变、时钟、单实例 | specification.md「前提」 | 其他文档只引 `PRE-x` | | 消费权排他、ID 不复位、报文不可变、时钟、单实例 | specification.md「前提」 | 其他文档只引 `PRE-x` |
+4 -4
View File
@@ -8,7 +8,7 @@ msgexchange-v2 是 OMMS H5 查询系统的消息网关,替换旧版 `msgexchan
功能需求是 [requirements.md](requirements.md) 的十四条用户故事(采集、处理、投递、查询、维护,`US-01``US-14`),运行需求是四条验收(单实例、可观测、测试隔离、切换回退,`OPS-1``OPS-4`)。不生成航班/业务数据类报文,不替代 CIIMS/AODB,不调用 admin-api;完整非目标见同文件「范围与非目标」。 功能需求是 [requirements.md](requirements.md) 的十四条用户故事(采集、处理、投递、查询、维护,`US-01``US-14`),运行需求是四条验收(单实例、可观测、测试隔离、切换回退,`OPS-1``OPS-4`)。不生成航班/业务数据类报文,不替代 CIIMS/AODB,不调用 admin-api;完整非目标见同文件「范围与非目标」。
- **主要入口**:轮询共享 MySQL 入站表 `CMINMSGS`,只取处理标记为空的行,按编号升序、每批有上限(`US-01``C-30`)。 - **主要入口**:轮询共享 MySQL 入站表 `CMINMSGS`,只取处理时间为空的行,按编号升序、每批有上限(`US-01``C-30`)。
- **兼容入口**`POST /cminmsgs/send` 供联调工具把报文写进信箱,与上游投递走同一条处理路径;返回的编号只表示已进信箱,不代表已处理或下游已收到(`US-02`)。 - **兼容入口**`POST /cminmsgs/send` 供联调工具把报文写进信箱,与上游投递走同一条处理路径;返回的编号只表示已进信箱,不代表已处理或下游已收到(`US-02`)。
- **查询入口**`GET /all/flights` 返回当前全部动态航班(不含共享航班),读 Redis,与网页客户端同源(`US-12``INV-24`)。 - **查询入口**`GET /all/flights` 返回当前全部动态航班(不含共享航班),读 Redis,与网页客户端同源(`US-12``INV-24`)。
- **出站**:只向 AODB 发参考数据请求 `RQRD` 和日计划请求 `RQFD`,经共享 MySQL 出站表 `COUTMSGS`,由 CIIMS adapter 消费;只保证请求写入信箱,不保证 AODB 收到(`US-09``C-24`)。 - **出站**:只向 AODB 发参考数据请求 `RQRD` 和日计划请求 `RQFD`,经共享 MySQL 出站表 `COUTMSGS`,由 CIIMS adapter 消费;只保证请求写入信箱,不保证 AODB 收到(`US-09``C-24`)。
@@ -41,7 +41,7 @@ delivery 投递 jobs 作业:回填 / 出站重试 / 历史清理 /
四组线程在同一进程、互不调用,协作只经自有 PG 的持久记录交接;HTTP 接口走事件循环,不占这四组线程。重启后各段从记录接着做,不依赖内存进度(`US-01` AC4、`US-03` AC3、`US-10` AC2)。 四组线程在同一进程、互不调用,协作只经自有 PG 的持久记录交接;HTTP 接口走事件循环,不占这四组线程。重启后各段从记录接着做,不依赖内存进度(`US-01` AC4、`US-03` AC3、`US-10` AC2)。
线程之间不加锁,靠幂等写入:收报按信箱编号只登记一次(`US-01` AC2)、回填只写空标记(`C-5`)、记录清理只删已回填且超过保留期的行(`INV-25`);发生竞争时后到的操作复查状态并重试。唯一的例外是处理消息的主循环(主泵)与航班历史清理之间要加锁(`INV-18`)。 线程之间不加锁,靠幂等写入:收报按信箱编号只登记一次(`US-01` AC2)、回填只写空标记(`C-15`)、记录清理只删已回填且超过保留期的行(`INV-25`);发生竞争时后到的操作复查状态并重试。唯一的例外是处理消息的主循环(主泵)与航班历史清理之间要加锁(`INV-18`)。
运行边界:同一时刻只允许一个实例处理消息(`OPS-1`);切换与回退以信箱处理标记交接,本系统内部处理和回填进度保存在自有 PG 的 `PROC_STATE`,停旧启新时未处理的消息由旧系统继续(`OPS-4`);积压、处理失败、投递失败、回填失败各有指标与告警(`OPS-2`)。 运行边界:同一时刻只允许一个实例处理消息(`OPS-1`);切换与回退以信箱处理标记交接,本系统内部处理和回填进度保存在自有 PG 的 `PROC_STATE`,停旧启新时未处理的消息由旧系统继续(`OPS-4`);积压、处理失败、投递失败、回填失败各有指标与告警(`OPS-2`)。
@@ -63,7 +63,7 @@ delivery 投递 jobs 作业:回填 / 出站重试 / 历史清理 /
航班动态消息走满全链,其余类别只换其中几步: 航班动态消息走满全链,其余类别只换其中几步:
1. **收报**:按「处理标记为空」发现信箱行,登记入队(`INV-2b`)。 1. **收报**:按「处理时间为空」发现信箱行,登记入队(`INV-2b`)。
2. **主泵**:按最小未完成 `MSG_ID` 取队头,解码,按业务身份去重(`INV-3``INV-9`);非法或不支持的报文无副作用,终态留档(`US-03`)。 2. **主泵**:按最小未完成 `MSG_ID` 取队头,解码,按业务身份去重(`INV-3``INV-9`);非法或不支持的报文无副作用,终态留档(`US-03`)。
3. **事务一**:持 `PIPELINE_LOCK`,领域变更、未映射字段记录与待发事件一起提交。 3. **事务一**:持 `PIPELINE_LOCK`,领域变更、未映射字段记录与待发事件一起提交。
4. **投影**:写 Redis,写成功才算处理完成(`INV-23`);失败保持未完成、下轮重写投影,业务效果幂等(`US-03` AC3)。 4. **投影**:写 Redis,写成功才算处理完成(`INV-23`);失败保持未完成、下轮重写投影,业务效果幂等(`US-03` AC3)。
@@ -103,7 +103,7 @@ delivery 投递 jobs 作业:回填 / 出站重试 / 历史清理 /
|---|---|---| |---|---|---|
| 自有 PostgreSQL | 处理锁 `PIPELINE_LOCK`、消息处理状态与回填意图 `PROC_STATE`、待发事件 `MSG_EVENT`、出站请求跟踪 `REQ_TRACK`、航班当前态 `FLIGHT_SCHD`、资源明细表、`FLIGHT_ROUTE_POINT`、未映射字段长期记录表 `UNMAPPED_FIELD`、静态参考数据 `REF_MASTER`、日计划快照留痕 `SCHD_SNAP_LOG` | 本系统唯一的业务数据库,也是航班当前态的唯一权威(`INV-11b`);本地事务只发生在这里,事务怎么分段见「主流程」。`UNMAPPED_FIELD` 不随 `PROC_STATE` 到期清理。 | | 自有 PostgreSQL | 处理锁 `PIPELINE_LOCK`、消息处理状态与回填意图 `PROC_STATE`、待发事件 `MSG_EVENT`、出站请求跟踪 `REQ_TRACK`、航班当前态 `FLIGHT_SCHD`、资源明细表、`FLIGHT_ROUTE_POINT`、未映射字段长期记录表 `UNMAPPED_FIELD`、静态参考数据 `REF_MASTER`、日计划快照留痕 `SCHD_SNAP_LOG` | 本系统唯一的业务数据库,也是航班当前态的唯一权威(`INV-11b`);本地事务只发生在这里,事务怎么分段见「主流程」。`UNMAPPED_FIELD` 不随 `PROC_STATE` 到期清理。 |
| Redis | 航班查询投影 | 只作查询,不是权威,也不存处理状态(`INV-11b`);只由本系统写入和移除(`INV-24`),内容来自 PG 当前态;`GET /all/flights` 与网页客户端读的就是它。 | | Redis | 航班查询投影 | 只作查询,不是权威,也不存处理状态(`INV-11b`);只由本系统写入和移除(`INV-24`),内容来自 PG 当前态;`GET /all/flights` 与网页客户端读的就是它。 |
| 共享 MySQL | `CMINMSGS` 入站信箱、`COUTMSGS` 出站信箱 | 信箱归外部系统所有。本系统只读写消息、回写处理标记,不建表、不改表结构、不清数据、不写历史表(`C-14`);原文保留多久、何时清除由库方定(`C-5``C-12``Q7``Q9`)。出站请求写进去就算交付(`C-24`)。 | | 共享 MySQL | `CMINMSGS` 入站信箱、`COUTMSGS` 出站信箱 | 信箱归外部系统所有。本系统只读写消息、回写处理标记,不建表、不改表结构、不清数据、不写历史表(`C-14`);原文保留多久、何时清除由库方定(`C-6``C-9``Q7``Q9`)。出站请求写进去就算交付(`C-24`)。 |
| 航班历史存储(Elasticsearch) | 已结束航班的历史副本 | 已结束航班写入这里作历史副本;写入确认成功后才删实时数据,写不成一条也不删(`D1``INV-28`)。保留期与容量上限未定(`G-FLIGHT-HIST-RETENTION`)。 | | 航班历史存储(Elasticsearch) | 已结束航班的历史副本 | 已结束航班写入这里作历史副本;写入确认成功后才删实时数据,写不成一条也不删(`D1``INV-28`)。保留期与容量上限未定(`G-FLIGHT-HIST-RETENTION`)。 |
**PG 的事务只管自己库。** Redis 写没写成、信箱标记写没写上、Kafka 发没发出,PG 事务都管不着;这些步骤各自可重试,重做多少遍结果都一样,重启后从 PG 记录接着走。 **PG 的事务只管自己库。** Redis 写没写成、信箱标记写没写上、Kafka 发没发出,PG 事务都管不着;这些步骤各自可重试,重做多少遍结果都一样,重启后从 PG 记录接着走。
+81 -40
View File
@@ -1,101 +1,142 @@
# 接口契约草案 # 接口契约草案
本文记录 msgexchange-v2 与联调工具、运营航班显示界面、CIIMS adapter、admin-api、Elasticsearch 航班历史存储之间需要共同遵守的接口。范围由 [requirements.md](../requirements.md) 的 `US-02``US-08``US-09``US-12``US-13``US-14` 和 [architecture.md](../architecture.md)「系统定位与范围」「数据归属与一致性」确定;下表没有给出字段的地方,不能作为字段级联调依据 本文记录 msgexchange-v2 与联调工具、运营航班显示界面、CIIMS adapter、admin-api、Elasticsearch 航班历史存储之间需要共同遵守的接口:收报、出站请求与应答、Kafka 通知、Redis 查询投影、共享信箱、参考数据与航班历史
《msgexchange-api 旧项目业务逻辑与用户故事文档》只提供现役行为基线。下文的“现役候选”是待对拍的字段线索;旧系统未校验收报 XML、Kafka 批次可能丢失、航班从 Redis 转历史的做法不改变本文件的目标边界 范围由 [requirements.md](../requirements.md) 与 [architecture.md](../architecture.md) 确定,出处随各条标注;未列出的字段不能作为字段级联调依据
旧项目用户故事文档([legacy/](../legacy/))只供字段核对,不作为新版契约的依据。
## HTTP ## HTTP
| 接口 | 请求契约 | 成功响应契约 | 失败契约 | 尚需确定 | | 接口 | 请求契约 | 成功响应契约 | 失败契约 | 尚需确定 |
|---|---|---|---|---| |---|---|---|---|---|
| `POST /cminmsgs/send` | 请求体是 XML 原文;接受 `text/xml``application/xml``text/plain`,默认 UTF-8;仅限内网,网络层限制来源。 | 报文写入 `CMINMSGS` 后返回信箱编号;该响应只证明已落信,不证明业务处理或下游投递。 | 空报文、超大小上限、非法 XML 不落信并返回错误;写信失败不返回编号;XML 解析禁用外部实体和外部资源访问。 | 大小上限、请求编码与 `Content-Type` 的精确处理规则、HTTP 状态码、成功/失败响应体字段及样例。 | | `POST /cminmsgs/send` | 请求体是 XML 原文;接受 `text/xml``application/xml``text/plain`,默认 UTF-8;仅限内网,网络层限制来源。 | 报文写入 `CMINMSGS` 后返回信箱编号;写入的报文与上游投递走同一条处理路径、效果一致;该响应只证明已落信,不证明业务处理或下游投递`US-02`。 | 空报文、超大小上限、非法 XML 不落信并返回错误;写信失败不返回编号;XML 解析禁用外部实体和外部资源访问。 | 大小上限、请求编码与 `Content-Type` 的精确处理规则、HTTP 状态码、成功/失败响应体字段及样例。 |
| `POST /schd/sync` | 触发向 AODB 请求日计划的 `RQFD` 请求。 | 响应内容尚未定义。 | 错误响应尚未定义。 | 请求字段、重复调用语义、请求登记或落信与响应之间的关系、状态码、响应字段及样例。 | | `POST /schd/sync` | 触发一次 `RQFD` 日计划请求;请求字段尚未定义,登记与落信规则见「在途与作废」。 | 响应内容尚未定义;无论表示登记还是落信,都不代表 AODB 已收到。 | 错误响应尚未定义。 | 请求字段与时间格式、成功响应表示已登记还是已落信(`Q17`、状态码、响应字段及样例。 |
| `GET /all/flights` | 无已定义的请求字段;从 Redis 查询投影读取当前全部动态航班,不含共享航班。 | 返回查询到的航班集合。 | Redis 异常时返回错误,不能返回空列表伪装成功。 | 查询参数、分页规则、航班字段与类型、集合外层结构、状态码和错误响应样例。 | | `GET /all/flights` | 无已定义的请求字段;从 Redis 投影读取当前全部动态航班,不含共享航班,与网页客户端同源(`US-12`。 | 返回查询到的全部航班,不分页。 | Redis 异常时返回错误,不能返回空列表伪装成功。 | 航班字段与类型、集合外层结构、状态码和错误响应样例。 |
现役候选:三个接口使用 `ResponseDto`,字段为 `is_success``err_code``err_msg``body`(旧项目用户故事「HTTP 接口清单」)。`POST /cminmsgs/send` 的成功 `body``CMINMSGS_ID`,旧样例为 `{is_success: true, body: <ID>}``GET /all/flights``body` 是非共享航班的 `SCHD.FLTR` 列表。`POST /schd/sync` 的旧请求体是 `{startDate, endDate}`,旧格式 `yyyy-MM-dd hh:mm` 使用无 AM/PM 的 12 小时制,存在时间歧义;字段是否沿用、时间改用何种无歧义格式、响应是否沿用封装,都需对拍后定稿。 旧系统线索(来源:旧项目用户故事「HTTP 接口清单」「日计划请求」):
`/schd/sync` 把日期范围编码为 `RQFD.STDB` / `STDE`,旧出站报文使用 `TYPE=RQFD``STYP=NONE``SNDR=OSH5`(旧项目用户故事「日计划请求」)。这些是报文构造线索,发送方取值、时间格式与新版请求跟踪语义仍需对接方确认 - 三个接口共用 `ResponseDto`,字段为 `is_success``err_code``err_msg``body`
- `POST /cminmsgs/send` 的成功 `body``CMINMSGS_ID`,样例为 `{is_success: true, body: <ID>}`
- `GET /all/flights``body` 是非共享航班的 `SCHD.FLTR` 列表。
- `POST /schd/sync` 的请求体是 `{startDate, endDate}`,时间格式 `yyyy-MM-dd hh:mm` 用无 AM/PM 的 12 小时制。
- 出站报文用 `TYPE=RQFD``STYP=NONE``SNDR=OSH5`,日期范围编码为 `RQFD.STDB` / `STDE`
这些字段是否沿用、时间改用何种无歧义格式,都要核对后定稿。
新版日计划是 AODB 当前时刻的完整航班列表(`US-07`),请求是否仍带日期范围由这条全量语义判断。
## 入站报文与请求应答
AODB 经 CIIMS adapter 把 XML 报文写入 `CMINMSGS`,格式以架构指定的 [SIS 接口规范](../legacy/SIS_AODB_RMS-V0.1.md) 与 [XSD](../legacy/unisysaodbsis.xsd) 为依据。
入站报文按类型处理:`ADFT` 建立计划外航班,`FLOP` 改航班动态,`FDEL` 删航班,`SCHD-DNLD``SCHD-RESP` 同步日计划快照,参考数据写入 `REF_MASTER``US-04``US-07``US-13`);报文不合法进死信,合法但本系统不支持的类型跳过留档并按已处理写回信箱(`US-03`)。
| 环节 | 已确定的边界 | 尚需确定 |
|---|---|---|
| 出站请求 | 本系统只发 `RQRD` 参考数据请求与 `RQFD` 日计划请求,经 `COUTMSGS` 落信,交付承诺止于落信(`US-09`;架构「系统定位与范围」「主流程」)。 | 报文类型与子类型清单、发送方取值、时间与序号的构造规则。 |
| 在途与作废 | `RQRD``RQFD` 各自同时最多一条已落信、未结案的请求;同一子类型发新请求时旧请求作废,新请求登记为待发送,等同一报文类型的在途请求收到应答、失败或超时后再落信;请求超过时限未等到应答标记超时(`US-09`)。 | 超时的时限取值。 |
| 应答匹配 | 应答按报文类型对应到等待中的请求;AODB 发错或迟到的应答不更新数据,记录后跳过(`US-09`);`SCHD-RESP` 只在请求未过期时生效,迟到的应答不更新数据(`US-07`)。 | 请求与应答的对应字段、过期判定的依据字段。 |
| 错误回报 | 收到 `EROR` 时定位到本系统发出的请求,标记失败并告警(`US-09`)。 | `EROR` 与请求的对应字段。 |
## Kafka ## Kafka
| 主题 | 已确定的消息语义 | 尚需确定 | | 主题 | 已确定的消息语义 | 尚需确定 |
|---|---|---| |---|---|---|
| `msg` | 单条航班变更通知;处理航班动态与删除后投递发送失败自动重试;同一 `FLID` 的变更保序,对外按至少一次投递。 | Kafka key、value 的字段与类型、变更和删除的区分方式、版本与去重标识、编码方式、分区规则、消费者处理重复和乱序的规则。 | | `msg` | 单条航班变更通知;航班动态与删除处理完成后投递发送失败自动重试,一直失败的记录保留可查并告警(`US-08`;同一 `FLID` 的变更保序,对外按至少一次投递。删除通知的来源有三处:`FDEL` 删除(`US-06`)、日计划快照缺席删除(`US-07`)、历史清理在物理删除前必要时登记(架构 `D1`)。 | Kafka key、value 的字段与类型、变更和删除的区分方式、版本与去重标识(`Q19`;可用依据是航班当前态的版本规则,见 `FLIGHT_SCHD` 行)、编码方式、分区规则、消费者处理重复和乱序的规则。 |
| `schd` | 定时批量发送最新航班状态;发送失败自动重试;对外按至少一次投递。 | “批量”对应的 Kafka record 粒度、key/value 字段与类型、删除航班的表达方式、批次边界、编码方式、消费者去重规则。 | | `schd` | 定时批量发送最新航班状态;发送失败自动重试;对外按至少一次投递。 | “批量”对应的 Kafka record 粒度、key/value 字段与类型、删除航班的表达方式、批次边界、编码方式、消费者去重规则。 |
现役候选:`msg` 的 value 是 `MSG` 的 JSON,包含 `META` 与对应业务体;日计划到达通知只有 `META``schd` 每次把窗口内航班组成 `SCHD.FLTR` 数组 JSON,队列为空时不发送(旧项目用户故事「前端通知」);这能提供消费者样例,仍不能确定新版的 Kafka key、最新状态聚合粒度或去重标识。 旧系统线索(来源:旧项目用户故事「前端通知」「动态类(FLOP-*)处理」):
跨航班顺序不构成契约;测试环境须使用独立 Kafka 主题 - `msg` 的 value 是 `MSG` 的 JSON,含 `META` 与对应业务体;日计划到达通知只有 `META`
- 只对非共享航班的变更单条下发,共享航班随主航班下发;例外是删除,共享航班被删时也单独发一条 `msg` 删除消息。
- `schd` 把窗口内航班组成 `SCHD.FLTR` 数组 JSON,队列为空时不发送。
## 数据库读写边界 跨航班顺序不构成契约。
测试环境须使用独立的数据库、Redis 与 Kafka 主题,且不连接生产信箱(`OPS-3`)。
## 存储读写边界
### 共享 MySQL:外部信箱 ### 共享 MySQL:外部信箱
| 表 | 本系统的操作 | 需要对接方提供的物理契约 | | 表 | 本系统的操作 | 需要对接方提供的物理契约 |
|---|---|---| |---|---|---|
| `CMINMSGS` | 按信箱编号升序、分批读取处理标记为空的报文;兼容 HTTP 入口写入 XML 原文;处理完成后仅将空处理标记写成库方认可的已处理值。 | 表 DDL、信箱编号报文原文字段、处理标记与处理时间字段、列类型与可空性、写入必需列、标记允许值、原文保留期和索引。 | | `CMINMSGS` | 按信箱编号升序、分批读取处理的报文,扫描与回写用同一处理时间列;兼容 HTTP 入口写入 XML 原文;处理完成后写入处理完成时刻,只填空值、不覆盖已有值;写回失败由后台任务重试,一直写不上的记录保留在案并告警(`US-01``US-02``US-10`。 | 表 DDL、信箱编号报文原文字段、处理时间列的列名和允许值、该列与对接方所称处理标记是否为同一列、各列类型与可空性、写入必需列、原文保留期和索引;信箱编号按到达顺序单调递增、不复用、不回退的保证(架构「必须保持的约束」)。 |
| `COUTMSGS` | 写入 `RQRD` 参考数据请求与 `RQFD` 日计划请求;CIIMS adapter 消费。交付承诺止于请求落信。 | 表 DDL、请求原文字段、写入必需列、编号生成方式、ACK/错误列的写入责任、重复落信的识别规则。 | | `COUTMSGS` | 写入 `RQRD` 参考数据请求与 `RQFD` 日计划请求;CIIMS adapter 消费。交付承诺止于请求落信;写入结果不明时记录并告警,不直接重发(架构「主流程」)。 | 表 DDL、请求原文字段、写入必需列、编号生成方式、ACK/错误列的写入责任、重复落信的识别规则。 |
共享 MySQL 归 CIIMS adapter 方所有;本系统不建表、不改表结构、不清除数据,也不写共享历史表。外部表的物理字段必须以对接方提供的现行 DDL 与读写样例核对,不能由本文件推造。 共享 MySQL 归 CIIMS adapter 方所有;本系统不建表、不改表结构、不清除数据,也不写共享历史表。外部表的物理字段必须以对接方提供的现行 DDL 与读写样例核对,不能由本文件推造。
旧项目用户故事「数据表列清单」提供以下**实体映射列名**,不是现场 DDL、可空性或写权限的证明: 旧项目用户故事「数据表列清单」提供以下**旧系统实体映射列名**,不是现场 DDL、可空性或写权限的证明:
| 表 | 现役实体映射列名 | 现役写入线索 | | 表 | 旧系统实体映射列名 | 旧系统写入线索 |
|---|---|---| |---|---|---|
| `CMINMSGS` | `CMINMSGS_ID``CMINMSGS_CLOB_MSG``CMINMSGS_DATE_RECEIVED``CMINMSGS_DATE_PROCESSED``CMINMSGS_STATUS``CMINMSGS_SUBSYSTEM_DATE_SENT``CMINMSGS_SUBSYSTEM_NAME``CMINMSGS_SUBSYSTEM_SEQUENCE``CMINMSGS_SUBSYSTEM_SUBTYPE``CMINMSGS_SUBSYSTEM_TYPE` | 兼容入口写原始 XML 与接收时间,处理时间为空;新版必须先按 `US-02` 校验 XML。 | | `CMINMSGS` | `CMINMSGS_ID``CMINMSGS_CLOB_MSG``CMINMSGS_DATE_RECEIVED``CMINMSGS_DATE_PROCESSED``CMINMSGS_STATUS``CMINMSGS_SUBSYSTEM_DATE_SENT``CMINMSGS_SUBSYSTEM_NAME``CMINMSGS_SUBSYSTEM_SEQUENCE``CMINMSGS_SUBSYSTEM_SUBTYPE``CMINMSGS_SUBSYSTEM_TYPE` | 兼容入口写原始 XML 与接收时间,处理时间为空;旧系统的扫描与回写都落在 `CMINMSGS_DATE_PROCESSED`(旧项目用户故事「术语与数据语义」);新版必须先按 `US-02` 校验 XML。 |
| `COUTMSGS` | `COUTMSGS_ID``COUTMSGS_ACK_DATE_RECV``COUTMSGS_ACK_REQD``COUTMSGS_ACK_RESEND_TIMES``COUTMSGS_CLOB_MSG``COUTMSGS_DATE_INSERTED``COUTMSGS_DATE_SENT``COUTMSGS_ENCRYPT``COUTMSGS_ERROR``COUTMSGS_FINAL_GROUP_IND``COUTMSGS_GROUP_ID``COUTMSGS_GROUP_ORDER``COUTMSGS_NO_MESSAGES``COUTMSGS_TRUEFALS_GROUP``ROUTINGID` | 旧 `/schd/sync` 写报文 XML、插入时间与 `ROUTINGID=OSH5RQFD`;ACK/错误列的写入责任仍未确认。 | | `COUTMSGS` | `COUTMSGS_ID``COUTMSGS_ACK_DATE_RECV``COUTMSGS_ACK_REQD``COUTMSGS_ACK_RESEND_TIMES``COUTMSGS_CLOB_MSG``COUTMSGS_DATE_INSERTED``COUTMSGS_DATE_SENT``COUTMSGS_ENCRYPT``COUTMSGS_ERROR``COUTMSGS_FINAL_GROUP_IND``COUTMSGS_GROUP_ID``COUTMSGS_GROUP_ORDER``COUTMSGS_NO_MESSAGES``COUTMSGS_TRUEFALS_GROUP``ROUTINGID` | 旧 `/schd/sync` 写报文 XML、插入时间与 `ROUTINGID=OSH5RQFD`;ACK/错误列的写入责任仍未确认。 |
### 自有 PostgreSQL:内部存储与下游读取 ### Redis:航班查询投影
| 存储 | 承载内容 | 边界 | 尚需确定 |
|---|---|---|---|
| Redis | 航班查询投影 | 只作查询,不是权威,也不存处理状态;只由本系统写入和移除,网页客户端与 `GET /all/flights` 读同一份,内容来自自有 PostgreSQL 的航班当前态;写投影成功、删除时移除成功,才算对应消息处理完成(架构「数据归属与一致性」;`US-05``US-06`)。 | key 与 value 结构及序列化方式、网页客户端读取约定(`Q20`)、每次处理后刷新哪些航班。 |
旧系统线索:投影是 Redis hash `flightInfo`field 为 `FLID`、value 为 `SCHD.FLTR` 的带类型 JSON(旧项目用户故事「Redis key 汇总」「术语与数据语义」)。
### 自有 PostgreSQL:内部存储与 admin-api 只读
| 表或表组 | 边界 | 尚需确定的字段级契约 | | 表或表组 | 边界 | 尚需确定的字段级契约 |
|---|---|---| |---|---|---|
| `REF_MASTER` | 静态参考数据与资源状态按类别、编号保存在独立数据表,admin-api 直接只读;新消息覆盖旧记录,全量消息整体替换,增删改消息逐条处理。 | 类别和编号的物理列、各类别字段及类型、主键/唯一键、空值表示、更新可见性。类别范围与消息中的识别标签见下表。 | | `REF_MASTER` | 静态参考数据与资源状态按类别、编号保存在独立数据表,admin-api 直接只读;新消息覆盖旧记录,全量消息整体替换,增删改消息逐条处理`US-13`);一类校验不通过只停这一类、其他类照常,校验失败类别的已有记录不变;字段为空表示「当前没有值」,不是删除(架构「必须保持的约束」)。 | 类别和编号的物理列、各类别字段及类型、主键/唯一键、空值在列中怎样保存、写入后何时可读。类别范围与消息中的识别标签见下表。 |
| `FLIGHT_SCHD`、资源明细表、`FLIGHT_ROUTE_POINT` | 航班当前态的唯一权威;Redis 和 Kafka 从处理结果派生,不反向覆盖这些表。 | 主键、字段与类型、资源明细表清单、外键/索引、版本字段迁移 DDL。 | | `FLIGHT_SCHD`、资源明细表、`FLIGHT_ROUTE_POINT` | 航班当前态的唯一权威;`FLID` 唯一,已写入非空的运营日不可改,每次成功写入版本号加一、重复消息不重复加(架构「必须保持的约束」);Redis 和 Kafka 从处理结果派生,不反向覆盖这些表。 | 主键、字段与类型、资源明细表清单、外键/索引、版本字段的物理列与迁移 DDL。 |
| `PROC_STATE``MSG_EVENT``REQ_TRACK``SCHD_SNAP_LOG``PIPELINE_LOCK``UNMAPPED_FIELD` | 管道处理、待发事件、请求跟踪、留痕、互斥及未映射字段由本系统维护;不对外提供直接读写接口。 | 字段、约束、索引与迁移 DDL 由内部实现设计确定;若其他系统需读取,须另立读取契约。 | | `PROC_STATE``MSG_EVENT``REQ_TRACK``SCHD_SNAP_LOG``PIPELINE_LOCK``UNMAPPED_FIELD` | 管道处理、待发事件、请求跟踪、留痕、互斥及未映射字段由本系统维护;不对外提供直接读写接口。 | 字段、约束、索引与迁移 DDL 由内部实现设计确定;若其他系统需读取,须另立读取契约。 |
自有 PostgreSQL 的物理表结构由本系统的迁移 DDL 定稿。生产环境若改用 Oracle 11g,字段类型与迁移方案需先完成适配验证。 自有 PostgreSQL 的物理表结构由本系统的迁移 DDL 定稿。生产环境若改用 Oracle 11g,字段类型与迁移方案需先完成适配验证。
### 静态参考数据类别与编号来源 ### 静态参考数据类别与编号来源
类别码与识别标签来自架构引用的 [SIS 接口规范](../legacy/SIS_AODB_RMS-V0.1.md)每条普通参考记录以对应类别码识别标签值组成逻辑身份。标签值的格式、唯一范围和 `REF_MASTER` 中的物理存储方式,仍需在本系统的表结构中明确。 类别码与识别标签来自架构引用的 [SIS 接口规范](../legacy/SIS_AODB_RMS-V0.1.md)识别同一条参考记录时,用类别码识别标签值。标签值的格式、标签在哪个范围内唯一,以及保存到 `REF_MASTER` 方式,仍需在本系统的表结构中明确。
| 类别码 | 类别 | 消息中的识别标签 | SIS 依据 | | 类别码 | 类别 | 消息中的识别标签 | SIS 依据 |
|---|---|---|---| |---|---|---|---|
| `COUL` | 国家代码 | `COUC` | `SIS:3.1` | | `COUL` | 国家代码 | `COUC` | 「AODB country codes event」 |
| `ARPT` | 机场代码 | `ITCD` | `SIS:3.2` | | `ARPT` | 机场代码 | `ITCD` | 「AODB airport codes event」 |
| `AIRL` | 航空公司代码 | `ITOP` | `SIS:3.3` | | `AIRL` | 航空公司代码 | `ITOP` | 「AODB airline codes event」 |
| `AIRC` | 机型代码 | `ITAT` | `SIS:3.4` | | `AIRC` | 机型代码 | `ITAT` | 「AODB aircraft codes event」 |
| `REGN` | 注册号 | `RNUM` | `SIS:3.5` | | `REGN` | 注册号 | `RNUM` | 「AODB registration codes event」 |
| `ORGN` | 机构代码 | `OGID` | `SIS:3.6` | | `ORGN` | 机构代码 | `OGID` | 「AODB organization codes event」 |
| `FLTL` | 航班类型代码 | `FTYP` | `SIS:3.7` | | `FLTL` | 航班类型代码 | `FTYP` | 「AODB flight type codes event」 |
| `TLST` | 航站楼代码 | `TCOD` | `SIS:3.8` | | `TLST` | 航站楼代码 | `TCOD` | 「AODB terminal codes event」 |
| `GLST` | 登机门代码 | `GCOD` | `SIS:3.9` | | `GLST` | 登机门代码 | `GCOD` | 「AODB gate codes event」 |
| `SLST` | 机位代码 | `SCOD` | `SIS:3.10` | | `SLST` | 机位代码 | `SCOD` | 「AODB stand codes event」 |
| `CLST` | 值机柜台代码 | `CCOD` | `SIS:3.11` | | `CLST` | 值机柜台代码 | `CCOD` | 「AODB check in counter codes event」 |
| `BLST` | 行李转盘代码 | `BCOD` | `SIS:3.12` | | `BLST` | 行李转盘代码 | `BCOD` | 「AODB carousel codes event」 |
| `CHLT` | 行李滑槽代码 | `CCOD` | `SIS:3.13` | | `CHLT` | 行李滑槽代码 | `CCOD` | 「AODB chute codes event」 |
资源状态是第十四类消息,类别码为 `RSTA``SIS:3.14`),识别一条资源状态时须同时使用资源类型 `RTYP` 和资源编号 `RSID``CLST``CHLT` 都使用 `CCOD` 标签,因此不能脱离类别码识别记录。 资源状态是第十四类消息,类别码为 `RSTA`SIS 接口规范「AODB resource status event」),识别一条资源状态时须同时使用资源类型 `RTYP` 和资源编号 `RSID``CLST``CHLT` 都使用 `CCOD` 标签,因此不能脱离类别码识别记录。
## Elasticsearch 航班历史写入 ## Elasticsearch 航班历史写入
| 契约项 | 已确定的边界 | 尚需确定 | | 契约项 | 已确定的边界 | 尚需确定 |
|---|---|---| |---|---|---|
| 写入对象 | 满足 `US-14` 已结束判据的航班从自有业务数据库写入 Elasticsearch 历史库,作为历史查询副本。 | 历史索引名称、文档 ID、写入字段及类型、嵌套资源结构、字段缺失与删除状态的表达方式。 | | 写入对象 | 满足 `US-14` 已结束判据的航班从自有业务数据库写入 Elasticsearch 历史库,作为历史查询副本。 | 历史索引名称、文档 ID、写入字段及类型、嵌套资源结构、字段缺失与删除状态的表达方式、索引保留期与容量上限(架构「数据归属与一致性」列为未定)。 |
| 成功确认 | 只有该航班的历史写入成功,才允许从实时数据物理删除;删除前按 `D1` 必要时登记待发删除事件。 | Elasticsearch 写入响应中何种结果算成功、成功是否要求可查询、批量响应如何逐项确认。 | | 成功确认 | 只有该航班的历史写入成功,才允许从实时数据物理删除;删除前按 `D1` 必要时登记待发删除事件。 | Elasticsearch 写入响应中何种结果算成功、成功是否要求可查询、批量响应如何逐项确认。 |
| 写入粒度 | 单个航班写入失败不影响其他航班。 | 使用逐条请求还是批量请求、批量大小、部分成功时的确认与继续处理规则。 | | 写入粒度 | 单个航班写入失败不影响其他航班。 | 使用逐条请求还是批量请求、批量大小、部分成功时的确认与继续处理规则。 |
| 失败与重试 | 写入失败的航班保持在实时数据中,下次运行再试;已写入的航班不重复写入;正在被消息处理的航班跳过,下轮再处理。 | 超时或响应不明时的对账方式、可重试错误分类、重试间隔、文档 ID 和覆盖策略如何保证幂等。 | | 失败与重试 | 写入失败的航班保持在实时数据中,下次运行再试;已写入的航班不重复写入;正在被消息处理的航班跳过,下轮再处理。 | 超时或响应不明时的对账方式、可重试错误分类、重试间隔、文档 ID 和覆盖策略如何保证幂等。 |
Elasticsearch 的接口样例与映射确认前,不得以“请求已发送”代替逐航班的成功确认,也不得清除写入结果不明的实时数据 旧系统线索:逐航班按 `SODT + FLID` 查询旧索引 `flight_hts`,存在则更新、不存在则新增;写入内容是 `SCHD.FLTR` 序列化后的 JSON,单条写入失败跳过该航班(旧项目用户故事「动态航班转历史」)
现役候选:旧项目逐航班查询 `flight_hts` 中的 `SODT + FLID` 组合,存在则更新,不存在则新增;写入内容是 `SCHD.FLTR` 序列化后的 JSON,单条写入失败跳过该航班(旧项目用户故事「动态航班转历史」)。`flight_hts` 是旧索引名,`SODT + FLID` 是旧查询条件;新版索引、文档 ID、字段集合和 Elasticsearch 响应的逐航班成功判据仍需确定。 ## 待决事项
由本系统与需求方确定的事项见 [specification.md](../specification.md)「本系统与需求方待决」(`Q17``Q19`)。
## 定稿所需证据 ## 定稿所需证据
1.三个 HTTP 接口的现役候选字段,取得成功和失败响应样例、实际状态码及 `/schd/sync` 的无歧义时间格式。 1. 对三个 HTTP 接口的旧系统线索,取得成功和失败响应样例、实际状态码及 `/schd/sync` 的无歧义时间格式。
2. 运营航班显示界面的 `msg``schd` 消费样例和字段要求,特别是变更、删除、重复投递的处理方式。 2. 运营航班显示界面的 `msg``schd` 消费样例和字段要求,特别是变更、删除、重复投递的处理方式。
3. CIIMS adapter 方提供的 `CMINMSGS``COUTMSGS` 现行 DDL、读写样例和写权限说明,核对现役实体映射列名。 3. CIIMS adapter 方提供的 `CMINMSGS``COUTMSGS` 现行 DDL、读写样例和写权限说明,核对旧系统实体映射列名,并确认信箱编号单调递增、不复用、不回退
4. 13 类参考数据与资源状态的编号规则,以及自有 PostgreSQL 的迁移 DDL。 4. 13 类参考数据与资源状态的编号规则,以及自有 PostgreSQL 的迁移 DDL。
5. Elasticsearch 历史索引映射、文档样例、逐条或批量写入响应,以及写入结果不明时的对账规则 5. Redis 投影的 key 与 value 结构、序列化方式及网页客户端读取约定
6. AODB 应答与 `EROR` 样例,含请求与应答的对应字段、超时判定依据。
7. Elasticsearch 历史索引映射、文档样例、逐条或批量写入响应、索引保留期与容量上限,以及写入结果不明时的对账规则。
只有上述证据核对完成后,待定字段才能转为字段级契约;任何新增字段、默认值或错误码都需写明其来源与对接方。 只有上述证据核对完成后,待定字段才能转为字段级契约;任何新增字段、默认值或错误码都需写明其来源与对接方。
+20 -19
View File
@@ -14,7 +14,7 @@
| 术语 | 语义 | | 术语 | 语义 |
|---|---| |---|---|
| 扫描谓词 | 信箱读取条件「处理标记为空」(`INV-2b`);本系统不以 ID 区间或水位作为消费边界。 | | 扫描谓词 | 信箱读取条件「处理时间为空」(`INV-2b`);本系统不以 ID 区间或水位作为消费边界。 |
| 队头 | 最小的未完成消息(`PENDING``FAILED` 都占位)。 | | 队头 | 最小的未完成消息(`PENDING``FAILED` 都占位)。 |
| 终态 | `SUCCEEDED` / `SKIPPED` / `DEAD`;到达后队列方可推进。 | | 终态 | `SUCCEEDED` / `SKIPPED` / `DEAD`;到达后队列方可推进。 |
| 回填意图 | 「还欠一次信箱标记」的持久化事实,与终态同一条语句落库,且发生在 Redis 投影写成功之后(`INV-23`)。 | | 回填意图 | 「还欠一次信箱标记」的持久化事实,与终态同一条语句落库,且发生在 Redis 投影写成功之后(`INV-23`)。 |
@@ -30,7 +30,7 @@
| `REF_MASTER` | SIS 消息提供的静态参考数据与资源状态(目标表) | `(RTYPE, RKEY)` 唯一;`RTYPE` 类别、合并语义与资源状态见「静态参考数据」;取数路径见 [requirements.md](requirements.md) `US-13`。 | | `REF_MASTER` | SIS 消息提供的静态参考数据与资源状态(目标表) | `(RTYPE, RKEY)` 唯一;`RTYPE` 类别、合并语义与资源状态见「静态参考数据」;取数路径见 [requirements.md](requirements.md) `US-13`。 |
| `FLIGHT_SCHD` | 航班标量及单值异常字段 | `FLID` 主键;`OPERATION_DAY` 一经确定不可变;版本与最近消息 ID 用于追踪。变长集合存于资源明细表与 `FLIGHT_ROUTE_POINT`,规则见「航班域」。 | | `FLIGHT_SCHD` | 航班标量及单值异常字段 | `FLID` 主键;`OPERATION_DAY` 一经确定不可变;版本与最近消息 ID 用于追踪。变长集合存于资源明细表与 `FLIGHT_ROUTE_POINT`,规则见「航班域」。 |
| `SCHD_SNAP_LOG` | 日计划处理留痕 | 只追加、可重建,不参与状态决策;保留期见 [reference.md](reference.md)。 | | `SCHD_SNAP_LOG` | 日计划处理留痕 | 只追加、可重建,不参与状态决策;保留期见 [reference.md](reference.md)。 |
字段与索引以 `src/main/resources/db/migration/` 的迁移链为准(Oracle 11g 目录为占位,未接入 Flyway)。报文原文仍从共享信箱读取,原文保留期必须满足 `C-7`;清除前提保留期下界与处理标记值集`C-5``C-12` 字段与索引以 `src/main/resources/db/migration/` 的迁移链为准(Oracle 11g 目录为占位,未接入 Flyway)。报文原文仍从共享信箱读取,原文保留期必须满足 `C-7`处理标记值集见 `C-5`清除前提保留期下界见 `C-6``C-9``C-11`
## 2. 消息、身份与决策 ## 2. 消息、身份与决策
@@ -66,10 +66,10 @@
### 4.1 收报流程 ### 4.1 收报流程
`InboxPoller` 按配置周期查信箱中「处理标记为空」的行,按编号升序、每批有上限,在自有 PG 登记 `PENDING``INV-2b`)。每轮: `InboxPoller` 按配置周期查信箱中「处理时间为空」的行,按编号升序、每批有上限,在自有 PG 登记 `PENDING``INV-2b`)。每轮:
1. 信箱不可读时记日志、等下一轮——这是基础设施失败,不能当成「没有新消息」。 1. 信箱不可读时记日志、等下一轮——这是基础设施失败,不能当成「没有新消息」。
2. 取「处理标记为空」的行的升序前 `PARAM:msgx.pipeline.claim-batch` 条。 2. 取「处理时间为空」的行的升序前 `PARAM:msgx.pipeline.claim-batch` 条。
3. 在同一个 PG 事务内对每一行 `insertIfAbsent(MSG_ID, RECEIVED_AT, ENQUEUED_AT)`;主键冲突表示已登记(重复扫描与兼容入口并发都安全),不计入、不报错。 3. 在同一个 PG 事务内对每一行 `insertIfAbsent(MSG_ID, RECEIVED_AT, ENQUEUED_AT)`;主键冲突表示已登记(重复扫描与兼容入口并发都安全),不计入、不报错。
4. 提交。已打标的行不再出现在扫描结果里;终态但未回填的行会被重复读到,按已有记录幂等跳过。 4. 提交。已打标的行不再出现在扫描结果里;终态但未回填的行会被重复读到,按已有记录幂等跳过。
@@ -81,7 +81,7 @@
### 4.3 兼容 HTTP 入口 ### 4.3 兼容 HTTP 入口
`POST /cminmsgs/send` 把报文写入共享信箱(处理标记为空),效果与上游投递一致:由收报扫描发现、登记、处理。客户端失败重试可能再次写信箱,业务身份去重仍然必需。响应语义见 `C-28` `POST /cminmsgs/send` 把报文写入共享信箱(处理时间为空),效果与上游投递一致:由收报扫描发现、登记、处理。客户端失败重试可能再次写信箱,业务身份去重仍然必需。响应语义见 `C-28`
### 4.4 单实例 ### 4.4 单实例
@@ -185,7 +185,7 @@ LIMIT PARAM:msgx.pipeline.backfill-batch
| 信箱行不存在 | 写入 0 行且信箱行不存在 | **立即放弃自动重试**(原因 `MISSING_ROW`)并告警。终态行存在而信箱行不存在,只可能是该行在入队后被删除(永久空洞 ID 从不入队,不会进入本扫描) | | 信箱行不存在 | 写入 0 行且信箱行不存在 | **立即放弃自动重试**(原因 `MISSING_ROW`)并告警。终态行存在而信箱行不存在,只可能是该行在入队后被删除(永久空洞 ID 从不入队,不会进入本扫描) |
| 暂时性故障持续超期 | 超时 / 连接失败持续到 `R` 仍未打标 | **停止自动重试**(原因 `TRANSIENT_DEADLINE`)并告警;`R` 之前只退避重试,**不按尝试次数放弃**;保留人工恢复能力 | | 暂时性故障持续超期 | 超时 / 连接失败持续到 `R` 仍未打标 | **停止自动重试**(原因 `TRANSIENT_DEADLINE`)并告警;`R` 之前只退避重试,**不按尝试次数放弃**;保留人工恢复能力 |
**放弃 ≠ 标记已确认**:放弃行不写 `BACKFILL_AT`因此不满足 `C-8` 的清除前提,库方不得据此清除;放弃清单需人工对账确认后才可用于清除判定 **放弃 ≠ 标记已确认**:放弃行不写 `BACKFILL_AT`处理标记仍为空,「行最终都有标记」因此不能对外承诺(`CLM-4`);放弃清单能否作为清除判定依据,属未确认的清除协议(`C-8`;见 `Q7`/`Q9`
### 6.3 `R` 的作用 ### 6.3 `R` 的作用
@@ -196,15 +196,16 @@ LIMIT PARAM:msgx.pipeline.backfill-batch
放弃判据用**时间**而不是**尝试次数**:固定次数不能稳定表达允许的故障持续时间,因此按 `R` 判断放弃,`PARAM:msgx.pipeline.backfill-max-attempts` 只用于告警。 放弃判据用**时间**而不是**尝试次数**:固定次数不能稳定表达允许的故障持续时间,因此按 `R` 判断放弃,`PARAM:msgx.pipeline.backfill-max-attempts` 只用于告警。
关于「最终一定打标」,准确表述是三段,缺一不可 关于「最终一定打标」,本系统能保证的只有两段
1. 退避重试(`R` 之前不放弃); 1. 退避重试(`R` 之前不放弃);
2.`R` 仍失败则停止自动重试、告警,进入放弃清单,保留人工恢复(`reopen` 2.`R` 仍失败则停止自动重试、告警,保留人工恢复(`reopen`
3. `C-8` 允许以「放弃清单 + 人工确认」作为清除判定,避免一行永久卡住整个分区。
第三段「库方以放弃清单作为清除判定」未确认,因此「最终一定打标」当前不可承诺(`CLM-4`)。
两个边界要说清:`MISSING_ROW`(信箱行不存在)是**确定性结论**,立即放弃,不受 `R` 保护;`R` 只要求 `R ≤ R_keep`,原文保留期的唯一约束来源是 `C-7` 两个边界要说清:`MISSING_ROW`(信箱行不存在)是**确定性结论**,立即放弃,不受 `R` 保护;`R` 只要求 `R ≤ R_keep`,原文保留期的唯一约束来源是 `C-7`
库方清除语义是「打标即可清除」,则清除前提`C-6``C-8`不成立,必须与库方另定保留期;增大 `R` 无效。 库方清除语义未确认`C-6``C-8`;见 `Q7`/`Q9`):若为「打标即可清除」,`C-7` 的保留期下界不成立,必须与库方另定;增大 `R` 无效。
## 7. 日计划快照与请求匹配 ## 7. 日计划快照与请求匹配
@@ -248,7 +249,7 @@ PENDING → SENT → DONE
发送确认后才标记 `SENT`,失败记录次数并按退避推后,达到上限转 `DEAD`(记录保留作 DLQ)。所有外部调用需要有界超时,避免阻塞投递线程。 发送确认后才标记 `SENT`,失败记录次数并按退避推后,达到上限转 `DEAD`(记录保留作 DLQ)。所有外部调用需要有界超时,避免阻塞投递线程。
投递是至少一次:Broker 或其他目标已接受但本地未标记成功时可能重发;目标端接受不等于业务消费者已消费。Kafka 生产约束沿用 `D3`,生产者幂等不替代应用层事件去重。 投递是至少一次:Broker 或其他目标已接受但本地未标记成功时可能重发;目标端接受不等于业务消费者已消费。Kafka 生产约束沿用 `D2`,生产者幂等不替代应用层事件去重。
### 8.2 `schd` 聚合 ### 8.2 `schd` 聚合
@@ -285,7 +286,7 @@ PENDING → SENT → DONE
| 中断位置 | 重启后的判定 | 恢复动作 | | 中断位置 | 重启后的判定 | 恢复动作 |
|---|---|---| |---|---|---|
| 已落信、未入队 | 信箱行处理标记为空且 PG 无记录 | 重扫补建登记记录 | | 已落信、未入队 | 信箱行处理时间为空且 PG 无记录 | 重扫补建登记记录 |
| 事务执行中 | PG 无该消息终态 | 事务整体回滚,按 `PENDING` 重新处理 | | 事务执行中 | PG 无该消息终态 | 事务整体回滚,按 `PENDING` 重新处理 |
| 领域事务已提交、Redis 写失败或终态未提交 | 该消息无终态(`PENDING`),仍占队头 | 整条消息重处理:投影按当前完整态重写,领域变更依赖逐类幂等(`INV-20b``G-FLOP-IDEMPOTENT`),已提交结果不回滚(`INV-16` | | 领域事务已提交、Redis 写失败或终态未提交 | 该消息无终态(`PENDING`),仍占队头 | 整条消息重处理:投影按当前完整态重写,领域变更依赖逐类幂等(`INV-20b``G-FLOP-IDEMPOTENT`),已提交结果不回滚(`INV-16` |
| 事务已提交、标记未写 | 终态行仍持有回填意图 | 仅补写标记;业务处理结果保持不变 | | 事务已提交、标记未写 | 终态行仍持有回填意图 | 仅补写标记;业务处理结果保持不变 |
@@ -301,15 +302,15 @@ PENDING → SENT → DONE
**通则**(对本系统所有持久对象适用) **通则**(对本系统所有持久对象适用)
- **时间不构成清除依据**:到期只是必要条件,**终局证据才是充分条件**(共享库`C-8`,航班见 `INV-28`)。 - **时间不构成清除依据**:到期只是必要条件,**终局证据才是充分条件**(航班`INV-28`;共享库的清除依据属未确认的清除协议,见 `C-8``Q9`)。
- **证据不随清除消失**清除所依赖的证据(如 `C-8` 引用的回填放弃清单,本期承诺见 `C-16`在其覆盖的信箱边界被清除前必须保持可查。 - **证据不随清除消失**回填失败与放弃的记录在其覆盖的信箱被清除前保持可查`C-16`
- **证据缺失或结果不明时按最保守处置**:航班清理为删 0 条(`INV-28`)。 - **证据缺失或结果不明时按最保守处置**:航班清理为删 0 条(`INV-28`)。
**逐对象生命周期**(保留期取值一律见 [reference.md](reference.md) **逐对象生命周期**(保留期取值一律见 [reference.md](reference.md)
| 对象 | 终局判据 | 归档目标 | 清除证据 | 执行方 | 偏差 | | 对象 | 终局判据 | 归档目标 | 清除证据 | 执行方 | 偏差 |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| 共享信箱 `CMINMSGS` 原文 | 处理标记 / 回填放弃清单 | — | `C-8` | 库方 | 契约未确认(`Q7`/`Q9` | | 共享信箱 `CMINMSGS` 原文 | 处理标记 | — | 待确认(`Q9` | 库方 | 契约未确认(`Q7`/`Q9` |
| `FLIGHT_SCHD` + 资源明细 | 判史规则 | 历史存储 | 历史写入确认 + 版本复查 | 我们 | — | | `FLIGHT_SCHD` + 资源明细 | 判史规则 | 历史存储 | 历史写入确认 + 版本复查 | 我们 | — |
| 航班历史存储 | 保留期 | — | — | 我们 | `G-FLIGHT-HIST-RETENTION` | | 航班历史存储 | 保留期 | — | — | 我们 | `G-FLIGHT-HIST-RETENTION` |
| `SCHD_SNAP_LOG` | 保留期 | 无(本地可重建) | 无 | 我们 | — | | `SCHD_SNAP_LOG` | 保留期 | 无(本地可重建) | 无 | 我们 | — |
@@ -321,12 +322,12 @@ PENDING → SENT → DONE
候选 = 终态 **且** 回填已了结 **且** 终局后超过保留期(基准是 `UPDATED_AT`:终态与了结都推进它,了结后不再更新)。两处不可省: 候选 = 终态 **且** 回填已了结 **且** 终局后超过保留期(基准是 `UPDATED_AT`:终态与了结都推进它,了结后不再更新)。两处不可省:
- **回填已了结** = `BACKFILL_AT` 非空,或已放弃 **且经人工对账**。放弃行不写标记,是 `C-8` 的清除授权证据,未对账前不得删除。 - **回填已了结** = `BACKFILL_AT` 非空`INV-25`)。放弃行不写标记,按未了结保留,不参与删除。
- **写入前复查** = `DEAD` 可被人工重放改回 `PENDING`。人工重放走 `MessageLifecycleGate`、不取 `PIPELINE_LOCK`,因此该锁不构成复查依据:删除在同一事务内按候选时的 `STATE` 条件执行;影响 0 行即整体回滚、该行跳过。重放先一步改回 `PENDING` 时谓词不匹配,天然互斥。批量删除不得持 `PIPELINE_LOCK`——那会阻塞主泵 FIFO,与「作业不使到期消息饥饿」冲突。 - **写入前复查** = `DEAD` 可被人工重放改回 `PENDING`。人工重放走 `MessageLifecycleGate`、不取 `PIPELINE_LOCK`,因此该锁不构成复查依据:删除在同一事务内按候选时的 `STATE` 条件执行;影响 0 行即整体回滚、该行跳过。重放先一步改回 `PENDING` 时谓词不匹配,天然互斥。批量删除不得持 `PIPELINE_LOCK`——那会阻塞主泵 FIFO,与「作业不使到期消息饥饿」冲突。
清理范围只含「终态且已回填」;保留期内同身份去重成立(`INV-9`,保留期过后同身份消息按新消息处理(去重记忆期 = 保留期,见 [specification.md](specification.md)「契约数值」) 清理范围只含「终态且已回填」;保留期内同身份去重成立(`INV-9`)。
**时间常数排序**`R ≤ R_keep`、放弃清单可见期 ≥ `R_keep` 两个下界关系的定义与理由见 [specification.md](specification.md)「契约数值」,本文件不复述。只补一条实现口径:保留期计的是**终局之后**的时间,不是入队之后——终态行未了结回填时不进入候选。 **时间常数排序**`R ≤ R_keep` 的定义与理由见 [specification.md](specification.md)「契约数值」,本文件不复述。只补一条实现口径:保留期计的是**终局之后**的时间,不是入队之后——终态行未了结回填时不进入候选。
**其余清理** **其余清理**
@@ -334,7 +335,7 @@ PENDING → SENT → DONE
- **留痕清理**`SCHD_SNAP_LOG` 按保留期与 `(SCOPE_END, RECV_AT)` 删除,不依赖历史存储开关。 - **留痕清理**`SCHD_SNAP_LOG` 按保留期与 `(SCOPE_END, RECV_AT)` 删除,不依赖历史存储开关。
- **出站事件清理**:见「事件清理」。 - **出站事件清理**:见「事件清理」。
共享信箱保留策略由库方管理(`C-5``C-12`)。历史写入与删除事件入队之间仍需恢复方案;顺序调用不构成原子提交。 共享信箱保留策略由库方管理(`C-6``C-9``C-11`)。历史写入与删除事件入队之间仍需恢复方案;顺序调用不构成原子提交。
## 10. 容量假设与设计取舍 ## 10. 容量假设与设计取舍
+5 -5
View File
@@ -44,7 +44,7 @@
| `msgx.identity.include-day-boundary` | `false` | 安全默认 | 身份是否加日期边界;影响去重语义,不能当调优项切换(`Q11` | | `msgx.identity.include-day-boundary` | `false` | 安全默认 | 身份是否加日期边界;影响去重语义,不能当调优项切换(`Q11` |
| `msgx.health.backlog-cache-ttl-ms` | `30000` | 假定 | 积压快照缓存窗口;`0` = 不缓存;`/health``/metrics` 共用同一快照 | | `msgx.health.backlog-cache-ttl-ms` | `30000` | 假定 | 积压快照缓存窗口;`0` = 不缓存;`/health``/metrics` 共用同一快照 |
| `msgx.history.history-store-enabled` | `false` | 安全默认 | 历史存储门控;关闭时历史清理删 0 条 | | `msgx.history.history-store-enabled` | `false` | 安全默认 | 历史存储门控;关闭时历史清理删 0 条 |
| `msgx.history.cancelled-hours` | `48` | 假定 | 历史清理的取消态判据窗口 | | `msgx.history.cancelled-hours` | `1` | 假定 | 历史清理的取消态判据窗口 |
| `msgx.history.terminal-hours` | `48` | 假定 | 历史清理的终态判据窗口 | | `msgx.history.terminal-hours` | `48` | 假定 | 历史清理的终态判据窗口 |
| `msgx.history.deleted-hours` | `48` | 假定 | 历史清理的已删除判据窗口 | | `msgx.history.deleted-hours` | `48` | 假定 | 历史清理的已删除判据窗口 |
| `msgx.history.idle-hours` | `168` | 假定 | 历史清理的静默期判据 | | `msgx.history.idle-hours` | `168` | 假定 | 历史清理的静默期判据 |
@@ -61,9 +61,9 @@
| `mailbox.shared-mysql.url` | 环境变量 | 安全 | 零入库,见 `.env.example`;同组 `username` / `password` | | `mailbox.shared-mysql.url` | 环境变量 | 安全 | 零入库,见 `.env.example`;同组 `username` / `password` |
| `datasources.default.connection-timeout` | `5000` | 假定 | 自有 PG 池(毫秒数,非 Duration 字面量);同组 `validation-timeout=3000``idle-timeout=300000``max-lifetime=1800000` | | `datasources.default.connection-timeout` | `5000` | 假定 | 自有 PG 池(毫秒数,非 Duration 字面量);同组 `validation-timeout=3000``idle-timeout=300000``max-lifetime=1800000` |
| `datasources.default.data-source-properties.connectTimeout` | `3` | 假定 | 驱动级连接超时(秒);同组 `socketTimeout=30` | | `datasources.default.data-source-properties.connectTimeout` | `3` | 假定 | 驱动级连接超时(秒);同组 `socketTimeout=30` |
| `kafka.producers.default.acks` | `all` | 契约(`D3` | 允许环境变量覆盖 | | `kafka.producers.default.acks` | `all` | 契约(`D2` | 允许环境变量覆盖 |
| `kafka.producers.default.enable-idempotence` | `true` | 契约(`D3` | 允许环境变量覆盖 | | `kafka.producers.default.enable-idempotence` | `true` | 契约(`D2` | 允许环境变量覆盖 |
| `kafka.producers.default.max-in-flight-requests-per-connection` | `1` | 契约(`D3` | 启动自检钉住三项联合满足 `D3`;允许环境变量覆盖 | | `kafka.producers.default.max-in-flight-requests-per-connection` | `1` | 契约(`D2` | 启动自检钉住三项联合满足 `D2`;允许环境变量覆盖 |
环境变量清单以 `.env.example` 为准。 环境变量清单以 `.env.example` 为准。
@@ -100,7 +100,7 @@
| 回填与投递 | `processing/BackfillService.kt``delivery/Dispatcher.kt``infra/kafka/KafkaDeliveryPort.kt` | | 回填与投递 | `processing/BackfillService.kt``delivery/Dispatcher.kt``infra/kafka/KafkaDeliveryPort.kt` |
| 维护作业 | `jobs/JobRunner.kt``jobs/HistorySweepJob.kt``jobs/EventCleanupJob.kt``infra/persistence/SnapshotLogPurge.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` | | 持久化与恢复 | `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``D3` 三联合启动自检) | | 启停与配置 | `PipelineLifecycle.kt``config/PipelineProps.kt``config/HistoryProps.kt``config/OperationDayProps.kt`(含运营日时区启动自检)、`config/MailboxProps.kt``config/KafkaD3Check.kt``D2` 三联合启动自检) |
| 指标与健康 | `infra/metrics/PipelineMetrics.kt``infra/metrics/JobActivity.kt``infra/health/BacklogSnapshotProvider.kt``infra/health/JobRunnerHealthIndicator.kt` | | 指标与健康 | `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/` 为占位) | | 迁移 | `src/main/resources/db/migration/`(单基线 `V1__flight_state_baseline.sql``oracle11g/` 为占位) |
+202 -183
View File
@@ -1,238 +1,257 @@
# 规范:术语、契约、前提、不变量与声明边界 # 规范:术语、契约、前提、不变量与声明边界
本文定义 msgexchange-v2 的术语、外部契约、前提、不变量与声明边界。
内容取自 [requirements.md](requirements.md)、[architecture.md](architecture.md) 与 [contracts/interface-contract.md](contracts/interface-contract.md);机制与数据模型见 [implementation.md](implementation.md),参数与指标见 [reference.md](reference.md)。
`C-x``PRE-x``INV-x``CLM-x``Qn``G-NAME` 只在本文件定义;ID 语法见 [README.md](README.md)。
契约条目的写法:整条口径还需对方确认的,末尾标 `(待确认 Qn`;正文已定、只有个别字段未定的,另起一行以「待确认:」列出并指向 `Qn`。未标注的,是本系统自己遵守或已由需求与架构定下。作废条款集中放在各节末尾,保留原文与作废标记。
## 1. 术语 ## 1. 术语
| 术语 | 含义 | | 术语 | 含义 |
|---|---| |---|---|
| 上游 | 报文的写入方。现在是 CIIMS adapter,它把 AODB 下发的报文写进信箱。 | | 上游 | 产生报文的 AODB;报文由 CIIMS adapter 写入信箱。 |
| 信箱 | CIIMS adapter 的 MySQL 里用于消息交换的表——入站 `CMINMSGS`、出站 `COUTMSGS`;报文由 adapter 写入,网关读取。 | | 信箱 | 共享 MySQL 的 `CMINMSGS`(入站)与 `COUTMSGS`(出站);归库方所有,本系统只读写消息、回写处理标记。 |
| 库方 | CIIMS adapter 方。信箱表在它的 MySQL 里,表结构变更与数据清除由它执行,机制未定;本系统只读消息、写回处理标记,不改表结构、不清数据。 | | 库方 | 信箱所在的共享 MySQL 管理方,即 CIIMS adapter 方。 |
| 处理标记 | 信箱行上表示已处理」的字段,需求文档说的「处理时间」就是它。本系统只把空标记写成已处理值,不回撤、不覆盖。 | | 处理标记 | 信箱行上表示已处理的值。读取条件是「处理时间为空」;处理完成时写入完成时刻。本系统只填空值,不回撤、不覆盖。 |
| 落信 | 报文入信箱成为其中一行。写入的是 adapter 或兼容 HTTP 入口。 | | 落信 | 报文入信箱成为一行;入站由 CIIMS adapter 或兼容入口写入,出站由本系统写入 `COUTMSGS`;入站报文写进信箱后可能尚未登记。 |
| 入队 | 本系统在自有 PG 这条报文建立一条处理记录,开始处理。 | | 登记 | 本系统在自有 PG 这条报文建立处理记录,排队等待处理。 |
| 已回填 | 本系统已把处理标记写回该信箱行。 | | 处理完成 | 业务数据已写入自有 PG,该写的 Redis 投影也已写成功。 |
| 处理完成 | 消息处理完了:业务数据已写入自有 PG,该写的 Redis 投影也已写成功。 | | 回填 | 把处理标记写回信箱行。 |
| 投递确认 | 发出去的消息对方已接收,本系统的投递记录也记成已发送;这不代表对方的业务已经消费。 | | 投递 | 读待发事件发 Kafka。 |
| Redis 投影 | 本系统写进 Redis、供查询的航班数据;完整数据在本系统的 PostgreSQL 数据库里。 | | Redis 投影 | 供页面与 `GET /all/flights` 查询的航班数据;只由本系统写入和移除,不作权威。 |
| 自有 PG | 本系统自己的业务数据库 PostgreSQL,处理结果都存在这里;与信箱之间没有跨库事务。生产环境用 PostgreSQL 还是 Oracle 11g,还没定。 | | 自有 PG | 本系统唯一的业务数据库;航班当前态、管道记录与静态参考数据都在这里。 |
| admin-api | 下游查询系统;直接读本系统写入的静态参考数据表,不向本系统提供数据。 | | 权威 | 航班当前态以自有 PG 为准;信箱、Redis、Kafka 与展示视图都不是。 |
| 出站请求 | 经 `COUTMSGS` 发向 AODB 的 `RQRD` 参考数据请求与 `RQFD` 日计划请求;交付承诺止于落信。 |
条款后面的状态标记有四种:`[待确认 Qn]`=还没拿到对方确认;`[已确认 YYYY-MM-DD]`=对方已确认并记录在此;`[我们单方承诺]`=不等对方、自己已生效的对外承诺;`[我们自证]`=本系统自身的部署事实,无需确认。 | 运营航班显示界面 | Kafka 主题 `msg``schd` 的消费方,即需求所称网页客户端:变更通知走 Kafka(`US-08`),航班查询与 `GET /all/flights` 同源读 Redis 投影(`INV-24`)。 |
| 航班历史 | 已结束航班写入 Elasticsearch 后的副本;写成功后才从实时数据删除。 |
| 静态参考数据 | 13 类基础数据与资源状态,写自有 PG 的独立数据表,admin-api 直接只读。 |
## 2. 契约 ## 2. 契约
读者:库方(共享 MySQL 管理方)接口人、上游(CIIMS / AODB / SIS)接口人、本系统开发与运维。
### 2.1 共享信箱(库方) ### 2.1 共享信箱(库方)
**ID 与可见性** - **C-3** 信箱编号不复位、不复用、不回退,含表轮换、备份恢复与自增归零。不丢消息依赖这条(`PRE-2``PRE-3`)。`(待确认 Q2`
- **C-4** 报文行不可变:同一业务身份(`SNDR``TYPE``STYP``SEQN`)的重发是同一内容。`(待确认 Q15`
- **C-5** 处理标记的允许值由库方认可,本系统只在该值集内写入;死信、跳过与回填失败的原因记在自有 PG,不在信箱新增取值。
待确认:允许值的具体取值(`Q7`)。
- **C-6** 原文的保留期与清除时机由库方定;本系统不承诺清除时间,只要求不早于 `C-7` 的下界。`(待确认 Q7、Q9`
- **C-7** 原文保留期下界:回填重试期间原文必须还在(`US-10` AC2)。`(待确认 Q7、Q9`
- **C-8** 清除由库方执行;本系统不执行 DDL、不清数据、不写共享历史表(`C-14`)。清除是否需要标记以外的证据,另行确认。`(待确认 Q9`
- **C-9** 清除的方案与 DDL 授权由库方定,本系统不参与选型。`(待确认 Q9`
- **C-10** 信箱时间列由写入方写入,其时钟基准须可解释(`PRE-4`)。
待确认:时区与允许偏斜(`Q7`)。
- **C-12** 原文被提前清除时,本系统不补造原文,也不把这类消息当成功:尚未处理的消息按处理失败回滚、保持未完成,错误记在对应处理记录上(`US-03` AC3/AC5)。`(本系统单方承诺)`
- **C-14** 本系统不建表、不改表结构、不迁移 schema、不清数据,也不写共享历史表。
- **C-15** 处理标记只在空值上写入处理完成时刻,写完不回撤、不覆盖已有值。`(本系统单方承诺)`
- **C-16** 回填失败的行保留记录并告警,记录在对应信箱行被清除前保持可查。`(本系统单方承诺)`
- **C-30** 信箱读取口径:按配置周期读取「处理时间为空」的行,按编号升序、每批有上限;单活动实例运行,不引入并行消费者。
### 2.2 上游(AODB / SIS
- **C-20** 业务身份由 `SNDR``TYPE``STYP``SEQN` 组成,语义由上游定义,取值范围与回绕以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 为准;`SEQN` 的重置周期未知,它决定身份是否加入日期边界。`(待确认 Q11`
- **C-21** `FLID` 在保留期内不复用;复用会让「只进不退」的合并把新航班的事件压掉。`(待确认 Q16`
- **C-23** 应答到达时按报文类型对应到等待中的请求;AODB 发错或迟到的应答不更新数据,记录后跳过(`US-09` AC2)。`SCHD-RESP` 只在请求未过期时生效,迟到的应答不更新数据(`US-07`)。
待确认:用哪些字段对应、超时如何判定(`Q5`)。
- **C-24** 出站信箱 `COUTMSGS`:消费方是 CIIMS adapter。`RQRD``RQFD` 各自同时最多一条已落信、未结案的请求;同一子类型发新请求时旧请求作废,新请求登记为待发送,等同一报文类型的在途请求收到应答、失败或超时后再落信;请求超过时限未等到应答标记超时(`US-09` AC1)。收到 `EROR` 时定位到本系统发出的请求,标记失败并告警(`US-09` AC3)。重试只针对仍有效且确认未落信的请求;写入结果不明时记录并告警,不直接重发;交付承诺只到落信(架构「主流程」)。
待确认:ACK 与错误列的语义及写入责任、出站行的清除与保留期、重复落信的识别规则(`Q10`)。
- **C-25** 删除主航班时级联删除其共享航班,原子提交、不删一半;本系统不向 AODB 回发 `EROR``US-06` AC2)。
- **C-26** 日计划快照里没有携带的字段视为 AODB 已删除该值,本地同步清除(`US-07` AC3)。
### 2.3 HTTP 入口
- **C-28** `POST /cminmsgs/send` 的响应只表示接收结果:成功返回信箱编号,失败不返回编号;媒体类型支持 `text/xml``application/xml``text/plain`,默认 UTF-8;空报文、超过大小上限与非法 XML 不落信;解析禁用外部实体与外部资源访问;仅限内网,来源由网络层限制。
待确认:请求体上限取值与失败响应样例(`Q3`)。
- **C-33** `POST /schd/sync` 触发一次 `RQFD` 日计划请求;成功响应无论表示已登记还是已落信,都不代表 AODB 已收到,交付承诺止于落信(`C-24`)。
待确认:成功响应表示已登记还是已落信(`Q17`)。
### 2.4 下游(admin-api 与运营航班显示界面)
- **C-29** 对外投递按至少一次设计,不承诺端到端恰好一次:应用重启与待发事件重发都可能让同一条消息多发一次。主题 `msg` 上同一 `FLID` 的变更保序,跨 `FLID` 不承诺顺序;`schd` 不在本条保序范围内。
待确认:Kafka key 与分区规则(`Q4`)。
- **C-31** admin-api 直接读取本系统写入的静态参考数据表;本系统不调用 admin-api,也不从它拉取、补全或合并数据。
- **C-32** Redis 航班投影的读取口径:网页客户端与 `GET /all/flights` 读同一份投影,返回当前全部动态航班(不含共享航班)、不分页;投影只由本系统写入和移除,不是权威、也不存处理状态;Redis 异常时报错,不返回空列表(`INV-24`)。
待确认:投影 key/value 结构与网页客户端读取约定(`Q20`)。
### 2.5 已作废条款
- **C-1** ID 单调:信箱 ID 按提交顺序分配,已发布水位之下不再出现更小的新 ID。`[待确认 Q2]` **[作废 by C-30]** - **C-1** ID 单调:信箱 ID 按提交顺序分配,已发布水位之下不再出现更小的新 ID。`[待确认 Q2]` **[作废 by C-30]**
- **C-2** ID 分配 → 事务可见时延上界由库方**直接给出**。该值决定空洞老化阈值;**不可由 SIS 报文 `Expiry` 推导**`Expiry` 是报文保留与传输恢复口径,与「ID 分配后多久对读事务可见」不是同一个量)。`[待确认 Q2]` **[作废 by C-30]**2026-09-14:扫描改为处理标记谓词,按轮自愈,可见时延不再决定任何阈值。) - **C-2** ID 分配 → 事务可见时延上界由库方**直接给出**。该值决定空洞老化阈值;**不可由 SIS 报文 `Expiry` 推导**`Expiry` 是报文保留与传输恢复口径,与「ID 分配后多久对读事务可见」不是同一个量)。**[作废 by C-30]**
- **C-3** ID 空间不复位、不复用、不回退:含表轮换、备份恢复、`AUTO_INCREMENT` 归零。采用整表轮换方案时,新表种子必须 ≥ `max(ID)+1`,保证 ID 不断链;本系统的水位 `W` 是不可逆单游标,ID 回退会导致其后所有行永久不可见`[待确认 Q2]` - **C-11** 原文保留期是否沿用旧系统的 1 天窗口待确认;无论取何值都必须满足 `C-7` 的下界`待确认 Q9` **[作废 by C-6]**
- **C-4** 报文行不可变:同一业务身份(`SNDR|TYPE|STYP|SEQN`)的重发必为同一内容。若上游会以同一身份改发正文,需要另定识别规则(`Q15`)。`[待确认 Q15]`
**保留与清除(标记、保留期、清除前提)**
- **C-5** 处理标记值集与写权限:本系统只写入库方认可的 legacy 值集内的「已处理」值(默认值见 [reference.md](reference.md) `PARAM:mailbox.processed-value`),只写空标记、不回撤、不覆盖;内部原因(死信、重复、放弃)记录在自有 PG,**不在信箱新增枚举**。`[待确认 Q7]`
- **C-6** 清除语义必须是「标记 + 保留期」:打标本身不触发清除,触发条件是「到达保留期 `R_keep`」且「边界内全部行已打标」。若库方语义是「打标即可清除」,则清除前置条件不成立,且**增大 `R` 无法补救**,必须与库方另定保留期。`[待确认 Q7][待确认 Q9]`
- **C-7** 保留期下界(本文件是唯一定义处):
`R_keep ≥ max(审计期限, 回填重试上限)`
报文在 CIIMS 的 `Expiry`480 分钟量级,`SIS:3.16`)可作为原文保留期的参照,但它是报文有效期,不等于本处所需的保留期。`[待确认 Q7][待确认 Q9]`
- **C-8** 清除前置条件(本文件是唯一定义处):执行清除时,边界内**每行必须已有终局**——即「已持有处理标记」**或**「已登记在本系统的回填放弃清单中且经人工对账确认」。放弃行不写标记,未达终态的行顺延至处理完成后清除;本系统不执行 DDL,也不写共享历史表。`[待确认 Q7][待确认 Q9]`
- **C-9** 清除执行方与方案:清除由库方执行或书面授权执行。方案 A(按 `DATE_RECEIVED` 日分区 + `TRUNCATE/DROP PARTITION`)为首选;方案 B`CREATE TABLE ... LIKE` + 保留窗复制 + `RENAME TABLE` + 对账 + `CMINMSGS_HST` 归档 + `DROP`)为备选。现场 MySQL 版本与分区 DDL 能力待确认。`[待确认 Q9]`
- **C-10** 时间语义:时间比较与换算统一采用机场时区 `Asia/Shanghai` 及明确类型转换;`DATE_RECEIVED` 由上游/库方写入,其时钟基准需可解释(见 `PRE-4`)。`[待确认 Q7]`
**原文保留**
- **C-11** legacy 现役按接收超 1 天归档并删除 `CMINMSGS`;若沿用该窗口,则与 `C-7` 冲突,须以 `C-7` 为准。`[待确认 Q9]`
- **C-12** 若原文被提前清除(违反保留契约),本系统的死信处置不变,按契约违例走运维追责;该情形不改变 `C-8` 的清除前提。`[我们单方承诺]`
**我们向库方的承诺**
- **C-13** 只读约定区间的信箱行(`ID > W`),单活动实例运行,不引入并行消费者。`[我们单方承诺]` **[作废 by C-30]** - **C-13** 只读约定区间的信箱行(`ID > W`),单活动实例运行,不引入并行消费者。`[我们单方承诺]` **[作废 by C-30]**
- **C-14** 不建表、不改表结构、不迁移 schema、不写共享历史表;兼容 HTTP 入口按既有契约写入入站信箱。`[我们单方承诺]` - **C-22** 报文不可变,见 `C-4``(待确认 Q15` **[作废 by C-4]**
- **C-15** 处理标记只写 `C-5` 认可的值,不回撤、不覆盖已有非空标记。`[我们单方承诺]`
- **C-16** 回填放弃清单在对应信箱边界被清除前必须保持可查:`C-8` 以本清单作为清除授权证据之一,该证据不得随处理记录的清除而消失。`[我们单方承诺][待确认 Q7][待确认 Q9]`
- **C-30** 信箱读取口径:本系统按配置周期扫描信箱,谓词为「处理标记为空」,按编号升序读取、每批有上限;单活动实例运行,不引入并行消费者。编号即到达顺序:已读过的编号之后不应再出现更小的新编号(较小编号迟提交只会被发现得晚,不会丢;ID 空间不复位、不复用见 `C-3`)。`[我们单方承诺]`(顺序依据 `[待确认 Q2]`
### 2.2 上游(SIS / AODB ## 3. 前提
- **C-20** 业务身份四元组 `SNDR|TYPE|STYP|SEQN` 的语义由上游定义;`SEQN` 的取值范围与回绕见 `SIS:2.8.1`。**重置周期未知**,它决定业务身份是否加入日期边界(默认不加)。`SNDR` 取值域也需对拍(SIS 为 AODB/RMSlegacy 实发 OSH5 等)。`[待确认 Q11]`
- **C-21** `FLID` 在保留期内不复用。若复用,事件版本(`STATE_VERSION`)必须按 incarnation 作用域,否则「保留最新版本」的合并规则会把新航班的事件压掉,旧 tombstone 也可能删掉在用航班。`[待确认 Q16]`
- **C-22** 报文不可变(同 `C-4`)。`[待确认 Q15]`
- **C-23** 请求/应答回显契约:目标优先按已确认的回显字段精确匹配;回显未确认时的降级匹配(同类开放请求且报文 `DTTM ≥ sentAt`)存在跨代误配风险,必须明确接受并审计,不得宣称精确关联。比较前统一时区与时间单位。`[待确认 Q5]`
- **C-24** 出站信箱 `COUTMSGS`:消费方为 CIIMS adapter。未确认项:消费顺序、`COUTMSGS_ACK_DATE_RECV` / `COUTMSGS_ACK_RESEND_TIMES` / `COUTMSGS_DATE_SENT` / `COUTMSGS_ERROR` 各列语义与写入责任、出站行清除责任与保留期、落信成功但本地未置 `SENT` 时的重复写入风险及下游去重契约。本系统对出站的交付承诺只到**落信**为止。`[待确认 Q10]`
- **C-25** 删除主航班时级联删除其共享航班,原子提交、不允许删一半;不向 AODB 回发 EROR。`[我们单方承诺]`2026-09-14 定案,依据 `US-06` AC2SIS 原要求见 `SIS:1.6.1-1.d``SIS:4.8`。)
- **C-26** 日计划快照中未携带的可选字段视为 AODB 已删除该值,本地同步清除。`[我们单方承诺]`2026-09-14 定案,依据 `US-07` AC3SIS 语义见 `SIS:3.16-note-4``SIS:3.17`。)
- **C-28** 兼容 HTTP 入口的响应只表示**接收结果**,不表示业务处理成功:成功返回信箱编号,失败不返回编号;目标为现役 `ResponseDto`。请求媒体类型支持 `text/xml``application/xml``text/plain`,默认 UTF-8;请求体上限取值(暂定 10MB)与失败响应样例仍需与现役逐项对拍。仅限内网使用,来源由网络层限制。`[待确认 Q3]`
- **C-29** 对外投递按**至少一次**设计,不承诺端到端恰好一次;Kafka 消息的 key 为 `FLID`,同一 `FLID` 内保序,跨 `FLID` 不承诺顺序。`[待确认 Q4]`
### 2.3 下游(admin-api
- **C-31** admin-api 直接读取本系统写入的静态参考数据表;本系统不调用 admin-api,也不从 admin-api 拉取、补全或合并任何数据。`[我们单方承诺]`
## 3. 前提(外部提供)
前提失效时不变量必须整体重估。 前提失效时不变量必须整体重估。
| 编号 | 前提 | 若不成立的影响 | 状态 | | 编号 | 前提 | 若不成立的影响 | 状态 |
|---|---|---|---| |---|---|---|---|
| PRE-1 | 信箱消费权排他:同一时刻只有一个系统有权处理、打标判定可清除(迁移期由切流规程保证单一权威写者) | 采集谓词、身份去重清除前提全部失效 | `[待确认]`(切流由运维规程保证,上线前另立 | | PRE-1 | 信箱消费权排他:同一时刻只有一个系统有权处理、打标判定可清除 | 采集谓词、身份去重清除前提全部失效 | 由切流规程保证,上线前另立 |
| PRE-2 | 信箱 ID 分配顺序即到达顺序:见 `C-30` | 「编号即到达顺序」的顺序声称失去依据 | `[待确认 Q2]` | | PRE-2 | 信箱编号的分配顺序即到达顺序 | 「编号即到达顺序」的顺序声称失去依据 | 待确认 `Q2` |
| PRE-3 | ID 空间不复位、不复用、不回退:见 `C-3` | 处理记录按信箱编号唯一的前提被破坏,同编号可能重复登记 | `[待确认 Q2]` | | PRE-3 | 信箱编号空间不复位、不复用、不回退 | 同一编号可能重复登记 | 待确认 `Q2` |
| PRE-4 | 报文的 `DATE_RECEIVED` 时钟基准可解释偏斜有界范围内) | 跨系统时间比较`RECEIVED_AT` 与本地 `NOW`会提前或推迟判定 | `[待确认 Q7]` | | PRE-4 | 信箱时间列的时钟基准可解释偏斜有界 | 与本地时间比较会提前或推迟判定 | 待确认 `Q7` |
| PRE-5 | 单活动实例运行信箱读取不加锁 | 并行读会让「已处理」判定互相踩踏 | `[我们自证]`(部署约束,见 architecture.md | | PRE-5 | 单活动实例运行信箱读取不加锁 | 并行读会让「已处理」判定互相踩踏 | 部署约束 |
| PRE-6 | 信箱与自有 PG 之间没有跨库事务 | 回填清除都不能声称原子 | `[我们自证]`架构事实 | | PRE-6 | 信箱与自有 PG 之间没有跨库事务 | 回填清除都不能声称原子 | 架构事实 |
| PRE-7 | 报文不可变:同一业务身份的重发必为同一内容:见 `C-4` | 上游改发会被判为重复并静默跳过 | `[待确认 Q15]` | | PRE-7 | 报文不可变:同一业务身份的重发同一内容 | 上游改发正文会被判为重复并静默跳过 | 待确认 `Q15` |
| PRE-8 | `FLID` 在保留期内不复用`C-21` | 「保留最新版本」的合并规则可能压掉新航班事件,旧 tombstone 可能删掉在用航班 | `[待确认 Q16]` | | PRE-8 | `FLID` 在保留期内不复用`C-21` | 「只进不退」的合并可能压掉新航班事件,旧的删除标记可能删掉在用航班 | 待确认 `Q16` |
运行约束:测试环境的实例使用独立的数据库、Redis 与 Kafka 主题,不连接生产信箱(`OPS-3`)。
## 4. 不变量 ## 4. 不变量
### 4.1 管道 ### 4.1 管道
- **INV-1** 五个独立事实互不替代:落信 / 入队 / 处理完成 / 已回填 / 投递确认各有独立证据,前一个不蕴含后一个。 - **INV-1** 五个事实互不替代:落信、登记、处理完成、回填、投递各有独立证据,前一个不蕴含后一个。
- **INV-2** 水位与入队同事务:不允许出现「水位已推进、消息未入队」的持久化状态;水位只增不减,遇空洞即停,只有判定为永久空洞才放行,且放行只跳过空洞本身、不越过任何已存在的行。**[作废 by INV-2b]** - **INV-2b** 扫描谓词与幂等登记:每轮按编号升序读取「处理时间为空」的信箱行,每批有上限;同一编号只登记一次;重复扫描与重启恢复不重复登记、不重复处理、不丢行。
- **INV-2b** 扫描谓词与幂等登记:每轮按编号升序读取「处理标记为空」的信箱行,每批有上限;同一编号只登记一次(`INV-9`);重复扫描与重启恢复不重复登记、不重复处理、不丢行;终态但未回填的行会被重复读到,按已有记录幂等跳过。 - **INV-3** 队头唯一:任一时刻只有一个可执行的队头(编号最小的未完成消息),队头未完成时后面的消息不得越过。
- **INV-3** 队头唯一:任一时刻只有一个可执行队头(最小未完成 `MSG_ID``PENDING``FAILED` 都占位);`FAILED` 未退避到期时后续消息不得越过 - **INV-6** 处理终态不可逆:已提交的成功不因回填或投递失败回改
- **INV-4** 只领取已发现的行:主泵只领 `MSG_ID ≤ W`;水位之外的行只可能来自兼容入口,必须等水位追平后按序处理。**[作废 by INV-2b]**(水位取消,兼容入口写入的行即普通行。)
- **INV-5** 发现与处理互不阻塞:收报只看 `ID > W`,不以处理标记为谓词;终态而未回填的行不阻断后续消息的发现。**[作废 by INV-2b]**
- **INV-6** 处理终态不可逆:已提交的 `SUCCEEDED` 不因回填或投递失败回改。
- **INV-7** 处理标记单调:任何路径只把空标记写成已处理值,不回撤、不覆盖。 - **INV-7** 处理标记单调:任何路径只把空标记写成已处理值,不回撤、不覆盖。
- **INV-8** 回填只针对终态(`PENDING` / `FAILED` 永不写标记);「还欠一次回填」的事实与终态由**同一条语句**落库,不存在第二处落账。 - **INV-8** 回填只针对已有终态的消息;「还欠一次回填」与终态由同一条语句落库,不存在第二处落账。
- **INV-9** 一信一行、一身份一记录:`PROC_STATE``MSG_ID` 唯一同一业务身份至多绑定一条有效处理记录。 - **INV-9** 一信一行、一身份一记录:处理记录按信箱编号唯一同一业务身份至多绑定一条有效记录。
- **INV-10** 对外投递至少一次;端到端恰好一次不在交付范围。 - **INV-10** 对外投递至少一次;端到端恰好一次不在交付范围。
- **INV-16** 回填与 Kafka 投递失败可重试,不回滚已提交的本地业务结果;出站请求写入结果不明时记录告警、不直接重发(`C-24`)。
- **INV-17b** 状态变更与待发事件在同一事务提交;处理终态与回填意图在另一事务提交,且晚于 Redis 投影写成功。日计划的分批与静态参考数据的单事务见架构「主流程」。
- **INV-19** 整包校验失败或运营日冲突时整包不落地,既有状态与版本不变。
- **INV-20b** 处理器幂等:同一消息在失败重处理与重复发现下都只产生一次业务效果。身份唯一只防「重复记录」,不防「重新执行」;逐类幂等规则补齐前本条不可声明(`CLM-3`)。
### 4.2 航班域 ### 4.2 航班域
- **INV-11** 自有 PG 的航班当前态是唯一权威;信箱、Kafka、展示视图都不是权威。**[作废 by INV-11b]**
- **INV-11b** 自有 PG 的航班当前态是唯一权威;信箱、Redis 投影、Kafka、展示视图都不是权威。 - **INV-11b** 自有 PG 的航班当前态是唯一权威;信箱、Redis 投影、Kafka、展示视图都不是权威。
- **INV-12** `FLID` 唯一;已写入非空的 `OPERATION_DAY` 不可改 - **INV-12** `FLID` 唯一;已写入非空的运营日不可改。
- **INV-13** 每个航班每次成功状态写入单调推进 `STATE_VERSION`;重复消息不重复推进 - **INV-13** 每次成功写入版本号加一;重复消息不重复加,版本不回退
- **INV-14b** 增量报文未携带的字段不被隐式清空。日计划快照不适用本条(`INV-15b`)。
- **INV-15b** 日计划快照以 AODB 下发为准:快照里没有的航班删除——标记已删除、登记删除事件、从 Redis 投影移除;未携带的字段同步清除(`US-07` AC2/AC3)。
- **INV-18** 航班表的写者是主泵处理器与航班历史清理,两者必须互斥,不得出现清理删除与处理器更新同一个 `FLID` 的竞态。
- **INV-21** 主航班与其共享航班不出现只删一半的状态:删除共享航班时联动更新其主航班,删除主航班级联删除其共享航班(`US-06` AC2)。
- **INV-22** 主航班与共享航班的删除在同一 PG 事务内提交。被删除的航班从 Redis 投影移除;删除共享航班时,主航班的投影随更新结果刷新。移除与刷新都成功后才算这次处理完成(`INV-17b``INV-23`)。
### 4.3 投影、投递、历史与参考数据
- **INV-23** Redis 投影写成功才算处理完成:写失败的消息不置终态、不写回填标记,保持未完成、下轮重新处理;自有 PG 已提交的业务结果不回滚。
- **INV-24** Redis 航班投影只由本系统写入和移除;`GET /all/flights` 与网页客户端读同一份,返回当前全部动态航班(不含共享航班),不分页;Redis 异常时报错,不返回空列表伪装成功。
- **INV-25** 处理记录清理:只删「已有终态且已回填」并超过保留期的记录,未完成的不删,保留期可配置;未映射字段的记录不随处理记录到期清理(`US-05` AC3)。
- **INV-26** 静态参考数据按类别与编号保存,新消息覆盖旧记录;全量消息整体替换,增删改消息逐条处理;字段为空表示「当前没有值」,不是删除。
- **INV-27** 一类参考数据校验不通过就只停这一类,其他类照常;该类的已有记录不变。
- **INV-28** 航班只在历史写入确认成功后才从实时数据删除,删除前按 `D1` 必要时登记待发删除事件,历史存储未接通时一条也不删;写失败的下轮重来,已写入的不重复写入,单个航班失败不影响其他航班。与消息处理的互斥见 `INV-18`
### 4.4 已作废条款
- **INV-2** 水位与入队同事务:不允许出现「水位已推进、消息未入队」的持久化状态;水位只增不减,遇空洞即停,只有判定为永久空洞才放行,且放行只跳过空洞本身、不越过任何已存在的行。**[作废 by INV-2b]**
- **INV-4** 只领取已发现的行:主泵只领 `MSG_ID ≤ W`;水位之外的行只可能来自兼容入口,必须等水位追平后按序处理。**[作废 by INV-2b]**(水位取消,兼容入口写入的行即普通行。)
- **INV-5** 发现与处理互不阻塞:收报只看 `ID > W`,不以处理标记为谓词;终态而未回填的行不阻断后续消息的发现。**[作废 by INV-2b]**
- **INV-11** 自有 PG 的航班当前态是唯一权威;信箱、Kafka、展示视图都不是权威。**[作废 by INV-11b]**
- **INV-14** 报文未携带的字段不被隐式清空;集合按完整合并结果写入,保留输入顺序与源序号。**[作废 by INV-14b]** - **INV-14** 报文未携带的字段不被隐式清空;集合按完整合并结果写入,保留输入顺序与源序号。**[作废 by INV-14b]**
- **INV-14b** 增量报文(FLOP/ADFT)未携带的字段不被隐式清空;集合按完整合并结果写入,保留输入顺序与源序号。日计划快照不适用本条(`INV-15b`)。
- **INV-15** 缺席于某个日计划不构成删除理由;删除只由 FDEL 或受控历史清理触发。**[作废 by INV-15b]** - **INV-15** 缺席于某个日计划不构成删除理由;删除只由 FDEL 或受控历史清理触发。**[作废 by INV-15b]**
- **INV-15b** 日计划快照以 AODB 下发为准:快照里没有的航班删除——标记已删除、登记删除事件、从 Redis 投影移除;快照里未携带的字段视为 AODB 已删除该值,本地同步清除(`US-07` AC2/AC3)。
- **INV-16** 外部副作用(回填、Kafka 投递、出站信箱)失败可重试,但不回滚已提交的本地业务结果。
- **INV-17** 状态变更、待发事件、处理终态与回填意图在同一 PG 事务内原子提交。**[作废 by INV-17b]** - **INV-17** 状态变更、待发事件、处理终态与回填意图在同一 PG 事务内原子提交。**[作废 by INV-17b]**
- **INV-17b** 状态变更与待发事件在同一 PG 事务内提交;处理终态与回填意图在同一 PG 事务内提交,且该事务在 Redis 投影写成功之后(`INV-23`)。日计划例外:分批写入,每批一个事务(状态变更 + 待发事件),终态与回填意图在整包完成后同一事务提交;失败不标记已处理,下轮整包重新处理(`US-07` AC4)。静态参考数据不产生待发事件与投影写,落库、终态与回填意图在同一事务提交。
- **INV-18** 航班表的写者集合是「主泵处理器」与「历史清理」;两者必须互斥(同一 `PIPELINE_LOCK`,或清理在同一事务内复查判据后再删除),不得出现清理删除与处理器更新同一 `FLID` 的竞态。
- **INV-19** 整包校验失败或运营日冲突时整包不落地,既有状态与版本保持不变。
- **INV-20** 处理器幂等:同一消息重复执行只产生一次业务效果。身份唯一只防「重复记录」,不防「重新执行」;SIS 25 类与经 `Q8` 定案启用的 legacy 子类型完成逐类幂等矩阵前,本条**不可声明**(`G-FLOP-IDEMPOTENT`)。**[作废 by INV-20b]** - **INV-20** 处理器幂等:同一消息重复执行只产生一次业务效果。身份唯一只防「重复记录」,不防「重新执行」;SIS 25 类与经 `Q8` 定案启用的 legacy 子类型完成逐类幂等矩阵前,本条**不可声明**(`G-FLOP-IDEMPOTENT`)。**[作废 by INV-20b]**
- **INV-20b** 处理器幂等:同一消息重复执行(失败重处理、重复发现)只产生一次业务效果。身份唯一只防「重复记录」,不防「重新执行」;逐类幂等矩阵(SIS 25 类与现场延续的 7 类)补全前,本条**不可声明**(`G-FLOP-IDEMPOTENT`)。
- **INV-21** `MAFL` 是派生投影:内容恒等于「`STATE = ACTIVE``MAID = 主航班 FLID`」的子航班集合(元素 `FLID` + `FLNO`,按 `FLID` 升序),不落库、不从入站解析;自引用与悬挂引用不入投影。
- **INV-22** 子航班集合变化必须使涉及的主航班在同一事务内推进 `STATE_VERSION` 并登记主航班事件;投影只进不退,版本不推进即被下游丢弃。
- **INV-23** Redis 投影写成功才算处理完成:写失败的消息不置终态、不写回填标记,保持未完成,下轮重新处理;自有 PG 已提交的业务结果不因此回滚(`US-05` AC4、`US-06` AC1)。
- **INV-24** Redis 航班投影只由本系统写入与移除;`GET /all/flights` 与网页客户端同源读 Redis;Redis 异常时报错,不返回空列表伪装成功(`US-12` AC1/AC2)。
- **INV-25** 处理记录清理:只删「终态且已回填」且超过保留期的记录;未完成的不删。保留期可配置(`US-11`)。
- **INV-26** 静态参考数据按(类别, 编号)保存,新消息覆盖旧记录;全量消息整体替换,增删改消息逐条处理;消息里字段为空表示「当前没有值」,不是删除(`US-13` AC1/AC3/AC4)。
- **INV-27** 一类参考数据校验不通过就不更新这一类,其他类照常处理;该类已有数据不动(`US-13` AC2)。
- **INV-28** 航班先写历史存储、确认成功后才从实时数据删除;写入失败下次重来,已写入的不重复写入;单个航班失败不影响其他航班;正在被消息处理的航班跳过、下轮再处理(`US-14` AC3/AC4;写者互斥见 `INV-18`)。
## 5. 声明边界 ## 5. 声明边界
| 编号 | 主张 | 依赖 | 当前可否声明 | 挂起原因 | | 编号 | 承诺 | 依赖 | 现在能否作出 | 限制或原因 |
|---|---|---|---|---| |---|---|---|---|---|
| CLM-3 | 失败重处理与重复发现不产生重复业务副作用 | INV-20b`G-FLOP-IDEMPOTENT` | **不可** | 逐类幂等矩阵未补全 | | CLM-3 | 失败重处理与重复发现不产生重复业务副作用 | `INV-20b` | 不能 | 逐类幂等规则未补齐(`G-FLOP-IDEMPOTENT` |
| CLM-4 | 回填不会被短暂故障放弃:最终打标,或进入可对账的放弃清单 | INV-8`C-5``C-8` | **可声明(有条件)** | 条件:`R` 之前不放弃;`MISSING_ROW` 立即放弃并告警;放弃行须经人工对账才可用于清除判定(`C-8` | | CLM-4 | 信箱行最终都被写上处理标记 | `INV-7``C-15``C-16` | 不能 | 一直写不上的行留案并告警(`US-10` AC2 |
| CLM-6 | 单实例内严格 FIFO | PRE-5INV-3 | **可**(限于单活动实例) | — | | CLM-6 | 单实例内严格 FIFO | `PRE-5``INV-3` | 能 | 仅限单活动实例 |
| CLM-7 | 事件投递在同一 `FLID` 内保序 | INV-10、投递设计 | **可**(跨 `FLID` 不承诺) | 实现当前按目标级全序投递,收敛到按 `FLID` 属投递改造 | | CLM-7 | 主题 `msg`同一 `FLID` 内保序 | `INV-10``C-29``D2` | 能 | 须满足 `D2` 的三项生产端约束;跨 `FLID` 不承诺;不覆盖 `schd` |
| CLM-8 | 出站交付承诺只到落信 | `C-24``Q10` | **可**(仅落信语义) | 消费方已定(CIIMS adapterACK 列语义未确认 | | CLM-8 | 出站交付承诺只到落信 | `C-24` | 能 | 只覆盖落信ACK 与错误列语义未确认`Q10` |
| CLM-9 | 处理标记延迟由调度周期决定 | — | **不可** | 扫描周期不等于完成时限;批次积压单行超时与清理作业都会延长实际延迟 | | CLM-9 | 处理标记的写入时刻不等于处理完成的时限 | — | 不能 | 扫描周期不构成完成时限;批次积压单行重试都会延长实际延迟 |
| CLM-10 | 容量量级假设(单实例、入站日消息量千级到万级、单报文 ≤ 10⁴ 字节) | — | **不可** | 实测,无生产负载数据;解除条件:取得现役信箱日量、峰值与单报文上限后重估 | | CLM-10 | 容量与吞吐量级 | — | 不能 | 实测数据,取得信箱日量、峰值与单报文上限后重估 |
## 6. 验证映射 ## 6. 待确认事项台账
每条不变量至少一条证据。测试名以仓库现状为准;新增测试按本表补位。本表只记录**验收口径与证据位置**,覆盖进展只在 Plane(ACM2)。 ### 6.1 待对方确认
| 不变量 / 声明边界 | 场景 | 证据 / 测试 | | 编号 | 事项 | 当前假定 | 影响 |
|---|---|---|---|
| Q2 | 信箱编号的分配顺序、单调与不复用 | 见 `C-3``PRE-2``PRE-3` | 「编号即到达顺序」目前无法确认 |
| Q3 | 兼容入口的请求体上限与失败响应样例 | 响应语义与解析限制已定(`C-28` | 兼容入口无法验收 |
| Q4 | Kafka 载荷与去重标识 | 两个主题名、`msg` 单条变更、`schd` 定时批量已定(`US-08`;接口契约「Kafka」);`msg``FLID` 保序见 `C-29``schd` 每条 record 装什么、批次边界未定 | key、分区规则、`schd` 粒度及去重标识仍须确定 |
| Q5 | 请求与应答的对应字段、超时判定 | 见 `C-23` | 请求跟踪无法闭环 |
| Q7 | 处理标记的允许值、原文保留期、处理时间列、信箱时钟基准与时区 | 只填空值,不回撤不覆盖(`C-15`);回填重试期间原文须在(`C-7`) | 允许值、保留期取值与时钟基准未定 |
| Q8 | 现场会发但 SIS 未定义的子类型(靠桥、延误等)的报文形态与逐类终态 | 按现有处理逻辑延续(`US-05` AC1) | 逐类终态与幂等规则未定(`G-FLOP-SEMANTICS` |
| Q9 | 清除方案与保留期 | 清除由库方执行(`C-8`);旧系统按接收超过 1 天归档并删除入站行,是否沿用待确认。见 `C-6``C-9` | 清除边界与保留期未定 |
| Q10 | 出站 ACK 与错误列语义、出站行清理与重复落信识别 | 交付承诺止于落信(`C-24` | 出站行清理与去重责任未定 |
| Q11 | 上游 `SEQN` 的重置周期与身份是否加日期边界 | 暂不加日期边界 | 身份算法不能定稿 |
| Q15 | 上游是否会以同一业务身份改发正文 | 假定不可变(`PRE-7` | 身份去重语义未定 |
| Q16 | `FLID` 的重用语义 | 假定保留期内不复用(`C-21` | 事件合并与删除标记未定 |
| Q20 | Redis 投影的 key/value 结构、序列化方式与网页客户端读取约定 | 旧系统线索为 hash `flightInfo`、field 取 `FLID`(见接口契约「Redis:航班查询投影」) | 消费方读取契约无法定稿 |
答复就地更新结论,并按 [README.md](README.md)「维护清单」落到对应条款。
### 6.2 已确认
| 编号 | 事项 | 结论 |
|---|---|---| |---|---|---|
| INV-1 | 五事实互不替代:入队不引用标记、回填不引用投递、投递不引用回填 | 需接口级断言 | | Q13 | 日计划未携带字段的删除语义 | 见 `C-26` |
| INV-2b | 谓词扫描与批次上限 | 只读「处理标记为空」的行、每批有上限;`InboxPollerTest`(随 `G-SCAN-PREDICATE` 改造重写) | | Q14 | 主/共享删除顺序与 EROR 回报义务 | 见 `C-25` |
| INV-2b | 重复扫描、重启恢复 | 不重复登记、不重复处理、不丢记录 |
| INV-3 | 较小 ID 迟提交 | 迟发现的较小编号仍按编号序登记处理,不丢、不重复 |
| INV-3 | 队头失败、退避及作业竞争 | 消息不越队;到期后恢复;作业不使消息无限饥饿 |
| INV-6 | 投递失败后终态不变 | 需用例 |
| INV-7 | 回填四种结果 | 写入成功 / 早已标记(不覆盖、记成功)/ 信箱行不存在(立即放弃并告警,不得视为已标记)/ 暂时故障持续到 `R` 仍未打标(停止自动重试,可人工恢复) |
| INV-7 | `RECEIVED_AT` 为 NULL | 超期分支仍成立且不导致标记提前写入——判据是本地 `ENQUEUED_AT`,与库方时钟及 NULL 无关 |
| INV-8 | PG 提交失败、回填失败 | 事件、终态与回填意图一起回滚;已提交结果只补写标记,不重放业务;中间态永不补写 |
| INV-8 | 非业务型终态 | 不触碰航班表 / `MSG_EVENT`,只写 `PROC_STATE`,且终态与回填意图同语句生效 |
| INV-9 | 同身份多条记录、失败后重试、清理后重复 | 只产生一次有效业务处理,不把自身重试判为重复 |
| INV-10 | 投递确认丢失、批次失败、次数耗尽 | 允许可识别的重发、保持目标顺序、整批退避并保留死信 |
| INV-11b | 权威唯一 | 需断言信箱、Redis 投影、展示视图不成为写入或对账来源 |
| INV-12 / INV-13 | PG 事务失败、快照重复或迟到 | 整体回滚重试、不重复推进版本、不回退状态、不误删增量航班 |
| INV-12 | 运营日冲突 | 整包 `DEAD(PROTOCOL)`,既有状态与版本不变 |
| INV-14b | 增量未携带字段保留、显式清空才清除;集合按完整合并结果写入 | `FlightStateEngineTest`「flop merge retains absent scalars and collections」;`FdelAndAdftProcessorTest`「ADFT merge does not clear absent scalars」 |
| INV-14b | FLOP 逐类空标签语义(清除、撤销、集合清空)与「未携带不清空」的区分 | 逐类用例见 [implementation.md](implementation.md)「动态运行事件」;`G-FLOP-UNMAPPED``G-FLOP-SEMANTICS` |
| INV-15b | 快照缺席航班删除、未携带字段清除;增量不适用快照语义 | 新增用例(`G-SCHD-SNAPSHOT` |
| INV-16 | 外部副作用失败后本地结果不变 | 需用例 |
| INV-17b | 状态与事件同事务;终态与回填意图同事务且晚于 Redis 写成功;日计划分批 | 需真实 PG 用例 |
| INV-18 | 清理与处理并发 | `HistorySweepJobTest`(归档后被主泵更新的航班不删除、不发 tombstone)+ `HistorySweepPurgePgTest`(删除阶段失败时 tombstone 与删除整体回滚) |
| INV-19 | 整包协议拒绝(声明数不符、运营日冲突) | `DEAD(PROTOCOL)`,整包不落地、整体回滚、既有状态不变 |
| INV-21 | `MAFL` 投影与 `ACTIVE` 子航班集合一致(子航班删除后退出、自引用与悬挂引用不入、顺序确定) | `G-MAFL`:投影未实现 |
| INV-22 | 子航班新增、删除、`MAID` 迁移时主航班版本与事件 | `G-MAFL`:主/共享级联未实现 |
| INV-20b / CLM-3 | 同一消息失败重处理、重复发现 | 阻塞于逐类幂等矩阵(`G-FLOP-IDEMPOTENT` |
| INV-23 | Redis 写失败 | 消息保持未完成、不提前写标记;重处理收敛 |
| INV-24 | 同源读取与异常 | Redis 异常时报错、不返回空列表 |
| INV-25 | 清理谓词 | 只删「终态且已回填」超保留期的记录 |
| INV-26 / INV-27 | 参考数据覆盖 / 全量替换 / 空标签 / 逐类门控 | 需用例(`G-REF-DATA` |
| INV-28 | 历史先行 | 历史未确认成功删除 0 条;已写入不重复写入 |
| — | 请求超时、无匹配 RESP、时间单位不一致 | 不误用迟到应答、不提前完成请求 |
| — | stub 误配置、重复实例、停机中断 | 生产拒绝不安全启动,工作线程能正确退出 |
上表首列是**引用**`INV-x` 的定义见本文件「不变量」);同一行可覆盖多个 `INV`,例如 `INV-20b / CLM-3` ### 6.3 本系统与需求方待决
声明边界的证据指针:`CLM-4` 断言放弃行不写标记、不被当作已打标;`CLM-9` 由作业心跳与回填年龄指标提供观测(指标名见 [reference.md](reference.md)「指标与健康」),实际延迟仍需现场数据。 以下事项不出自对接方,由本系统与需求方决定:
| 编号 | 事项 | 当前假定 | 影响 |
|---|---|---|---|
| Q1 | 生产库选型 | 自有 PG 是唯一权威;生产环境用 PostgreSQL 还是 Oracle 11g 不能从三份依据确定,Oracle 适配验证通过前不作支持承诺 | 生产部署验收 |
| Q17 | `POST /schd/sync` 的成功响应表示已登记还是已落信 | 架构只约定交付承诺止于落信(`C-24`),两种含义都相容 | 响应契约无法定稿(`C-33` |
| Q18 | 人工发起 `RQRD` 的方式 | `US-09` 要求人工发起,HTTP 接口清单没有对应入口 | 参考数据请求无法人工触发 |
| Q19 | `msg` 的版本与去重标识是否直接采用航班当前态的版本号 | 可用依据是 `FLIGHT_SCHD` 的版本规则(`INV-13` | 消费方去重规则未定(与 `Q4` 衔接) |
## 7. 当前已知偏差 ## 7. 当前已知偏差
本表是**偏差标记的唯一出处**其他文档只在相应位置`G-NAME`,不解释、不记进度。偏差只写事实与受影响稳定 ID,不写负责人、日期、进度或 Plane 状态;闭合时在同一变更中删除本行与全仓 `G-NAME` 引用,关闭证据留在 Plane 本表是偏差标记的唯一出处其他文档只写 `G-NAME`。闭合时删除本行与全仓引用
| 偏差 | 含义 | 影响 | | 偏差 | 缺什么,会怎样 | 影响 |
|---|---|---| |---|---|---|
| `G-RESP-GUARD` | `RESP` 应答守卫未实现,当前与 `DNLD` 无差别进入快照写入 | 请求匹配闭环`C-23` | | `G-RESP-GUARD` | `SCHD-RESP` 没有过期判断,晚到的应答也会更新本地数据 | `US-07``C-23` |
| `G-REQ-TRACK` | `REQ_TRACK` 无运行时协调器:出站适配、请求编码、超时与应答匹配实现 | `US-09``C-24` | | `G-REQ-TRACK` | 出站请求没有跟踪:登记、编码、超时与应答匹配都没有实现 | `US-09``C-24` |
| `G-FLOP-IDEMPOTENT` | SIS 25 类与现场延续的 7 类(靠桥、延误、计划机位等)尚无完整逐类幂等矩阵 | `INV-20b``CLM-3` | | `G-REQ-OPEN-UNIQUE` | 同一报文类型同时最多一条已落信、未结案请求的限制没有实现;待发送登记不算占用该名额 | `US-09` |
| `G-MAFL` | 主航班 `MAFL` 派生投影及主/共享原子级联未实现(规则见 `INV-21`/`INV-22`);`MAFL` 不是 SIS/XML 入站字段 | 航班完整态;删除与重建 | | `G-REQ-TRACK-RETENTION` | `REQ_TRACK` 已结案记录的保留期取值未定,到期清理作业没有可依据的窗口 | `US-09` |
| `G-SRVT-VIPF` | SIS/XML 的 `SRVT``VIPF` 无界集合尚未映射到持久化明细;wire/domain 只保留出现事实与原始内容,不参与合并与投递(清空语义见 `Q13` | 航班完整态;无损字段保存 | | `G-FLIGHT-HIST-RETENTION` | 历史存储的保留期与容量上限未定 | `INV-28`;实时数据删除后历史是唯一副本 |
| `G-COMPAT-HTTP` | compat 入口尚未按 `US-02` 口径实现:请求体上限取值与失败响应样例未对拍(媒体类型集与成功/失败响应语义已定案) | `C-28``US-02` | | `G-FLOP-IDEMPOTENT` | 逐类幂等规则未补齐 | `INV-20b``CLM-3` |
| `G-REQ-OPEN-UNIQUE` | `REQ_TRACK` 尚无约束开放态 `(REQ_TYPE, OPERATION_DAY, SENDER)` 唯一性的部分索引 | `US-09``G-REQ-TRACK` | | `G-FLOP-SEMANTICS` | `STYP` 没有白名单,`ROUT` 未限制 4 条,运行状态落点与已删除航班的处理与 `US-05` 不符 | `US-05``INV-14b` |
| `G-REQ-TRACK-RETENTION` | `REQ_TRACK` 关闭态行(`DONE`/`EXPIRED`)的保留期与清除作业未定义 | 自有 PG 无界增长;`US-09` | | `G-FLOP-UNMAPPED` | [XSD](legacy/unisysaodbsis.xsd)「FLOP 元素」里有些字段没有解码或映射错了,会被静默丢掉 | `US-05` |
| `G-FLIGHT-HIST-RETENTION` | 航班历史存储(外部)的保留期与容量上限未定义 | `INV-28`实时数据物理清除后历史存储是唯一副本 | | `G-MAFL` | 航班的共享航班列表与主/共享原子级联未实现 | `INV-21``INV-22` |
| `G-FLOP-UNMAPPED` | [XSD](legacy/unisysaodbsis.xsd)「FLOP 元素」的 `FFID``UNCL``CKOP``GTOP``CKCL``BDOP``LCTM``BDCL``FRET``FDIV``FLAB``RUNW``PXNO``FLBG``CKPB``BDPB` 未解码;`CHDT.CCLS/CTYP` 被错误写成 `CHCLS/CHTYP`,均会静默丢失。其中 `FRET``FDIV``BDPB` 在 legacy 有对应处理 | `US-05`;无损字段保存 | | `G-SRVT-VIPF` | `SRVT``VIPF` 两个集合没有落到持久化明细 | `US-05` |
| `G-FLOP-SEMANTICS` | FLOP 未按 SIS 25 类建立 `STYP` 白名单;`ROUT` 未按方向截取至 4 条且仍保存 `SCAT`/`SCDT`;BOTM/LACL 未同步设置或重置运行状态;除 FDEL 外,已删除航班仍会被合并、增版并发事件 | `US-05``INV-14b``INV-20b` | | `G-SCAN-PREDICATE` | 收报仍按水位扫描,不是按「处理时间为空」读取 | `US-01``INV-2b` |
| `G-REF-DATA` | SIS 静态参考数据处理(`SIS:3.1``SIS:3.14`)未实现;当前按「合法但不支持」跳过并回填(`US-03` AC2),独立参考数据表与 admin-api 直读未落地 | `US-13``US-03` | | `G-REDIS-PROJECTION` | Redis 投影没有写入与移除路径 | `US-05``US-06``US-07``US-12` |
| `G-SCAN-PREDICATE` | 收报仍按持久水位 ID 区间扫描(含空洞老化与切流播种),未达「处理标记为空」谓词口径 | `US-01``INV-2b` | | `G-SCHD-SNAPSHOT` | 日计划快照不删除缺席航班、不清除未携带字段,也没有分批 | `INV-15b``INV-17b` |
| `G-REDIS-PROJECTION` | 无任何 Redis 投影写/移除路径:航班动态写入、删除移除、快照刷新、查询读取均未实现 | `US-05``US-06``US-07``US-12` | | `G-PROC-CLEANUP` | 处理记录的到期清理作业未实现 | `US-11``INV-25` |
| `G-SCHD-SNAPSHOT` | 日计划快照未达 `US-07` 口径:缺席航班不删除、未携带字段不清除、整包单事务(需求要求分批、每批一个事务) | `INV-15b``INV-17b` | | `G-REF-DATA` | 静态参考数据没有处理,当前按「合法但不支持」跳过并回填;参考数据表与 admin-api 直读未落地 | `US-13``US-03` |
| `G-PROC-CLEANUP` | 处理记录到期清理作业未实现(归档链随 `US-11` 口径废弃) | `US-11``INV-25` |
## 8. 待确认事项台账 ## 8. 验证映射
| 编号 | 事项 | 当前假定 | 阻塞 | 状态 | | 不变量 | 需求验收 | 要观察的结果 |
|---|---|---|---|---| |---|---|---|
| Q1 | 权威存储(内部方向)与生产库选型 | 自有 PG 单库权威 + 无损明细;生产环境用 PostgreSQL 还是 Oracle 11g 未定,Oracle 适配验证通过前不构成支持承诺 | 生产部署验收 | 已定案(内部),生产库待定 | | INV-2b | `US-01` AC1/AC2/AC4 | 扫描重来与重启后登记数不变,行不丢 |
| Q2 | 信箱 ID 分配顺序即到达顺序;ID 空间不复位、不复用 | — | 到达顺序声称(`US-01` AC3 | 未确认 | | INV-3 | `US-03` AC1 | 队头未完成时,后面的消息不被处理 |
| Q3 | HTTP 契约:请求体上限取值、失败响应样例 | 上限暂定 10MB(`C-28` | 兼容入口验收 | 未确认 | | INV-6、INV-16 | `US-03` AC3 | 失败回滚后消息仍在未完成;已提交结果不被副作用回滚 |
| Q4 | Kafka 载荷与去重标识 | 主题/粒度/保序已按 `US-08` 定案:`msg` 发单条变更、`schd` 定时批量发最新状态、key=`FLID` | 投递契约 | 未确认 | | INV-7、INV-8 | `US-10` AC1/AC2 | 标记只写一次;重启后继续,写不上的有记录与告警 |
| Q5 | 请求匹配:回显字段可靠性与降级匹配 | `RQFD` 60 秒 / `RQRD` 30 秒超时 | 请求跟踪闭环 | 未确认 | | INV-9 | `US-01` AC2 | 同一编号重复出现时,处理记录数不增加 |
| Q7 | 处理标记值集与写权限、原文保留期、处理时间语义 | 写入 `PROCESSED` | 回填值集、保留期下界 | 未确认 | | INV-10 | `US-08` AC2 | 失败重试后仍能投出;`msg` 上同一 `FLID` 的顺序不颠倒(`C-29``CLM-7` |
| Q8 | 逐类覆盖:SIS 未定义但现场会发的 7 类(靠桥、延误、计划机位等)的报文形态、除 FDEL 外目标航班不存在/已删除时的逐类终态、BOTM/LACL 运行状态的落点 | 航班动态的字段与空标签规则以 `SIS:3.19``SIS:3.43` 为准(`US-05` AC1 定案);静态参考数据以 `SIS:3.1``SIS:3.14` 为准(`US-13`);legacy 7 类按现有处理逻辑延续(`US-05` AC1 定案) | 逐类幂等矩阵与 golden(`G-FLOP-SEMANTICS``G-FLOP-IDEMPOTENT``G-REF-DATA` | 未确认(不阻塞 `US-13` 验收) | | INV-11b | 架构「系统定位与范围」 | 航班当前态的权威写入只在自有 PG |
| Q9 | 清除执行方与 DDL 授权、方案 A/B 选型、分区能力 | 首选方案 A | `R_keep` 与清除边界 | 未确认 | | INV-12、INV-13 | 架构「必须保持的约束」 | 运营日写入后不变;重复消息不推进版本 |
| Q10 | 出站 ACK 列语义、出站行清理与去重契约 | — | 出站信箱 | 未确认 | | INV-14b | `US-04` AC2、`US-05` AC2 | 未携带的字段保持原值 |
| Q11 | 上游 `SEQN` 重置周期与业务身份的日期边界 | 不含日期边界 | 身份算法 | 未确认 | | INV-15b | `US-07` AC2/AC3 | 缺席的航班在 PG 标为已删除并从 Redis 投影移除;未携带的字段被清空 |
| Q13 | 日计划缺失可选字段的删除语义 | 结论(2026-09-14):未携带字段视为 AODB 已删除该值,本地同步清除(`C-26` | — | 已定案 | | INV-17b | `US-05` AC4、`US-06` AC1 | 投影写失败时,没有终态与回填意图落库 |
| Q14 | 主/共享删除顺序与 EROR 回报义务 | 结论(2026-09-14):幂等原子级联、不回发 EROR(`C-25` | — | 已定案 | | INV-18 | `US-14` AC4 | 历史清理跳过正在被消息处理的航班 |
| Q15 | 上游是否会以同一业务身份改发正文(决定是否需要区分「重复」与「改发」) | 假定期望不可变(`C-4` | 身份去重语义 | 未确认 | | INV-19 | `US-07` AC1 | 校验失败后本地数据与版本不变 |
| Q16 | `FLID` 重用语义(决定版本是否按 incarnation 作用域) | 假定不复用(`C-21` | 事件合并与 tombstone | 未确认 | | INV-20b、CLM-3 | `US-01` AC2、`US-03` AC3 | 重复执行不增加业务效果 |
| INV-21、INV-22 | `US-06` AC2 | 主/共享删除联动后没有半删状态 |
Q 的答复只在本表就地更新(补「结论」与日期),并触发 [README.md](README.md)「维护清单」的落地 4 步;不另开文件。 | INV-23 | `US-05` AC4、`US-06` AC1 | 投影写失败的消息下轮仍被处理 |
| INV-24 | `US-12` AC1/AC2 | 返回全部非共享航班,且与 Redis 一致;Redis 故障时返回错误 |
| INV-25 | `US-11` AC1/AC2 | 未完成的记录不被删除 |
| INV-26、INV-27 | `US-13` AC1~AC4 | 失败类别的已有记录不变 |
| INV-28 | `US-14` AC3/AC4 | 历史写入失败的航班仍在实时数据中 |
| CLM-4(留案告警) | `US-10` AC2 | 一直写不上的行有记录与告警 |
| CLM-9(不承诺完成时限) | `OPS-2` | 积压与延迟有指标与告警 |
| CLM-10(容量假设) | `OPS-2` | 上线前用现场量级重估 |
## 9. 契约数值 ## 9. 契约数值
| 量 | 定义处 | 约束 | | 量 | 定义处 |
|---|---|---| |---|---|
| `R_keep` | `C-7` | `R_keep ≥ max(审计期限, 回填重试上限)`;原文保留期下界(重放不在交付范围) | | 原文保留期下界 | `C-7` |
| `R` | [reference.md](reference.md) `PARAM:msgx.pipeline.overdue-backfill` | `R ≤ R_keep`;只决定强补写与放弃期限 | | 处理记录保留期 | `INV-25` |
| 去重记忆期 | `INV-9` | = 处理记录保留期(`US-11`);期内同身份去重成立,期满后同身份消息按新消息处理 |
| 回填放弃清单可见期 | `C-16` | ≥ `R_keep`;否则库方清除缺 `C-8` 依据 |
@@ -5,7 +5,7 @@ import io.micronaut.context.annotation.Value
import jakarta.inject.Singleton import jakarta.inject.Singleton
/** /**
* D3 启动自检:Kafka 生产者的 acks / enable-idempotence / max-in-flight 三项 * D2 启动自检:Kafka 生产者的 acks / enable-idempotence / max-in-flight 三项
* 必须联合满足幂等生产前提。不满足时拒绝启动——静默放行会让幂等保证在运行时失效。 * 必须联合满足幂等生产前提。不满足时拒绝启动——静默放行会让幂等保证在运行时失效。
* *
* 独立于 `PipelineLifecycle`(受 autostart 开关控制),无条件执行。 * 独立于 `PipelineLifecycle`(受 autostart 开关控制),无条件执行。
@@ -23,13 +23,13 @@ class KafkaD3Check(
fun validate() { fun validate() {
require(acks == "all" || acks == "-1") { require(acks == "all" || acks == "-1") {
"D3 violation: kafka.producers.default.acks must be 'all', got '$acks'" "D2 violation: kafka.producers.default.acks must be 'all', got '$acks'"
} }
require(idempotence) { require(idempotence) {
"D3 violation: kafka.producers.default.enable-idempotence must be true, got $idempotence" "D2 violation: kafka.producers.default.enable-idempotence must be true, got $idempotence"
} }
require(maxInFlight == 1) { require(maxInFlight == 1) {
"D3 violation: kafka.producers.default.max-in-flight-requests-per-connection must be 1, got $maxInFlight" "D2 violation: kafka.producers.default.max-in-flight-requests-per-connection must be 1, got $maxInFlight"
} }
} }
@@ -9,7 +9,7 @@ import org.apache.kafka.clients.producer.Producer
import org.apache.kafka.clients.producer.ProducerRecord import org.apache.kafka.clients.producer.ProducerRecord
/** /**
* Kafka 真实投递端口:通过 [ProducerRegistry] 拿 default 生产者,D3 参数 * Kafka 真实投递端口:通过 [ProducerRegistry] 拿 default 生产者,D2 参数
* acks=all / enable-idempotence=true / max-in-flight=1)由 `KafkaD3Check` 启动自检。 * acks=all / enable-idempotence=true / max-in-flight=1)由 `KafkaD3Check` 启动自检。
* *
* 发送同步等 broker 确认(`Future.get()`),语义是至少一次——满足 INV-10 / C-29。 * 发送同步等 broker 确认(`Future.get()`),语义是至少一次——满足 INV-10 / C-29。
+1 -1
View File
@@ -96,7 +96,7 @@ kafka:
servers: ${MSGX_KAFKA_SERVERS} servers: ${MSGX_KAFKA_SERVERS}
producers: producers:
default: # U03/R09Micronaut Kafka 按具名 producer 解析,须有 default 层 default: # U03/R09Micronaut Kafka 按具名 producer 解析,须有 default 层
# D3acks=all + 幂等 + max-in-flight=1 联合保证幂等生产; # D2acks=all + 幂等 + max-in-flight=1 联合保证幂等生产;
# 若对接旧版 Broker(如现网 0.10.x/1.x,无 INIT_PRODUCER_ID 协议), # 若对接旧版 Broker(如现网 0.10.x/1.x,无 INIT_PRODUCER_ID 协议),
# 三项须同时降级为 MSGX_KAFKA_ACKS=1 / MSGX_KAFKA_IDEMPOTENCE=false / MSGX_KAFKA_MAX_IN_FLIGHT=1。 # 三项须同时降级为 MSGX_KAFKA_ACKS=1 / MSGX_KAFKA_IDEMPOTENCE=false / MSGX_KAFKA_MAX_IN_FLIGHT=1。
acks: ${MSGX_KAFKA_ACKS:all} acks: ${MSGX_KAFKA_ACKS:all}
@@ -7,12 +7,12 @@ import org.junit.jupiter.api.Test
class KafkaD3CheckTest { class KafkaD3CheckTest {
@Test @Test
fun `default config satisfies D3`() { fun `default config satisfies D2`() {
assertDoesNotThrow { KafkaD3Check(acks = "all", idempotence = "true", maxInFlight = "1") } assertDoesNotThrow { KafkaD3Check(acks = "all", idempotence = "true", maxInFlight = "1") }
} }
@Test @Test
fun `acks=-1 also satisfies D3`() { fun `acks=-1 also satisfies D2`() {
assertDoesNotThrow { KafkaD3Check(acks = "-1", idempotence = "true", maxInFlight = "1") } assertDoesNotThrow { KafkaD3Check(acks = "-1", idempotence = "true", maxInFlight = "1") }
} }