Files
trade-message-center/docs/onetalk-media-message-format-reverification.md

221 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OneTalk 媒体消息格式二次运行态复核方案
> 日期:2026-09-02
> 性质:方案讨论与只读运行态复核清单,不是 Trellis task,不包含实现承诺
> 目标:通过真实运行态证据复核两份调查文档的共同结论、冲突结论和仍不明确的内容
## 1. 复核对象
本次复核以以下两份文档为输入:
1. `/Users/ybf/code/trade-message-center-worktree/docs/onetalk-media-message-format-investigation.md`
2. `/Users/ybf/code/trade-message-center-worktree/docs/onetalk-message-content-formats.md`
复核时必须区分三类结论:
- **运行态事实**:能够从真实 WebSocket、SDK 返回、页面桥、IndexedDB、Bright 或 Mind 中直接观察。
- **代码事实**:能够从当前代码路径确认,但尚未完成真实端到端验证。
- **方案决策**:例如 raw content 是否保留、normalized content 放在哪一层。这类结论不能伪装成运行态事实。
## 2. 安全边界与停止条件
### 2.1 默认只读
默认允许:
- 读取当前 Chromium/CDP 目标、版本和已加载 OneTalk SDK 方法。
- 调用 `getConversationListByPagination`
- 调用 `fetchMessagesWithoutUpdateToRead`
- 观察历史 WebSocket 响应和同一次调用的 SDK 归一化结果。
- 读取扩展 IndexedDB、Service Worker 状态和 Bright 的只读查询结果。
- 在内存中执行 Base64、UTF-8 和 JSON 解码,并只输出字段名、类型、长度和布尔判断。
默认禁止:
- 调用 `fetchMessages``updateMessageToRead` 或其它会改变已读状态的方法。
- 点击发送、上传、下载、文件选择器或会改变页面 store 的操作。
- 将 Cookie、SID、token、`chatToken`、加密账号、消息正文、完整 URL、URL query 值或原始 payload 写入文件、日志或报告。
- 为了便于复核而修改生产数据、IndexedDB、Local Storage、Service Worker 数据或 Bright 数据库。
图片/文件发送、真实上传、二进制下载和主动制造 live push 必须作为单独步骤,由用户明确同意后再执行;它们不属于默认只读复核范围。
### 2.2 立即停止
出现以下任一情况时停止当前检查并清除未保存的临时输出:
- DevTools、终端或脚本准备输出认证值、完整 URL 或消息正文。
- 所调用的方法实际改变已读状态、会话状态或页面数据。
- URL 验证开始返回二进制正文,而当前步骤只获准检查响应元数据。
- 无法区分测试账号、真实业务账号或目标会话。
## 3. 实际调试复核范围
### 3.1 环境基线
| 编号 | 检查项 | 需要记录的安全证据 | 完成条件 |
| ---- | --------------- | ---------------------------------------------------------- | ------------------------------ |
| R01 | Chromium 与 CDP | 浏览器版本、CDP protocol 版本、目标页面 URL 的 origin/path | 能确认本次检查对应 OneTalk PWA |
| R02 | 扩展与代码版本 | 扩展版本、仓库 commit、工作树是否有影响复核的未提交改动 | 复核结果能够绑定到唯一代码快照 |
| R03 | SDK 方法 | 方法是否存在及函数类型,不记录函数闭包或运行时凭证 | 确认只读历史方法可用 |
| R04 | 数据状态 | IndexedDB schema 版本及各表计数,不输出记录正文 | 能区分本次复核前后的已有数据 |
### 3.2 历史消息原始格式
至少选择包含下列样本的历史会话;每个样本只使用 `T1``I1``F1``C1` 等本地别名,不在报告中记录真实会话 ID 或消息 ID。
| 编号 | 样本 | 必须检查 | 不得记录 |
| ---- | --------------- | -------------------------------------------------------------------------------- | --------------------------------- |
| R10 | 文本 `T1` | `contentType``text.content` 类型、`text.extension` 的键名 | 正文、extension 字符串值 |
| R11 | 图片 `I1` | `contentType``custom.type`、Base64/UTF-8/JSON 是否成立、解码后字段名和字段类型 | MD5、fileId、完整 URL 和 query 值 |
| R12 | PDF 文件 `F1` | `custom.type``cardType``params` 字段名和类型 | 文件名原值、文件 ID、完整 URL |
| R13 | 非文件卡片 `C1` | `custom.type``cardType`、SDK `subType/msgType` | 卡片业务值和账号信息 |
每个样本都需要同时保留两种独立、脱敏的证据:
1. WebSocket 原始 `body.userMessageModels[].message.content` 的结构证据。
2. 同一次请求对应的 SDK `list[]` 归一化类型和 `originalData` 字段结构。
不能只根据 SDK 的 `msgType/subType` 反推 WebSocket 原始类型,也不能只根据 WebSocket 的 `custom.type` 推断业务含义。
## 4. 冲突结论的第二次确认
每项冲突必须得到两份相互独立的证据后才能定案。代码引用和同一份运行输出的重复截图不算两份独立证据。
### C1. `custom.type=10010` 是否等于附件
**第一次证据**
- 真实 PDF 样本:`custom.type=10010`,解码后 `cardType=12`SDK 归一化为文件类型。
**第二次证据**
- 真实非文件卡片:同样为 `custom.type=10010`,但解码后 `cardType=2000` 或其它非文件值,且 SDK 不把它归一化为文件。
**定案条件**
```text
file = contentType=101
&& custom.type=10010
&& decoded.cardType=12
&& decoded.params 通过文件 schema
```
若缺少非文件卡片样本,只能说“当前 PDF 样本是附件”,不能泛化为“所有 `custom.type=10010` 都是附件”。
### C2. 附件样本的 `cardType` 是 `0` 还是 `12`
**第一次证据**
- 直接读取 PDF WebSocket 原始 `custom.data` 的解码结果,只记录 `cardType` 数字。
**第二次证据**
- 对照同一条消息的 SDK `originalData.cardType`
**定案条件**
- 两个边界均为 `12`:文档中的 `0` 必须标记为错误或不当占位。
- 两个边界不一致:记录为 SDK 转换差异,不能先选择其中一个。
`cardType` 是判别字段,文档示例不得用 `0` 冒充脱敏占位;未知值应写成 `<number>`
### C3. raw content 应继续保留还是在 MAIN world 归一化
**第一次证据**
- 当前观察器确实复制完整 raw content,Bright 当前清洗器确实对字符串原样放行。
**第二次证据**
- 真实运行链检查证明序列化 JSON 或 Base64 中的敏感键名能够到达页面桥、IndexedDB、Bright 输入或持久化边界中的至少一处。
**定案条件**
- 若敏感内容能跨边界:raw content 不得继续作为跨层合同,必须在 MAIN world 白名单解码。
- 即使当前样本未穿透,也不能据此证明任意 raw content 安全;还需要负向 fixture 覆盖字符串和 Base64 嵌套敏感键。
建议的目标不变量是:
```text
raw content 仅在 MAIN world 短暂存在
→ 输出 text | image | file | unsupported
→ 下游只校验 normalized contract,不再解析 OneTalk raw payload
```
### C4. `content` 是 raw 事实源,还是 normalized 事实源
这不是单靠抓包能够决定的事实冲突,而是架构决策。运行态复核只需要确认现有消费者分别读取了什么;方案确认需要满足:
- 跨层只有一个可写内容事实源。
- 顶层 `message.text` 如需兼容,只能从 `content.kind === "text"` 派生。
- 有效但暂未支持的 card 使用 `kind="unsupported"`,不伪装成文本或文件。
- 非法 Base64、非法 JSON、字段错误和超限数据进入明确 anomaly 或受控失败,不静默变成空文本。
- 合同含义从 raw 改为 normalized 时必须提升协议版本,不能让同一协议版本同时承载两种语义。
### C5. 完整 `sourceUrl` 是否可以持久化和发送给 Mind
**第一次证据**
- 解析图片 `url`、文件 `url/downloadUrl/thumbnailUrl`,只记录 scheme、host、path 后缀、query 键名和是否为空,不记录 query 值。
**第二次证据**
- 分别验证 OneTalk 登录上下文和无登录上下文中的响应状态、跳转次数、最终 host、Cookie 依赖和 CORS;只记录状态与布尔结果。
**定案条件**
- 未确认 URL 生命周期、授权依赖和 query 敏感性前,`sourceUrl` 不能同时被定义为必填字段并承诺跨网络/数据库持久化。
- 如果 URL query 含短期授权或身份值,第一阶段只同步安全元数据和资源 ID;是否增加临时 URL 换取或受控代理另行决策。
- 如果需要实际 GET、重定向跟随或读取二进制,本项必须先获得用户明确同意;默认只允许检查响应元数据。
## 5. 不明确内容的再次确认
| 编号 | 当前不明确内容 | 再确认方法 | 定案所需证据 |
| ---- | -------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------ |
| U01 | 图片/文件 URL 是否依赖 OneTalk Cookie | 登录上下文与干净上下文分别检查响应元数据 | 两种上下文的状态、跳转和 Cookie 依赖结论 |
| U02 | URL 有效期 | 同一脱敏样本在不同时间点检查响应元数据,不记录 URL | 至少能观察有效、过期或续期行为中的一种明确规则 |
| U03 | URL 能否跨 Origin 给 Mind 使用 | 在 Mind 所在 Origin 做只读请求或实际 `<img>`/链接测试 | CORS、Cookie、跳转和 CSP 的真实结果 |
| U04 | `downloadUrl` 为空时 `params.url` 的真实语义 | 页面行为与 SDK/bundle 调用点交叉确认 | 能区分预览、下载、操作入口或临时跳转 |
| U05 | TXT 是否与 PDF 同构 | 获取一条真实入站 TXT 历史样本并按 R12 检查 | `cardType`、params schema 和 SDK 归一化结果 |
| U06 | live 图片/文件 push 是否与历史同构 | 观察真实入站 live 媒体消息,不主动发送 | live envelope 与 history content 的字段级比较 |
| U07 | 群聊媒体是否同构 | 在明确允许的群聊样本上执行只读历史检查 | 群聊 raw content、身份和 participant 结构 |
| U08 | `custom.type`/`cardType` 完整枚举 | 对已有历史样本做类型和 cardType 计数,只输出数字集合 | 至少确认当前账号样本范围,不宣称全局完整枚举 |
| U09 | `confirmed` 是否等于真实持久化 | 将样本 ACK 与 Bright 数据库行只读对应 | `accepted/duplicate` 与数据库事实的区别 |
| U10 | Mind 是否能显示媒体 | 检查真实 history/event 消费和 UI 模型 | 真实渲染结果,不以服务启动或 JSON 可返回代替 |
| U11 | MessagePack live/sync push 的媒体路径 | 捕获真实媒体 MessagePack 样本并与明文历史消息交叉对应 | 数字路径和业务字段一一对应,不靠数组位置猜测 |
| U12 | 图片/文件发送和确认 | 仅在用户明确授权后执行一次受控发送 | 上传结果、最终 sent-direction 消息和三态发送结果 |
U01U04、U06、U07 和 U12 依赖特定运行环境或真实样本;缺少样本时状态必须写为 `blocked_by_sample`,不能写成已验证或默认同构。
## 6. 复核输出格式
每个检查项使用以下固定格式记录:
```md
### Rxx / Cx / Uxx:标题
- 状态:verified | contradicted | not_verified | blocked_by_sample | blocked_by_environment
- 环境:浏览器版本、扩展版本、commit
- 样本别名:T1 / I1 / F1 / C1
- 证据 A:来源边界、字段名、字段类型或布尔结果
- 证据 B:独立来源边界、字段名、字段类型或布尔结果
- 结论:只描述证据能够支持的范围
- 未覆盖:明确列出不能外推的消息类型或场景
```
最终报告必须分别列出:
1. 两份文档共同成立的结论。
2. 经两次独立证据确认的冲突及采用哪一侧。
3. 被新证据推翻的原结论。
4. 尚未确认、缺样本或缺环境的内容。
5. 纯方案决策,不得写成运行态事实的内容。
## 7. 本轮不做
- 不修改两份原调查文档。
- 不创建 Trellis task、PRD 或实现计划。
- 不修改协议、数据库、扩展、Bright 或 Mind 代码。
- 不执行图片/文件发送、上传或二进制下载。
- 不以静态代码检查代替真实运行态结论。