改: 判据收洞(机器回信不算"回过")+ 写清 T-12 的语义空白;★ 记我自己的量错两处
pi 指出我那条 `收件人回过 ⇒ 必然读过` 有一个洞。**逐条复核成立**,已改。
## 一★ 洞:**机器回信**也被算成了"回过"
"回"只有在**是模型的产物**时才蕴含"读过"。桥有**自动**回信路径 ——
模型一次都没跑起来时,桥代它回一封 `处理失败: <父主题>`:
pi src/worker.mjs:619 / :711
zcode src/index.mjs:300
dsh src/index.ts:1215 / :1719 ← dsh 侧也有,pi 只列了 3 处
⇒ **那封"回信"恰恰是"没读过"的证据。**
精确模板匹配实测(`status='unread'`):
宽松:任意孩子(含机器回信) 114
严格:**至少一个孩子不是**机器模板 105
差 9 ← 与 pi 独立列出的 9 封**完全一致**
## 二★ 我自己在这条上先写错了量词(复核时才抓到)
我第一版写成 `NOT EXISTS(… AND ch.subject NOT LIKE '处理失败:%')` ——
**那问的是"一个真回信都没有",是反向量词**,实测只剩 **9 封**(正好是那批机器信)。
**判据从 105 翻成 9,照样返回行、照样不报错 —— 静默答错。**
正确写法是**带模板排除的存在量词**(已写进 docs 的 SQL)。
★ 又一次"先写结论、后复核",顺序反了。另记"并存"2 封(真回信+机器通知,均 `status=read`)
说明**不能用"父信含机器孩子就排除"的粗写法**。
## 三、T-12 的语义空白已写进契约(四个桥逐个量过)
`read_mail` 是否产生"已读",契约沉默。**四个桥各只有 1 处 `/mail/read`,且四处都只在 `read_inbox` 里**:
dsh src/index.ts:1340 read_inbox(:1302)
pi src/tools.mjs:188 read_inbox(:159)
zcode lib/tools.mjs:155 read_inbox(:120)
opencode index.js:291 —
而 `read_mail` 实现体只有 `client.get(...)` ⇒ **"只取正文、不改状态"是各桥一致的设计意图**。
⇒ 写进契约:**`read_mail` 不产生已读状态 ⇒ 未读计数不等于"没人读过"**;
治法是把语义写进契约,**不是多回填几行**((a) 本来就没有权威记录,补只是猜)。
## 四★ 记我自己的量错两处(同一形状,连错两次)
做上面那张四桥表时,我**两次**把"grep 返回空"读成了"没有":
1. opencode:grep 了 `opencode-mail-bridge/src/` —— **该目录不存在**,源码在包根 `index.js`。
2. zcode:grep 了 `zcode-mail-bridge/src/`(存在,但标已读那行在 `lib/tools.mjs`)。
**`grep` 对不存在的目录不报错、只返回空** ⇒ "路径写错"与"真的没有"**读数完全相同**。
⇒ **数一个东西"有几处"之前,先确认搜索路径存在、且覆盖所有落点。**
(第二次之所以抓到,是因为我改完表**回去逐处 `sed -n '<n>p'` 对行号** ——
只按 `grep -c` 收工,这张表就会带着两个"0 处"进仓库。)
★ 另修一处**漂移的绝对数**:严格口径下"会话未归档"是 **74**(我先前写 83,是旧口径的残留)。
已在 docs 注明该数会随我们的往来漂移,**只当量级、不当阈值**。
This commit is contained in:
@ -786,6 +786,52 @@ SSE 只推连上之后的事件。插件重启前发来的邮件不会再推一
|
||||
| **T-12** | `read_mail` | SHOULD | 读一封邮件的完整内容(含参与方地址与 reply_address) |
|
||||
| **T-13** | `propose_alias`(T-2 参数) | MAY | 模型在 `send_mail` 的 `propose_alias` 字段提议改名 |
|
||||
|
||||
> ★★ **T-12 的语义空白:`read_mail` 是否产生"已读"状态,契约没写**(2026-09-21 定)。
|
||||
>
|
||||
> 这不是猜测 —— **四个桥里逐个量过**,`/mail/read` 这个唯一的标已读端点:
|
||||
>
|
||||
> | 桥 | 源码位置(⚠️ 各不相同) | `/mail/read` | 所属工具 |
|
||||
> |---|---|---|---|
|
||||
> | `dsh-mail-bridge` | `src/index.ts` | **1 处**(`:1340`) | `read_inbox`(`:1302`) |
|
||||
> | `pi-mail-bridge` | `src/tools.mjs` | **1 处**(`:188`) | `read_inbox`(`:159`) |
|
||||
> | `opencode-mail-bridge` | `index.js`(**不在 `src/`**) | **1 处**(`:291`) | — |
|
||||
> | `zcode-mail-bridge` | `lib/tools.mjs`(**不在 `src/`**) | **1 处**(`:155`) | `read_inbox`(`:120`) |
|
||||
>
|
||||
> ⇒ **四个桥各自的路径都不同**,而四处都**只**在 `read_inbox` 里标已读。
|
||||
>
|
||||
> ⚠️ **我在这张表上连错两次,两次都是同一个形状 —— "路径写错 ⇒ grep 返回空 ⇒ 读成没有"**:
|
||||
>
|
||||
> 1. opencode:我 grep 了 `opencode-mail-bridge/src/` —— **该目录不存在**,
|
||||
> 而它的源码在**包根的 `index.js`** ⇒ 我把"没找到"写成了"0 处"。
|
||||
> 2. zcode:我 grep 了 `zcode-mail-bridge/src/`(存在,但只有 `index/prompt/turn-mode/zcode-run`)
|
||||
> —— **标已读那行在 `lib/tools.mjs`** ⇒ 我又写成"0 处"。
|
||||
>
|
||||
> **`grep` 对不存在的目录不报错、只返回空** ——
|
||||
> 于是"路径写错"与"真的没有"**读数完全相同**。这正是本仓那条
|
||||
> **"判据在,但走不到"**的同族:判据(`grep`)没问题,**它压根没走到该走的地方**。
|
||||
> ⇒ **数一个东西"有几处"之前,先确认搜索路径存在、且覆盖了所有可能的落点。**
|
||||
> (我第二次之所以抓到,是因为我改完表格**回去逐处 `sed -n '<n>p'` 对行号**;
|
||||
> 只按第一次的 `grep -c` 收工,这张表就会带着两个"0 处"进仓库。)
|
||||
>
|
||||
> 而 `read_mail` 的实现体(dsh `:1523`,pi 同形)**只有 `client.get('/agent/mail/<id>')`**,
|
||||
> **没有任何标已读的调用**。⇒ **"只取正文、不改状态"是各桥一致的设计意图,不是某个桥的疏忽。**
|
||||
>
|
||||
> 由此得到两条必须写进契约的话(pi 提议、我复核同意):
|
||||
>
|
||||
> 1. **`read_mail` 不产生已读状态。**
|
||||
> 2. ⇒ **未读计数不等于"没人读过"** —— 一个 Agent 完全可以读完正文、处理完、
|
||||
> 甚至回了信,而系统里它仍是 `unread`。
|
||||
> (实测后果见 `docs/API.md`:`status='unread'` 却"收件人回过"的信有 **105** 封,
|
||||
> 且其中 **74** 封的会话未归档 ⇒ 会出现在 `?status=unread`、被 `catchUp` 反复重投。)
|
||||
> ⚠️ 这个数**会漂移**(我们自己的往来就在改它,实测同日 113→116→105) ⇒ 只当**量级**用,
|
||||
> 别当验收阈值。
|
||||
>
|
||||
> ⇒ **治法是把语义写进契约,不是"多回填几行"。**
|
||||
> 回填只清"冗余列与权威列之间的差"(成因 (b));而 (a) 这一类
|
||||
> **在系统里本来就没有权威记录** —— 没有记录可回填,任何"补"都只是在猜。
|
||||
> ⚠️ 因此**不要**用"收件人回过"去批量标已读:那条判据是**充分**条件(还有 9 封是机器回信、
|
||||
> 不蕴含读过),把它当**必要**条件用就会把没读的标成已读。
|
||||
|
||||
> **为什么 T-8 到 T-12 都是 SHOULD/MAY 而不是 MUST**:它们解决的是静默投递错误
|
||||
> (`suggest_address`)和信息不足(`list_contacts` / `read_mail` / `read_thread`),
|
||||
> 但都不影响主链路(收信 → 起会话 → 自动回信)。一个最小可行插件只注册 T-1 和 T-2
|
||||
|
||||
Reference in New Issue
Block a user