新增 docs/spec-boundary-closure.md:自包含 spec,内联展开所有 PRE/INV/CLM/D/C/US/PARAM/Q/错误分类/表结构/时间常数序引用, 覆盖 G-KAFKA-D3 / G-IGNORE / G-EVENT-RETENTION / G-BACKFILL-BACKOFF / G-PROC-HST 五个缺口的实现决策、验收与测试决策。 同步修正 design.md(归档段落改条件 DELETE、ARCHIVED 影子行、 SENT_AT 描述)与 user-stories.md US-11(AC1 改 UPDATED_AT、 AC2 删投递判据、AC3 补条件回滚语义)。
26 KiB
Spec: 领域边界闭合(当前迭代)
Problem Statement
msgexchange-v2 的领域模型在文档中定义了完整的边界,但部分边界未在代码中落地。经过审查,REQ_TRACK 全套机制、MAFL 级联投影、归档保留期三连、REPLAY-CHANNEL 属于当前迭代的过度工程,推至 phase B。剩余必要缺口若不闭合,系统面临:
- Kafka 有序性违反(
G-KAFKA-D3) - 运维无法区分已知忽略与真实异常(
G-IGNORE) - outbox 无限增长(
G-EVENT-RETENTION) - 回填退避不可调(
G-BACKFILL-BACKOFF) PROC_STATE无生命周期终点(G-PROC-HST)
Solution
闭合 5 个必要的领域边界缺口,同步推进 3 个阻断性 Q 项确认,将过度工程化部分正式标记为 phase B deferred。
当前迭代交付:
G-KAFKA-D3:把PARAM:kafka.producers.default.max-in-flight-requests-per-connection收敛到D3要求值,满足 architecture D3 有序性约束G-IGNORE:codec 层识别 LDM/REGN/RSTA/EROR,分派为SKIPPED终态,走非业务型终态路径G-EVENT-RETENTION:jobs 模块新增 MSG_EVENT 已发送行清理作业,按PARAM:msgx.pipeline.event-retention定期清除G-BACKFILL-BACKOFF:将 BackfillService 硬编码退避提取为PARAM:msgx.pipeline.backfill-backoff-ms/-cap-msG-PROC-HST:建PROC_STATE_HST归档表,jobs 模块新增终态行归档作业
同步推进确认:
- Q2(ID 单调递增):G1 移除后水位线正确性的唯一保障
- Q7(processing-mark 语义):回填契约基础
- Q9(purge 权限与方案):系统对共享 MySQL 的清除权利
收窄:
G-FLOP-IDEMPOTENT:从「29 类全矩阵」收窄为「按实际到达类型增量补齐」
约束锚点(内联展开)
以下约束全部引自 docs/ 各锚点文件的稳定 ID,在此内联展开以便 spec 自包含。
前提(PRE-x,引自 invariants.md §1)
| 编号 | 前提 | 与本 spec 的关系 |
|---|---|---|
| PRE-2 | ID 单调 + 可见时延上界(见 C-1/C-2) |
G1 移除后水位线正确性依赖此前提 |
| PRE-3 | ID 空间不复位、不复用、不回退(见 C-3) |
同上 |
| PRE-5 | 单活动实例运行(信箱读取不加锁、水位是单行覆盖写) | 作业与主泵共存的部署前提 |
| PRE-6 | 信箱与自有 PG 之间没有跨库事务 | 回填、归档都不能声称原子 |
| PRE-7 | 报文不可变(见 C-4) |
身份绑定的前提;忽略规则在解码后判定 |
不变量(INV-x,引自 invariants.md §2)
管道不变量:
- INV-1 五个独立事实互不替代:落信 / 入队 / 处理完成 / 已回填 / 投递确认各有独立证据,前一个不蕴含后一个。
- INV-3 队头唯一:任一时刻只有一个可执行队头(最小未完成
MSG_ID,PENDING与FAILED都占位);FAILED未退避到期时后续消息不得越过。 - INV-7 处理标记单调:任何路径只把空标记写成已处理值,不回撤、不覆盖。
- INV-8 回填只针对终态(
PENDING/FAILED永不写标记);「还欠一次回填」的事实与终态由同一条语句落库,不存在第二处落账。 - INV-9 一信一行、一身份一记录:
PROC_STATE按MSG_ID唯一;同一业务身份至多绑定一条有效处理记录。 - INV-10 对外投递至少一次;端到端恰好一次不在交付范围。
- INV-18 航班表的写者集合是「主泵处理器」与「历史清理」;两者必须互斥(同一
PIPELINE_LOCK,或清理在同一事务内复查判据后再删除),不得出现清理删除与处理器更新同一FLID的竞态。
声明边界(CLM-x,引自 invariants.md §3)
- CLM-3 重放不产生重复业务副作用——依赖
INV-20、G-FLOP-IDEMPOTENT;当前不可声明(29 类 FLOP 幂等矩阵未补全) - CLM-9 处理标记延迟由调度周期决定(≤30 秒)——当前不可声明(批次积压、单行超时与历史作业都会延长实际延迟)
- CLM-16(即 contracts.md
C-16)回填放弃清单在对应信箱边界被清除前必须保持可查
架构决策(D-x,引自 architecture.md §7)
- D3 Kafka 生产要求
acks=all、enable.idempotence=true、max.in.flight=1;不允许通过关闭幂等来满足生产接入。当前证据/缺口:参数默认值与 D3 不一致[G-KAFKA-D3] - D4 自有库终态记录只归档到
PROC_STATE_HST,不侵入共享库的表结构或保留策略。当前证据/缺口:目标表未建[G-PROC-HST]
对外契约(C-x,引自 contracts.md)
保留与清除:
- C-5 处理标记值集与写权限:本系统只写入库方认可的 legacy 值集内的「已处理」值(默认
PROCESSED),只写空标记、不回撤、不覆盖;内部原因记录在自有 PG,不在信箱新增枚举。[待确认 Q7] - C-6 清除语义必须是「标记 + 保留期」:打标本身不触发清除,触发条件是「到达保留期
R_keep」且「边界内全部行已打标」。[待确认 Q7][待确认 Q9] - C-7 保留期下界:
R_keep ≥ max(人工重放期限 + 人工处置期限, 审计期限, 回填重试上限)。这是「重放窗口内原文仍在」的唯一保证来源。[待确认 Q6][待确认 Q9] - C-8 清除前置条件:执行清除时,边界内每行必须已有终局——即「已持有处理标记」或「已登记在本系统的回填放弃清单中且经人工对账确认」。放弃行不写标记,未达终态的行顺延至处理完成后清除。
[待确认 Q7][待确认 Q9] - C-16 回填放弃清单在对应信箱边界被清除前必须保持可查:
C-8以本清单作为清除授权证据之一,该证据不得随处理记录的归档或清除而消失。[我们单方承诺]
我们向库方的承诺:
- C-14 不建表、不改表结构、不迁移 schema、不写共享历史表。
[我们单方承诺]
用户故事验收标准(US-x,引自 user-stories.md)
US-04 明确忽略非业务报文:
- 解码 META 后、身份绑定及处理器分派前,大小写不敏感匹配
TYPE-STYP或TYPE-*;基线为LDM-* / REGN-* / RSTA-* / EROR-*,不混用ERROR。 - 命中后转
SKIPPED,记录ignored:<rule>和计数;不更新航班、不创建业务通知。 - 通过 US-09 保存回填意图;命中、未命中、大小写和重扫均有测试。合法忽略报文不应因
MsgKind尚不能表达它而先解码失败。
US-11 归档自有库终态记录(已按裁定修正):
- 终态记录在了结后经过的时间(
UPDATED_AT)达到PARAM:msgx.proc-state.archive-after时列为归档候选;PENDING/FAILED禁止归档,回填未了结的终态行不进入候选。 - 归档到自有 PG
PROC_STATE_HST,主表保留STATE='ARCHIVED'的去重影子行(仅IDENTITY_KEY与MSG_ID),使归档后同业务身份再次到达仍可去重;MSG_EVENT的历史目标与保留规则由G-EVENT-RETENTION独立处理,不构成归档判据。 - 归档写入与主行置
ARCHIVED在同一自有库事务内完成,按候选时的状态条件复查,影响 0 行即整体回滚;重复执行幂等,失败保留源记录并报告计数。 - 本系统不写共享 MySQL
CMINMSGS_HST、不清理外部信箱;由库方按Q9执行的清除与历史归档见 contracts.md「保留与清除」。
参数(PARAM:x,引自 reference.md)
| PARAM 键 | 当前默认 | 依据 | 与本 spec 的关系 |
|---|---|---|---|
kafka.producers.default.acks |
all |
契约(D3) | G-KAFKA-D3 不动此键 |
kafka.producers.default.enable-idempotence |
true |
契约(D3) | G-KAFKA-D3 不动此键 |
kafka.producers.default.max-in-flight-requests-per-connection |
5 |
与 D3 不一致 | G-KAFKA-D3 收敛到 1 |
msgx.pipeline.event-retention |
目标参数(未实现) | 假定 | G-EVENT-RETENTION 登记默认值(建议 7 天) |
msgx.proc-state.archive-after |
1d |
假定,未实现 | G-PROC-HST 实现后登记实际默认值(建议范围 1~7 天) |
msgx.pipeline.backfill-backoff-ms |
目标参数(未实现) | 假定 | G-BACKFILL-BACKOFF 提取硬编码 30 秒起步 |
msgx.pipeline.backfill-backoff-cap-ms |
目标参数(未实现) | 假定 | G-BACKFILL-BACKOFF 提取硬编码 15 分钟封顶 |
msgx.pipeline.max-attempts |
5 |
假定 | 处理与投递共用;达到即转 DEAD(EXHAUSTED) |
msgx.pipeline.overdue-backfill(R) |
30d |
契约(R ≤ R_keep) |
进入强补写窗口的阈值 |
错误分类(引自 reference.md §4)
| 错误类别 | 触发 | 处置 | 可重放 |
|---|---|---|---|
MALFORMED |
报文非法、原文缺失或缺少该类型必需的业务载荷 | 立即 DEAD |
否 |
PROTOCOL |
载荷存在但整包违反业务协议(运营日冲突、声明数不符) | 立即 DEAD,整包不落地 |
否 |
CODEC_ERROR |
解码能力问题 | FAILED 退避 |
是 |
UNSUPPORTED |
处理器或快照能力未实现 | FAILED 退避,受尝试上限约束 |
是 |
INFRA |
基础设施或执行异常 | FAILED 退避 |
是 |
EXHAUSTED |
尝试次数耗尽 | DEAD;ERROR_CLASS 被覆写为 EXHAUSTED |
是 |
表结构基线(引自 Flyway V1–V9 迁移链)
PROC_STATE(自有 PG,处理状态主表):
| 列 | 类型 | 约束 | 加入迁移 |
|---|---|---|---|
MSG_ID |
BIGINT | PK | V1 |
STATE |
VARCHAR(16) | NOT NULL | V1 |
IDENTITY_KEY |
VARCHAR(200) | UNIQUE (uk_proc_identity) |
V1 |
ATTEMPTS |
INT | NOT NULL DEFAULT 0 | V1 |
NEXT_ATTEMPT_AT |
TIMESTAMPTZ(6) | V1 | |
ERROR_CLASS |
VARCHAR(20) | V1 | |
LAST_ERROR |
VARCHAR(1000) | V1 | |
UPDATED_AT |
TIMESTAMPTZ(6) | NOT NULL | V1 |
RECEIVED_AT |
TIMESTAMPTZ(6) | V2 | |
BACKFILL_AT |
TIMESTAMPTZ(6) | V2 | |
BACKFILL_NEXT_AT |
TIMESTAMPTZ(6) | V2 | |
BACKFILL_ATTEMPTS |
INT | NOT NULL DEFAULT 0 | V2 |
BACKFILL_ERROR |
VARCHAR(512) | V2 | |
BACKFILL_ABANDONED_AT |
TIMESTAMPTZ(6) | V4 | |
BACKFILL_ABANDONED_REASON |
VARCHAR(64) | V4 | |
ENQUEUED_AT |
TIMESTAMPTZ(6) | NOT NULL DEFAULT now() | V6 |
V7 删除了
PROCESSING_STARTED_AT。当前终态值集:PENDING / FAILED / SUCCEEDED / SKIPPED / DEAD;G-PROC-HST 新增ARCHIVED。
MSG_EVENT(自有 PG,outbox):
| 列 | 类型 | 约束 | 加入迁移 |
|---|---|---|---|
EVENT_ID |
BIGSERIAL | PK | V1 |
TARGET |
VARCHAR(30) | NOT NULL | V1 |
PARTITION_KEY |
VARCHAR(32) | NOT NULL | V1 |
EVENT_TYPE |
VARCHAR(16) | NOT NULL DEFAULT 'UPSERT' | V1 |
STATE_VERSION |
BIGINT | NOT NULL | V1 |
PAYLOAD_JSON |
TEXT | NOT NULL | V1 |
STATE |
VARCHAR(16) | NOT NULL | V1 |
ATTEMPTS |
INT | NOT NULL DEFAULT 0 | V1 |
NEXT_ATTEMPT_AT |
TIMESTAMPTZ(6) | V1 | |
ERROR_CLASS |
VARCHAR(20) | V1 | |
LAST_ERROR |
VARCHAR(1000) | V1 | |
CREATED_AT |
TIMESTAMPTZ(6) | NOT NULL | V1 |
无
UPDATED_AT、无SENT_AT。G-EVENT-RETENTION 需 V10 迁移新增SENT_AT。
PROC_STATE_HST:尚未建表 [G-PROC-HST]。
时间常数排序(引自 design.md「生命周期与清除」)
R ≤ R_keep(C-7)- 去重记忆期 ≥
R_keep(INV-9)——否则「归档后重复」不成立 - 回填放弃清单可见期 ≥
R_keep(C-16)——否则库方清除缺C-8依据 - 归档阈值计的是终局之后的时间,不是入队之后:终态行未了结回填时不进入候选
待确认事项(Q-x,引自 contracts.md §C,仅列本 spec 涉及项)
| 编号 | 事项 | 当前假定 | 阻塞 |
|---|---|---|---|
| Q2 | 信箱 ID 单调、ID 分配→事务可见时延上界、ID 空间不复位 | 时延按 5 分钟 max-commit-delay(缺少依据的占位值) |
发现完整性声明、空洞老化阈值 |
| Q6 | 重放期限与人工处置期限的取值 | R = 30 天;重放/处置期限未定 |
R_keep 取值 |
| Q7 | 处理标记值集与写权限、原文保留期 | 写入 PROCESSED |
回填值集、保留期下界 |
| Q8 | 逐类覆盖清单(积压摸底的类型分布依据) | — | 积压处置与逐类矩阵(暂缓) |
| Q9 | 清除执行方与 DDL 授权、方案 A/B 选型 | 首选方案 A | R_keep 与清除边界 |
| Q11 | 上游 SEQN 重置周期与业务身份的日期边界 |
不含日期边界 | 身份算法 |
| Q13 | 日计划缺失可选字段的删除语义 | 保留未携带字段(暂缓) | 快照合并 |
User Stories
- As a 系统运维, I want Kafka producer
max-in-flight=1, so that 同一分区内事件投递顺序与写入顺序一致,满足 architectureD3 - As a 系统运维, I want LDM/REGN/RSTA/EROR 消息被显式标记为 SKIPPED, so that 监控告警能区分「已知忽略类型」和「真正的异常消息」
- As a 系统运维, I want MSG_EVENT 已投递行按保留期自动清理, so that outbox 表不会无限增长导致查询退化
- As a 系统运维, I want 回填退避参数可通过配置调整, so that 不同负载下无需改代码即可优化回填节奏
- As a 系统运维, I want PROC_STATE 终态行归档到 PROC_STATE_HST 并在主表留下去重影子行, so that PROC_STATE 表有生命周期终点且不破坏业务去重(
INV-9) - As a 领域模型维护者, I want FLOP 幂等规则按实际到达类型增量补齐, so that 不为未到达的类型做无用设计
Implementation Decisions
G-KAFKA-D3
- 把
PARAM:kafka.producers.default.max-in-flight-requests-per-connection从当前值5收敛到D3要求的1 - 影响
KAFKA:msg和KAFKA:schd两个投递目标 - 吞吐影响可接受:单分区有序是
INV-10的隐含前提,D3明确要求该键取有序值 - 配置落点:
application.yml的kafka.producers.default.max-in-flight-requests-per-connection(现由MSGX_KAFKA_MAX_IN_FLIGHT覆盖);.env.example注释中的简写改为全键名 README.md中max.in.flight=1简写统一为全键;保留「严禁非幂等降级」的契约口径- 验收:以配置断言(或启动自检)钉住生效值满足
D3,并在G-KAFKA-D3缺口索引中更新验证证据映射
G-IGNORE
- codec 层按
TYPE+STYP组合识别LDM/REGN/RSTA/EROR(大小写不敏感,基线见US-04) - 不新增
MsgKind变体:MsgKind的契约是Schd(RESP/DNLD/ADFT)/Flop/Fdel/Unsupported;忽略是解码后路由边界的策略,与MsgKind正交 - 判定位置:解码成功之后、身份绑定之前、
MsgKind分派之前(US-04);忽略报文不写IDENTITY_KEY - 分派路径:不走航班决策逻辑,直接走非业务型终态(
INV-8)- 单语句写
PROC_STATE(SKIPPED)+ 回填意图同语句落库 - 不取
PIPELINE_LOCK、不触航班表/事件 - 与
UNSUPPORTED区分:忽略类不进入重试、不占队头退避
- 单语句写
- 命中优先于
MsgKind.Unsupported分支:不得再落到FAILED(UNSUPPORTED)的退避路径 - 规则未实现前的回退行为:忽略类报文按
UNSUPPORTED可重试处理(现状),不得按MALFORMED判死 design.md明确:「类型未覆盖不等于报文非法:忽略类报文在规则实现前不按MALFORMED处理」- 计数:
US-04要求计数;若新增指标(建议msgx.pipeline.codec.ignored.total),须同 PR 登记reference.md指标表,不新增未登记指标
G-EVENT-RETENTION
MSG_EVENT新增SENT_AT列(可空,NULL= 未投递):V10 Flyway 迁移,只增不改;写入必须在条件确认SENT的同一 UPDATE 内完成(原子性保证,不存在先写状态再补时间的两步路径);KAFKA:schd代次重置为PENDING时清空;已有行SENT_AT为NULL,视为未投递,不会被清理谓词命中- jobs 模块新增清理作业,扫描
MSG_EVENT中STATE='SENT'、SENT_AT IS NOT NULL且SENT_AT < now() − PARAM:msgx.pipeline.event-retention的行,批量删除(ORDER BY EVENT_ID ASC+ 批次上限,幂等可重跑) - 删除必须带
STATE='SENT'条件:KAFKA:schd同FLID出现新代次时该行被重置为PENDING,两种执行顺序下都不会被误删- 清理先删 → upsert 新建行(新代次
PENDING,正确) - upsert 先重置为
PENDING→ 同一 DELETE 因谓词不匹配而跳过(正确)
- 清理先删 → upsert 新建行(新代次
KAFKA:msg的DEAD行保留作 DLQ,人工处置后再清理KAFKA:schd的DEAD行在同 FLID 出现新的可接受状态代次时被重置- 作业不触航班表,不需要
PIPELINE_LOCK:INV-18约束的是航班表写者互斥(「主泵处理器」与「历史清理」),MSG_EVENT不是航班表 - 默认保留期在 reference.md 登记(建议起点 7 天,需生产数据校准)
G-BACKFILL-BACKOFF
- 从 BackfillService 提取两个配置参数(reference.md 参数注册表已登记为目标参数):
PARAM:msgx.pipeline.backfill-backoff-ms(当前硬编码 30 秒起步)PARAM:msgx.pipeline.backfill-backoff-cap-ms(当前硬编码 15 分钟封顶)
- 不改变现有退避逻辑,仅将代码常量替换为配置注入
- 实现后更新 reference.md 状态从「目标参数(未实现)」为实际默认值
G-PROC-HST
- 新建 migration:
PROC_STATE_HST表,结构与PROC_STATE相同,增加ARCHIVED_AT时间戳 - 归档候选条件:
- 终态(
STATE IN (SUCCEEDED, SKIPPED, DEAD)) - 回填已了结:
BACKFILL_AT非空,或已放弃且经人工对账(放弃行不写标记,是C-8清除授权证据,未对账前不得归档) - 终局后经过
PARAM:msgx.proc-state.archive-after阈值;基准是UPDATED_AT(终态与了结都推进它,了结后不再更新),不是ENQUEUED_AT
- 终态(
- 归档操作不取
PIPELINE_LOCK:人工重放走MessageLifecycleGate、不取该锁,锁是假保护;批量归档持锁会阻塞主泵 FIFO。互斥由条件写入 +MessageLifecycleGate提供:同一事务内先INSERT INTO PROC_STATE_HST … SELECT,再按候选时的STATE条件写主表;影响 0 行即整体回滚、该行跳过(DEAD已被重放改回PENDING时谓词不匹配) - 主表不删除归档行,而是置
STATE='ARCHIVED'(新增状态值)并清空尝试/错误/回填/时间列,仅保留MSG_ID、IDENTITY_KEY、ARCHIVED_AT - 去重记忆约束:归档后
IDENTITY_KEY的业务去重能力必须保留(INV-9),去重记忆期 ≥R_keep(时间常数排序)。实现取「主表保留去重影子行」:影子行即STATE='ARCHIVED'行,IDENTITY_KEY唯一约束留在主表不动;所有非归档谓词必须显式排除ARCHIVED——队头推进(INV-3只计PENDING/FAILED)、backlog()聚合、回填扫描均不靠状态包含列表隐式过滤,而是显式STATE <> 'ARCHIVED'或等价的排除条件
G-FLOP-IDEMPOTENT 收窄
- 分析生产实际到达的 FLOP 类型分布
- 仅对实际到达类型补齐幂等规则
- 文档记录:「未到达类型在首次到达时补齐」
INV-20/CLM-3在矩阵完整前仍标记为不可声明
Testing Decisions
- G-KAFKA-D3:配置级变更,通过 application-test.yml 断言 producer 生效值满足
D3 - G-IGNORE:
- codec 单元测试:给定 LDM/REGN/RSTA/EROR XML,断言分派为 SKIPPED 路径(不走 FlightStateEngine)
- processing 集成测试:给定忽略类型消息,断言 PROC_STATE 写入 SKIPPED、不触航班表/事件、回填意图同语句落库
- 断言
INV-8:非业务型终态不触碰航班表 /MSG_EVENT,只写PROC_STATE,且终态与回填意图同语句生效 - 断言
INV-7:回填四分支对ignored:<rule>行成立(尤其MISSING_ROW立即放弃并告警) - 断言
INV-3:忽略报文到达终态后不占队头、不阻塞后续消息 - 不回退既有
duplicate-of行为
- G-EVENT-RETENTION:
- 使用 TestClock 推进时间,断言超过保留期的
SENT_AT行被清除 - 断言未超期的 SENT 行、
SENT_AT IS NULL的行保留 - 断言非 SENT 状态行(PENDING/DEAD)不受影响
- 断言
KAFKA:msgDEAD 行不被清理(DLQ 保留) - 断言
KAFKA:schd新代次重置出的PENDING行在同一轮清理中不被删(含两种执行顺序) - 断言清理幂等可重跑;批次执行不改变
KAFKA:msg的投递顺序与队头(INV-3/INV-10)
- 使用 TestClock 推进时间,断言超过保留期的
- G-BACKFILL-BACKOFF:断言配置参数注入生效,退避行为与硬编码时一致
- G-PROC-HST:
- 使用 TestClock + 内存适配器
- 断言归档候选条件:终态 + 回填了结 +
UPDATED_AT阈值过期;未了结与未到阈值均不进入候选 - 断言放弃行未对账前不被归档
- 断言条件写入复查:DEAD 行被重放为
PENDING后,归档事务整体回滚、该行跳过 - 断言归档原子性:HST 有对应行,且主表该行置
ARCHIVED - 断言
DELETE影响 0 行时整体回滚,源记录保留并报告计数 - 断言去重记忆:归档后同身份新消息仍被识别为
duplicate-of:<msgId> - 断言
ARCHIVED行不占队头(INV-3)、不参与backlog()聚合、不被回填扫描选中 - 迁移与删除用
PgTestSupport/FlywayMigrationTest本地验证
- 所有测试使用
TestClocks.kt与内存适配器,禁止 Thread.sleep 和真实外部基础设施 - 涉及排序/重试/幂等/投递必须补
invariants.md验证映射回归
Out of Scope
以下内容经 grill 审查后判定为当前迭代过度工程化,推至 phase B:
| 项 | 缺口 ID | 理由 |
|---|---|---|
| REQ_TRACK 运行时协调器 | G-REQ-TRACK |
当前迭代无 outbound request 用户故事(US-08 未进入迭代) |
| RESP 应答守卫 | G-RESP-GUARD |
依赖 REQ_TRACK,同上 |
| REQ_TRACK 开放态唯一索引运行时 | G-REQ-OPEN-UNIQUE |
索引已存在(V8 migration),运行时依赖 REQ_TRACK 协调器 |
| MAFL 派生投影实现 | G-MAFL |
无下游消费者读取主航班投影;INV-21/INV-22 保留为设计约束 |
| 原始报文留存通道 | G-REPLAY-CHANNEL |
replay 能力本身未建,mark-means-purge 语义未确认(Q9) |
| 归档保留期 | G-HST-RETENTION |
PROC_STATE_HST 尚未建成,保留期在建成时一并定义 |
| 航班历史存储保留期 | G-FLIGHT-HIST-RETENTION |
历史存储未接入 |
| REQ_TRACK 关闭态保留期 | G-REQ-TRACK-RETENTION |
REQ_TRACK 关闭行不存在 |
| FLOP 29 类全矩阵 | G-FLOP-IDEMPOTENT |
收窄为按到达类型增量补齐 |
| Compat HTTP 完整 ResponseDto | G-COMPAT-HTTP |
等 Q3 确认 |
| 非阻断 Q 项 | Q4, Q6, Q8, Q10, Q12, Q13, Q14, Q15, Q16 | 不阻塞当前核心路径;Q8/Q13 已标暂缓 |
Further Notes
脆弱边界风险
G1 移除后(commit b873855),水位线正确性完全依赖 PRE-2/PRE-3(库方承诺 ID 单调递增)。Q2 未确认 = 系统在此假设上裸奔。建议将 Q2 确认作为当前迭代的阻断性前置条件。
时间常数序
design.md「生命周期与清除」的时间常数排序定义的 R ≤ R_keep ≤ 去重记忆 ≥ 放弃清单可见期 约束在实现 G-EVENT-RETENTION 和 G-PROC-HST 时必须遵守:
R ≤ R_keep(C-7)- 去重记忆期 ≥
R_keep(INV-9) - 回填放弃清单可见期 ≥
R_keep(C-16) - 归档阈值计的是终局之后的时间,不是入队之后
C-16 回填放弃清单
C-16 要求放弃清单在信箱清除前保持可查。G-PROC-HST 归档作业必须确保:已放弃行的记录不会因归档而消失——放弃行未对账前不得归档(已在 Testing Decisions 中断言)。
通用约束
- 依据:
docs/为唯一设计依据;docs/legacy/仅用于Q项对拍。 - 参数:新增或改名
msgx.*/ 基础设施键,同 PR 登记reference.md;本文只引PARAM:键。 - 存储:共享 MySQL 零 Schema 变更(
C-14);Flyway 只动自有 PG,且只增不改(改列一律新迁移)。 - 测试:用
TestClocks.kt与内存适配器;涉及排序/重试/幂等/投递必须补invariants.md验证映射回归;禁Thread.sleep、禁真实外部基础设施。真实 PG 用例走PgTestSupport/FlywayMigrationTest。 - 交付:Conventional Commits + 小写 Plane scope,一单只关一个 work item。
文档修正(实施前完成)
以下文档段落与已裁定决策不一致,须在对应实施 PR 合入前修正:
- design.md「处理终态归档」段落:把「必须在
PIPELINE_LOCK内复查STATE未变再删」改为条件 DELETE 语义——同一事务内INSERT…SELECT+ 按候选时STATE条件DELETE,0 行即回滚;互斥由条件写入提供,不取锁 - user-stories.md
US-11:- AC1:阈值基准从
ENQUEUED_AT改为UPDATED_AT(与design.md时间常数排序一致,UPDATED_AT是「终局后且已了结」的保守时刻) - AC2:删除「未完成投递」判据(
INV-1明确投递只依赖MSG_EVENT,PROC_STATE不承担投递证据;MSG_EVENT清理归G-EVENT-RETENTION独立管)
- AC1:阈值基准从
文档同步(实施后完成)
实现完成后需更新:
- reference.md 参数注册表:
PARAM:msgx.pipeline.backfill-backoff-ms/-cap-ms/PARAM:msgx.pipeline.event-retention从「目标参数(未实现)」改为实际默认值 - reference.md 参数注册表:
PARAM:kafka.producers.default.max-in-flight-requests-per-connection的「依据」列从「与 D3 不一致」改为契约一致 - reference.md 参数注册表:
PARAM:msgx.proc-state.archive-after从「未实现」改为实际默认值 - invariants.md 缺口索引:闭合的
G-x从表中移除