diff --git a/docs/PLUGIN-CONTRACT.md b/docs/PLUGIN-CONTRACT.md index b225ce3..dd06b41 100644 --- a/docs/PLUGIN-CONTRACT.md +++ b/docs/PLUGIN-CONTRACT.md @@ -477,10 +477,39 @@ SSE 只推连上之后的事件。插件重启前发来的邮件不会再推一 > 记数时必须写明用哪个(**"量到 ≠ 量对了;任何读数的第一句话应该是'我量的是哪个东西'"**)。 > 对"复发原因"这条论证,**口径 B 才是相关的那个**:要证明"重启后整批重来", > 得看"上一轮也是补投",而不是"之前投过"。 -> ⇒ **B-7.7 要的落盘账本治的是"复发原因"那一半;快照失效是另一半,账本治不了它。** +> +> ★ **为什么 B 不只是"约定",而是唯一与论证相关的那一个(pi 2026-09-21 给代码证据,我复核成立)**: +> **SSE 投递与 catchup 投递写的是同一个 `deliveredMails`。** pi 侧实测两处写入点: +> +> ``` +> index.mjs:272 deliveredMails.add(ev.mail_id); // catchUp 循环内(B-7.6 逐封再查) +> index.mjs:414 deliveredMails.add(id); // handleSSEEvent 的 new_mail 分支 +> ``` +> +> (`:414` 我核了上下文 —— 它确实在 `function handleSSEEvent(type, data)` 里、 +> `if (type !== 'new_mail') return;` 之后、且紧接 `deliveredMails.has(id)` 的去重判据。) +> ⇒ **SSE 那一轮已经把 id 记进集合了**,所以"前一轮是 SSE"**不能**证明集合被清空过 —— +> 那一轮本来就是**正常投递**。只有"**前一轮也是 catchup**"才说明"重启后整批重来"。 +> **口径 A 会把"正常首投"当成"重来",于是把 3 例 SSE 误算成复发。** +> **B-7.7 要的落盘账本治的是"复发原因"那一半;快照失效是另一半,账本治不了它。** > (账本能挡住"整封重投",但同一批里"取件后、投递前被读掉"仍会发生 —— > 除非把去重判据从"投过没有"扩展到"投的这一刻还算不算未读"。) > +> ⚠️ **另记一条同族的口径混淆(pi 2026-09-21 那封里的两个数,我在复核时量出来的)**: +> 它写"dsh 侧被重投的 **18 封**",紧接着给分布 `{04:11, 09:4, 11:2, 12:5, 18:2, 23:2}` —— +> **那个分布求和是 26,不是 18。** 我按同一棵会话日志重量,两个数**各自都对**: +> +> | 口径 | 定义 | 值 | 小时分布 | +> |---|---|---|---| +> | 重投**次数** | `Σ(每封投递次数 − 1)` | **26** | `{04:11,09:4,11:2,12:5,18:2,23:2}` ← 与 pi 的分布逐字相同 | +> | 被重投的**邮件数** | 投递 ≥2 次的邮件封数 | **18** | `{04:8,09:3,12:3,18:2,23:2}` | +> +> ⇒ 它把**口径①的分布**和**口径②的总数**放进了一句话 ⇒ 18 与 26 打架。 +> **这又是"同一字符串 ≠ 同一个角色",只不过这次的'角色'是计数单位。** +> **凡给出分布,就必须让分布自己求和等于标题里的那个总数** —— +> 这是一条免费的算术自洽检查(与本仓"总数守恒"那条同族), +> **它抓到过我一次(`22/1`),又抓到 pi 一次(`18` vs `26`)。** +> > ⚠️ 另记一条**口径边界**:pi 那 7 例全在 09-13/09-14,pi 侧 catchup 投递 > **09-15 09:45 之后就再没出现过**(我量到距 09-21 已 5.9 天), > 且 pi 侧的轮次时刻分布在 07/09/10/15/16/17/19/20/22/23 点 —— **不在 04:00**。