Files
msgexchange-v2/docs/spec-boundary-closure.md
T
windyboy 30dc8adf00 docs(spec): 领域边界闭合 spec 与配套文档修正
新增 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 补条件回滚语义)。
2026-09-13 12:52:21 +08:00

26 KiB
Raw Blame History

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。

当前迭代交付:

  1. G-KAFKA-D3:把 PARAM:kafka.producers.default.max-in-flight-requests-per-connection 收敛到 D3 要求值,满足 architecture D3 有序性约束
  2. G-IGNOREcodec 层识别 LDM/REGN/RSTA/EROR,分派为 SKIPPED 终态,走非业务型终态路径
  3. G-EVENT-RETENTIONjobs 模块新增 MSG_EVENT 已发送行清理作业,按 PARAM:msgx.pipeline.event-retention 定期清除
  4. G-BACKFILL-BACKOFF:将 BackfillService 硬编码退避提取为 PARAM:msgx.pipeline.backfill-backoff-ms / -cap-ms
  5. G-PROC-HST:建 PROC_STATE_HST 归档表,jobs 模块新增终态行归档作业

同步推进确认:

  • Q2(ID 单调递增):G1 移除后水位线正确性的唯一保障
  • Q7processing-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_IDPENDINGFAILED 都占位);FAILED 未退避到期时后续消息不得越过。
  • INV-7 处理标记单调:任何路径只把空标记写成已处理值,不回撤、不覆盖。
  • INV-8 回填只针对终态(PENDING / FAILED 永不写标记);「还欠一次回填」的事实与终态由同一条语句落库,不存在第二处落账。
  • INV-9 一信一行、一身份一记录:PROC_STATEMSG_ID 唯一;同一业务身份至多绑定一条有效处理记录。
  • INV-10 对外投递至少一次;端到端恰好一次不在交付范围。
  • INV-18 航班表的写者集合是「主泵处理器」与「历史清理」;两者必须互斥(同一 PIPELINE_LOCK,或清理在同一事务内复查判据后再删除),不得出现清理删除与处理器更新同一 FLID 的竞态。

声明边界(CLM-x,引自 invariants.md §3

  • CLM-3 重放不产生重复业务副作用——依赖 INV-20G-FLOP-IDEMPOTENT;当前不可声明29 类 FLOP 幂等矩阵未补全)
  • CLM-9 处理标记延迟由调度周期决定(≤30 秒)——当前不可声明(批次积压、单行超时与历史作业都会延长实际延迟)
  • CLM-16(即 contracts.md C-16)回填放弃清单在对应信箱边界被清除前必须保持可查

架构决策(D-x,引自 architecture.md §7

  • D3 Kafka 生产要求 acks=allenable.idempotence=truemax.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 明确忽略非业务报文:

  1. 解码 META 后、身份绑定及处理器分派前,大小写不敏感匹配 TYPE-STYPTYPE-*;基线为 LDM-* / REGN-* / RSTA-* / EROR-*,不混用 ERROR
  2. 命中后转 SKIPPED,记录 ignored:<rule> 和计数;不更新航班、不创建业务通知。
  3. 通过 US-09 保存回填意图;命中、未命中、大小写和重扫均有测试。合法忽略报文不应因 MsgKind 尚不能表达它而先解码失败。

US-11 归档自有库终态记录(已按裁定修正):

  1. 终态记录在了结后经过的时间(UPDATED_AT)达到 PARAM:msgx.proc-state.archive-after 时列为归档候选;PENDING/FAILED 禁止归档,回填未了结的终态行不进入候选。
  2. 归档到自有 PG PROC_STATE_HST,主表保留 STATE='ARCHIVED' 的去重影子行(仅 IDENTITY_KEYMSG_ID),使归档后同业务身份再次到达仍可去重;MSG_EVENT 的历史目标与保留规则由 G-EVENT-RETENTION 独立处理,不构成归档判据。
  3. 归档写入与主行置 ARCHIVED 在同一自有库事务内完成,按候选时的状态条件复查,影响 0 行即整体回滚;重复执行幂等,失败保留源记录并报告计数。
  4. 本系统不写共享 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-backfillR 30d 契约(R ≤ R_keep 进入强补写窗口的阈值

错误分类(引自 reference.md §4

错误类别 触发 处置 可重放
MALFORMED 报文非法、原文缺失或缺少该类型必需的业务载荷 立即 DEAD
PROTOCOL 载荷存在但整包违反业务协议(运营日冲突、声明数不符) 立即 DEAD,整包不落地
CODEC_ERROR 解码能力问题 FAILED 退避
UNSUPPORTED 处理器或快照能力未实现 FAILED 退避,受尝试上限约束
INFRA 基础设施或执行异常 FAILED 退避
EXHAUSTED 尝试次数耗尽 DEADERROR_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 / DEADG-PROC-HST 新增 ARCHIVED

MSG_EVENT(自有 PGoutbox):

类型 约束 加入迁移
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「生命周期与清除」)

  1. R ≤ R_keepC-7
  2. 去重记忆期 ≥ R_keepINV-9)——否则「归档后重复」不成立
  3. 回填放弃清单可见期 ≥ R_keepC-16)——否则库方清除缺 C-8 依据
  4. 归档阈值计的是终局之后的时间,不是入队之后:终态行未了结回填时不进入候选

