# 账户消息时间线

[客户端—中继协议](../README.md) · [消息投递](message-delivery.md) · [消息方法](../methods/messaging.md)

## 时间线模型

账户在同一中继上使用同一条只追加的消息时间线；不同中继的时间线相互独立。当前归属中继向该账户的本地时间线追加以下记录：

- 该账户收到的每个 `MessageEnvelope`；
- 该账户通过 `message.send` 发出且提供了 `sender_boxes` 的消息。

每条记录保存原信封和时间线所属账户的密钥盒。

中继为账户消息时间线分配非负 `sequence`，并永久保留该账户在本中继曾经分配的最高序号。后续分配的 sequence 必须大于该值，不要求连续。消息清理和中继重启不得使最高序号减小或重置。时间线头指该最高序号；尚未分配过序号时为 `-1`。

sequence 表示账户在本中继上的记录位置，不在不同账户或不同中继之间比较。历史缺口位置也以账户在对应中继上的时间线为范围。

时间线只负责保留期内的跨设备同步，不是永久聊天存储。客户端持久化的消息和协议状态才是长期记录。

客户端在所有中继之间按信封的 `(from, message_id)` 去重。首次接受的消息必须通过验证；此后收到相同逻辑消息键的记录时直接忽略，不得覆盖已接受内容或重复应用。

## `MessageTimelineEntry`

`MessageTimelineEntry` 是同步响应中的一条可见记录。

| 字段 | 类型 | 必需 | 语义与约束 |
|---|---|---|---|
| `sequence` | integer | 是 | 账户时间线位置；非负并严格递增 |
| `envelope` | MessageEnvelope | 是 | 中继接受的原始消息信封 |
| `key_box` | MessageKeyBox | 是 | 为当前设备封装原消息内容密钥的密钥盒 |
| `accepted_at` | integer | 是 | 中继接受该记录的 Unix 秒 |

时间线所属账户必须是原信封的发送账户或接收账户。发送账户与接收账户不同时，客户端根据记录中信封的发送账户和目标账户判断方向；两者相同时是账户自身投递。

## 设备可见范围

一个入站 `MessageEnvelope` 在目标账户时间线中只占一个 sequence。中继在接受时确定该记录的设备可见范围：本账户密钥盒中当时有效的设备获准同步这一项；未知、尚未生效、已移除、已过期或已撤销的设备不能读取该项。

至少一台目标设备有效时，中继接受整条消息并按该范围提供同步；全部目标设备都不可用时，整个投递失败且不追加时间线。

发件记录只对本账户密钥盒中当时有效的设备可见。

同一设备 ID 被移除后又重新启用时，既有记录的设备可见范围保持不变，历史缺口的判定也不重置。

sequence 的间隔可能来自未分配的序号或设备不可见的记录，不得仅凭数值间隔判断历史丢失；历史缺口按[消息保留与历史缺口](#消息保留与历史缺口)的规则判定。

## 消息保留与历史缺口

每条记录的最低保留截止时间为 `accepted_at` 加中继接受该记录时的 `message_retention`，后续配置调整不得缩短该期限。当前配置通过 [`relay.info`](../methods/relay-information.md#relayinfo) 的 `limits` 返回。投递结果的保留期见[投递结果保留规则](message-delivery.md#投递结果保留规则)。

中继清理过期记录后，仍必须能够按设备可见范围和请求位置正确判断历史缺口。设备请求的位置之后至少存在一条原本对其可见、但完整内容已经过期的记录时，同步响应必须包含 `has_retention_gap: true`。未面向当前设备或接受时对应设备不可用的记录，不形成该设备的历史缺口。

## 消息时间线处理流程

客户端必须按 [`message.timeline.sync`](../methods/messaging.md#messagetimelinesync) 的响应约束验证本页 `certificates` 数组，并按 [`MessageEnvelope`](../core-objects/messages-and-content.md#messageenvelope) 的规则验证记录中的信封。随后确认 `key_box.device_id` 与当前设备 ID 一致，并使用该 `key_box` 按[端到端加密构造](../core-objects/messages-and-content.md#端到端加密构造)解封内容密钥并认证解密 payload。

应用来自其他账户的业务内容前，还必须验证发送关系或业务对象自身携带的授权材料；发送到其他账户的记录必须确认发送账户是本账户。账户自身投递必须确认信封两端都是本账户。

同一业务的状态变更必须按对应中继的 sequence 顺序生效。未知业务 `$type` 可以保存或忽略；无效或未通过授权验证的业务内容不得应用。

同步位置按账户和中继区分，作为 [`message.timeline.sync`](../methods/messaging.md#messagetimelinesync) 的 `after` 参数。响应通过验证后，客户端可以使用该页最大的 sequence 继续同步；尚未开始同步时，使用 `-1` 或省略 `after`。空页不改变同步位置。
