fix(channel): message_sent 回声升 T2 落行,出站消息 id 不再被丢

塔转 M12 登记的缺口(塔裁定「归你」,文件都在本片 scope 内):她自己发出消息
的回声(message_sent)被当成「帧级链路遥测」一并豁免 self 升档 ⇒ 停在 T1,
只进计数、不落行,`message_id` 被丢,「她最近发了哪些消息」在库里查不到。

- events.go: 把 MessageSent 从 isFrameTelemetry 的豁免里摘出(它不是帧级遥测:
  一帧 = 她真发出的一条消息,回声带的是那条出站消息的 id)。
  心跳 / 生命周期的豁免**保持不变**(其登记的迁移债注释原样保留)。
- events_mapping_test.go: 两条 MessageSent 断言同步更新
  (:52 自身回声命中自己 ⇒ 由 T1 改 T2;:33 无 self 信号仍走基础档 T1,标签改精确)。
- events_test.go: 新增端到端用例(真 SQLite):self 命中 ⇒ 落 1 行、tier=t2、
  MessageID=out-msg-42 留在库里、不进上下文、不驱动 loop。
This commit is contained in:
AskaEth committed 2026-10-13 20:50:31 +08:00
1 parent 537a8b7988
commit 1d49d266f0
3 files changed
+56 -9

No files matched your search

+11 -6
View File
@@ -259,10 +259,16 @@ func kindTier(kind adapter.NoticeKind) events.Tier {
}
}
// isFrameTelemetry 报告该类别是否属于**帧级链路遥测**:心跳(~15s 一帧)、生命周期、
// 她自己发出消息的回声。这三类的 self 命中是**恒定**的(帧帧都命中 self_id),所以
// 按 §2.2 升档等于「每帧写一行 channel_events」—— 而 §4.3 明令「心跳 / 在线状态逐帧
// 写库是纯浪费」(计数表存在的理由),§3.1 给这三类的档位也正是 T1。
// isFrameTelemetry 报告该类别是否属于**帧级链路遥测**:心跳(~15s 一帧)与生命周期。
// 这两类的 self 命中是**恒定**的(帧帧都命中 self_id),所以按 §2.2 升档等于「每帧写一行
// channel_events」—— 而 §4.3 明令「心跳 / 在线状态逐帧写库是纯浪费」(计数表存在的理由),
// §3.1 给这两类的档位也正是 T1。
//
// 🔴 **`message_sent`(她自己发出消息的回声)不在此列**(切 M13;原 M12 登记的缺口):
// 它**不是**帧级遥测 —— 一帧对应她真发出的一条消息,回声带的是那条出站消息的 id
// (`RefMessage`)。此前把它一并豁免,会让 self 命中**不升档** ⇒ 整条回声只进计数,
// `message_id` 被丢,「她自己最近发了哪些消息」在库里查不到(M12 的 A 层出站 id 因此
// 断在这里)。摘出后它按 §2.2 正常 T1 → T2 逐条落行。
//
// ⚠️ **由此登记的债**(design/26 §2.6「她自己账号的在线 / 离线」= T2 高价值):
// 那是**状态迁移**(在线→离线 / 离线→在线),不是每一帧。判定它需要迁移信号
@@ -272,8 +278,7 @@ func kindTier(kind adapter.NoticeKind) events.Tier {
// 迁移事件(T2)留待后续切片;已在切片报告「未做 / 存疑」中列明。
func isFrameTelemetry(cat adapter.NoticeCategory) bool {
switch cat {
case adapter.NoticeCategoryHeartbeat, adapter.NoticeCategoryLifecycle,
adapter.NoticeCategoryMessageSent:
case adapter.NoticeCategoryHeartbeat, adapter.NoticeCategoryLifecycle:
return true
default:
return false