Files
MailUI4Agents/server
JianFeeeee 17908c1623 修复: 已读**权威列**静默少行 —— markReadFor 占位符整体错位一格(最后一封永远标不上)
★ 这是我自己那封"重投"的根因。我先在自己的收件箱上量到症状:
  `read_inbox` 列了 **5 封**,落库只有 **4 封**变成已读,**少的正是最后一封**。
  顺着症状读 `repo.go` 才看到机制,不是先读代码再猜。

## 根因:reader 的占位符与调用方的 `$1` 撞号

`markReadFor` 原把 reader **前置**成 `$1`:

    append([]any{reader}, args...)     // → $1=reader, $2=args[0], …

而两个调用方的 `where` 早把 `$1` 用成了 recipient:

    MarkMailsReadFor          : $1=recipient,$2..$(N+1)=ids,args=[recipient, ids...]
    MarkAllInboxReadForSession : $1=recipient,$2=sessionID,args=[recipient, …]

⇒ 绑定表整体**右移一格**,`IN ($2 … $(N+1))` 实际收到 `(recipient, id1 … idN-1)`:

  · **最后一封永远插不进**(idN 从没被绑定)
  · **只传 1 封时一封都不插**(`$2` = recipient)
  · 带 session 那条 `$2=recipient` 被当成 `session_id` ⇒ 同样一行不插

## ★ 为什么长期零痕迹(比 bug 本身重要)

调用方返回的"标了几封"来自**随后那条 `UPDATE mails`**,它用的是**没被前置**的 args
⇒ **计数正确**、冗余列也正确,**只有权威列 `mail_reads` 静默少行**。
而未读判据是 `mail_reads`(`unreadFor`)⇒ 邮件**看起来已读、实际仍算未读**
⇒ 重启补投(`catchUp`)时被当新信**重投**。

**原有的 4 个测试全都守着这个 bug**:它们断言的是 `statusOf()` —— 读的正是那列
**冗余**。判据读错了列 ⇒ 守的是"看起来对"的值,不是**决定行为**的那个值。

## 实测(探针 + 变异,两边都跑)

    修复前:单封 mail_reads=0(期望 1);传 4 封 → n=4 但只插 3 行(丢第 4 封)
    修复后:单封 mail_reads=1;传 4 封 → 插 4 行;抄送、幂等、MarkAllInbox 各自 1 行

新判据 5 条在**旧实现上变异验证**:4 条红(含指名"漏标第 [5] 封"),
第 5 条「别人的邮件不该留下行」**正确地不红**(鉴权本来就没坏)。

★ **我中途假绿过一次,如实记**:第一版探针只有 `t.Logf` 没有 `t.Errorf`,
变异态 `go test` 仍 **rc=0** —— 读数(0/3 vs 1/4)确实变了,但**判据没断言**。
这正是本仓那条「判据在,但走不到」。改成 `t.Fatalf` 后才真抓住。

## 我另外自伤了一次(同一个判据抓到我)

`mail_status_readers_test.go` 数的是**原始源码**里 `mails.status` 的出现次数(不剥注释)。
我新写的注释里就有一句"…冗余列 `mails.status` 正确",把清册从 **13 撑到 14** ⇒
`TestMailsStatusReadersAreRegistered` 红。改掉那句措辞后回到 13。
⇒ 记一条:**在那个文件里写注释也会被记账**,说明它数的是"字面出现"而非"真读列"。
(我没有改那个判据 —— 它数得紧是有意的,我改的是自己的措辞。)

## 状态

`go test ./...`:`internal/repo` 仍有一条红 `TestStaleLunarRecurringDoesNotFlood`
(「农历日从 30 变成 29」= **日期相关**)。**我把 `repo.go` 还原成 HEAD 版单跑过,
它同样 FAIL** ⇒ 与本次改动无关的既有红,我没动它。
其余 12 个包全 `ok`。新判据 5/5、原有 `TestMarkMailsReadFor*` 4/4。
2026-09-21 04:25:17 +08:00
..