待确认事项(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

  1. As a 系统运维, I want Kafka producer max-in-flight=1, so that 同一分区内事件投递顺序与写入顺序一致,满足 architecture D3
  2. As a 系统运维, I want LDM/REGN/RSTA/EROR 消息被显式标记为 SKIPPED, so that 监控告警能区分「已知忽略类型」和「真正的异常消息」
  3. As a 系统运维, I want MSG_EVENT 已投递行按保留期自动清理, so that outbox 表不会无限增长导致查询退化
  4. As a 系统运维, I want 回填退避参数可通过配置调整, so that 不同负载下无需改代码即可优化回填节奏
  5. As a 系统运维, I want PROC_STATE 终态行归档到 PROC_STATE_HST 并在主表留下去重影子行, so that PROC_STATE 表有生命周期终点且不破坏业务去重(INV-9
  6. 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:msgKAFKA:schd 两个投递目标
  • 吞吐影响可接受:单分区有序是 INV-10 的隐含前提,D3 明确要求该键取有序值
  • 配置落点:application.ymlkafka.producers.default.max-in-flight-requests-per-connection(现由 MSGX_KAFKA_MAX_IN_FLIGHT 覆盖);.env.example 注释中的简写改为全键名
  • README.mdmax.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_ATNULL,视为未投递,不会被清理谓词命中
  • jobs 模块新增清理作业,扫描 MSG_EVENTSTATE='SENT'SENT_AT IS NOT NULLSENT_AT < now() PARAM:msgx.pipeline.event-retention 的行,批量删除(ORDER BY EVENT_ID ASC + 批次上限,幂等可重跑)
  • 删除必须带 STATE='SENT' 条件:KAFKA:schdFLID 出现新代次时该行被重置为 PENDING,两种执行顺序下都不会被误删
    • 清理先删 → upsert 新建行(新代次 PENDING,正确)
    • upsert 先重置为 PENDING → 同一 DELETE 因谓词不匹配而跳过(正确)
  • KAFKA:msgDEAD 行保留作 DLQ,人工处置后再清理
  • KAFKA:schdDEAD 行在同 FLID 出现新的可接受状态代次时被重置
  • 作业不触航班表,不需要 PIPELINE_LOCKINV-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

  • 新建 migrationPROC_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_IDIDENTITY_KEYARCHIVED_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:msg DEAD 行不被清理(DLQ 保留)
    • 断言 KAFKA:schd 新代次重置出的 PENDING 行在同一轮清理中不被删(含两种执行顺序)
    • 断言清理幂等可重跑;批次执行不改变 KAFKA:msg 的投递顺序与队头(INV-3 / INV-10
  • 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 时必须遵守:

  1. R ≤ R_keepC-7
  2. 去重记忆期 ≥ R_keepINV-9
  3. 回填放弃清单可见期 ≥ R_keepC-16
  4. 归档阈值计的是终局之后的时间,不是入队之后

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_EVENTPROC_STATE 不承担投递证据;MSG_EVENT 清理归 G-EVENT-RETENTION 独立管)

文档同步(实施后完成)

实现完成后需更新:

  • 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 从表中移除