Commit Graph

2 Commits

Author SHA1 Message Date
2b77b17e65 fix(归档): 标已读不得把归档邮件改回 read —— e78888b 的不变量有反向缺口
pi 2026-09-28 复核 e78888b 时指出:EnsureSessionOpen 堵住了「往归档会话里建邮件」,
但 MarkMailRead 能**反向**打破同一个不变量。复核成立。

  POST /api/v1/mail/{id}/read(对归档会话里的一封)
    → GetMailByID(无 status 过滤)→ UserCanAccessSession(只查参与方)
    → UPDATE mails SET status='read' WHERE mail_id=$1   ← 不查 status

实测:mails.status `archived → read`,而 sessions.status 仍是 archived
⇒ 两表分叉,且 readStateFor 返回 'read' 而非 'archived',
**归档语义在行级被抹掉**。已加 TestMarkReadDoesNotUnarchiveMail,
先确认它在修之前转红(不是改完就绿的装饰)。

同族的批量路径 MarkAllInboxRead 本来就有守卫
(`session_id IN (SELECT ... WHERE status <> 'archived')`,markread_test.go:126
断言了它)—— 缺的只有单封这一处,所以这是漏网而非设计如此。修法与批量那条同形。

★ mail_reads 的 INSERT 刻意**不加**守卫:已读是按读者记的事实,
人确实读过,归档不该改写它。行级那列是「全局可见性」的冗余、mail_reads 是
「谁读过」的事实,两者语义不同 —— 一起挡会把事实也丢掉。

读侧清册仍为 repo.go=13:新增的那 1 处命中在注释里(散文里拼了列名字面量),
改写措辞而不改数字 —— 让数字 +1 会给未来新增读取凭空送出 1 格余量。
2026-09-28 11:07:32 +08:00
e78888b756 fix(归档): 两表判据分叉的成因收口 —— TouchSession 不再写 status,建邮件一律拒归档会话
pi 2026-09-28 裁定 §1/§2 认可「判据分叉」这个定性,§4 要求做 1+2,
并把 permission/request 点为第三个复活入口。本轮做 1+2,并补上第四个。

# 缺陷:归档后不可见,判据挂在两张表上

  unreadFor / readStateFor   判邮件行   (repo.go:unreadFor)
  ListInbox / UnreadWorkspaces 判会话行   (repo.go:ListInbox)

两边对同一条已归档线索给出不同答案,而每一边单独看都「是对的」。
分叉由 `TouchSession` 的 `SET status='active'` 与建邮件 INSERT 只写
邮件行共同造成 ⇒ 只要有一个写路径碰会话行而不碰邮件行,半活会话就能被造出来。

# 改法:让不变量由构造保证,而不是逐个入口堵

  · TouchSession 只剩 updated_at —— 它是全库唯一能解除归档的入口
  · EnsureSessionOpen 是 CreateMail / CreatePermissionMail / CreateDecisionMail
    的共同前置(集中一处,新增建邮件函数必须经过它)
  · ErrSessionArchived 与 ErrSessionNotFound 分列:调用方要能分开回话
  · resolveTarget 的 reply_to 分支恢复归档契约(此前绕过别名路径的 404)
  · permission/request 补 SessionOpenFor:存在 + 未归档 + 参与方
  · FindSessionByPlatformID 补 s.status(adopt 路径,pi 未列的第四个入口)

# 判据:写成不变量而不是单点

session_status_invariant_test.go:对任意 session_id,
sessions.status='archived' ⟹ 该会话全部邮件 archived。入口级回归单测仍在,
但它们是说明。已实测把 TouchSession 改回旧实现后该判据转红
(不是「改完就绿」的装饰)。

# 读侧清册

mail_status_readers_test.go 的清册仍为 repo.go=13 / thread.go=1 / migrate.go=4:
本轮新增的 5 处命中全在注释里(散文里拼了列名字面量),已改写措辞而不改数字
—— 让数字变化会给未来新增读取凭空送出 5 格余量,正是那张表要防的事。

# 遗留(pi 裁定本轮不做,已登记)

FindSessionByAddress 无 status 条件:补上会把重复归档从 200 变成 404,
属行为变更,不在 bugfix 里夹带。
2026-09-28 11:01:38 +08:00