AskaEth 5989e0d4eb W74 撤掉「容忍 + 下次拉取」兜底 —— voice 键存在即语音,覆盖全部读面
后端回执三十八 §二:M44(合并号 fb55769,契约 v0.97)把落库顺序改成
INSERT → 写 voice 列 → notify,于是语音消息的**第一条** message_persisted /
channel_persisted 帧就带 voice,握手 history 回放也带(与 REST 读面同一收口点
attachVoiceTo → historyVoiceOf)。据此撤掉 W73 时期的「容忍 + 下次拉取」兜底。

## 撤掉的到底是什么(调查结论,不是运行时改动)

全仓 grep 过「容忍 / tolerat / 下次拉取 / refetch / 自动升级 / 四个 REST / 覆盖率」,
**没有任何运行时的兜底分支**:`domain.chat.isVoiceMessage` 从 W73 起就一直只是
`message.voice != null`,没有重试、轮询、延迟判定或「先按文本显示」的标记位。
兜底的全部实体是三处**口径**(两处 KDoc + 一条测试注释),本次逐条改正:

1. MessageDto.kt 类头:「[voice] 是『一处覆盖三条路』的例外,只在四个 REST 读取面填充,
   实时帧与握手回放缺席」→ v0.97 起**不再是例外**,四条读面都填、第一条帧就带。
2. MessageDto.kt 的 voice 字段 KDoc:删掉「覆盖率(v0.79):四个 REST 读取面才有 /
   实时帧没有 / 不要在实时帧上等 voice / 等下一次拉取自动升级成语音气泡 / 这是设计不是缺陷」,
   换成 v0.97 口径(全部读面都带、第一条帧就带),并把回执点名的两条实现前提写成护栏:
   ① voice 列写入必须先于 notify;② 帧内 voice 是发出那一刻的即时列读。
   任一被改回「先 notify 再写列」,本端判据立刻退回 v0.79 行为——而本端刻意不再留兼容分支。
   「列被外部改坏 ⇒ 字段缺席,与『不是语音』同形」那段仍然成立,原样保留。
3. MessageMergeTest.kt 用例 `a later copy carrying voice wins over the earlier copy`
   (原 `a later page carrying voice upgrades …`):断言一字未改,只重写注释理由——
   按 id 合并「新到的那份胜出」照旧必需(同一条消息会从落库帧 / 握手回放 / 分页 / 编辑帧
   多路到达),但它不再是「语音升级的唯一通路」。

## 新增的钉子

WsEventPersistedVoiceTest(5 个用例)钉住 v0.97 的正面事实:message_persisted 载荷里
voice 四键齐全时读得回来且 isVoiceMessage 为真;channel_persisted 同样;平台自带转写
(四键全缺席)仍是语音但 voiceAudio 为 null(不画播放控件);voice 键缺席 ⇒ null ⇒
不是语音(判据只有一个方向);message_edited 帧不许把语音气泡打回纯文本。
这条测试的意义:PersistedData 只解 message,生产 Json 是 ignoreUnknownKeys = true ⇒
未来谁把 voice 从落库帧上摘掉不会报错,只会静默退回纯文本。

## 验证

./gradlew.bat assembleDebug → BUILD SUCCESSFUL
./gradlew.bat testDebugUnitTest → 134 suites / 1675 tests,0 failures / 0 errors / 0 skipped
(基线 133 / 1670;+1 suite +5 tests 全部来自新增的 WsEventPersistedVoiceTest)
2026-10-26 21:59:39 +08:00
S
Description
昔涟在安卓设备上的载体。
Apache-2.0
19 MiB
0 Stars 1 Watchers 0 Forks
2026-05-29 22:15:56 +08:00