-- 0013_chat_client_msg_id: 客户端消息幂等键(Android 协同 §G4)。 -- -- 背景:chat 全链路此前**没有幂等** —— 同一段文本发两次就是两条用户消息、 -- 两个回合(history.insert 每次都新铸 id)。弱网/切后台下的客户端超时重发 -- 因此既留下重复气泡,也白烧一次模型调用。 -- -- client_msg_id 是**客户端生成**的去重键(例如一条乐观气泡的本地 id)。 -- 服务端据此在同一会话内只承认第一条:重复到达返回既有消息,不再落库、 -- 不再起回合。它**可选**:'' 意味着「这个客户端不带幂等键」,行为与加入本列 -- 之前逐字相同(老客户端零影响,历史行也全是 '')。 -- -- 判重由**唯一索引原子裁决**,而不是「先查再插」——后者是两个并发重发都 -- 查不到、都插入的经典竞态: -- (session_id, client_msg_id) 上建**部分**唯一索引,只覆盖 -- client_msg_id != '' 的行。部分索引是必须的:若约束覆盖全表,所有空值行 -- 会在同一会话里互相冲突,等于禁止一个会话出现第二条消息。空值行因此 -- 完全不参与约束。 -- -- 0003 的 chat_messages 表在此已存在,故 ALTER ADD COLUMN 是非破坏性的正确 -- 一步;全新库由 internal/runtime/history.NewStore 幂等地前滚(含该列与该 -- 索引)。索引命名沿用 idx_{表}_{列} 口径,表名复数(援引 AGENTS §10 已登记 -- 的例外)。 ALTER TABLE chat_messages ADD COLUMN client_msg_id TEXT NOT NULL DEFAULT ''; CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_messages_client_msg_id ON chat_messages (session_id, client_msg_id) WHERE client_msg_id != '';