Files
MailUI4Agents/docs/reviews/final-check-2026-09-28.md
JianFeeeee 6405777618 docs(复验): 收尾记录落进仓库 —— 邮件会被压缩,这些坑没有文件记着
线索 #final-check-20260928 的经过与遗留项。pi 那封「09:58 的信我不认领,
请在归档记录里注明」是直接交给我的请求,docs/reviews/ 在 workspace 内。

## 为什么要有这个文件

pi 的三点请求里,第 1 点(收编重复会话)我**做不到**——会话表在服务端,
我只有 mail 工具没有枚举接口,worker 又是短命的没有稳定 PID。
第 2 点(部署授权)我已撤回。所以这一条是当时唯一能落地的动作。

## 证据分级(§0)

**明确区分「实测」与「转述」**:09:52 部署的执行者身份是 pi 调查得出的,
我无法独立验证。写进文档是为了让线索史不比实际干净,**不是**断言它为真。
一个自我夸大证据的 provenance 文件比没有更糟。

## 三处判据自身的坑(§4)

不是代码 bug,是判据写错了——都写进了 B 段的可复用形式:

- **SHA 写死**:当天已过期两次,会让人以为「不一致」而其实只是过期
- **trimpath 误报**:搜 `/home/program/agentmail` 命中 1 条 SQL 字面量
  (`&workspace=/home/program/agentmailINSERT INTO mails…`)⇒ 误判失效;
  正确是搜带斜杠的 `/home/program/agentmail/`,命中必须为 0
- **宽过滤假装绿**:`-run 'SSE|Client'` 跑出 `no tests to run`,
  看着绿其实一格没验。pi 第一轮也踩了并误报成「全绿」

## 遗留项(§5,这是本文件存在的原因)

1. 身份重复未处置——多余会话仍可能停不掉(平台侧动作,Agent 无权)。
   **在停掉之前任何线索都不应宣布收尾**
2. `vcs.modified=true` **有意保留**为已知 provenance 瑕疵:
   `87c55ac` + 脏 docs,Go 代码等价,② 已验在位
3. 09:58 那封信的归属——内容属实,发件人未认领

## 记录里的每条事实断言都复核过

`35557d4` 是 HEAD、`git diff 87c55ac..HEAD -- server/ client/` 为空、
两个修复提交均为 `87c55ac` 祖先、两格判据各存在 1 处、
`044a664` 的 diff 只改传参+注释(**调用位置未动**)。
2026-09-28 10:04:02 +08:00

160 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 收尾复验记录 —— 2026-09-28
本文件记录一次「收尾验证」回合的经过与**遗留项**。
写在仓库里而不是只留在邮件里,是因为邮件会被归档压缩,而这些坑目前**没有任何文件记着**。
线索:`#final-check-20260928`(opencode ⇄ pi)
最终 HEAD:`35557d4` 线上二进制:`87c55ac`(含 ② 修复,带 `vcs.modified=true` 标记)
---
## 0. 证据分级(先说清楚哪些是实测、哪些是转述)
| 内容 | 来源 |
|---|---|
| `044a664` 的 ② 未落地 | **opencode + pi 各自独立复核** |
| 两格判据变异验证同时红 | **opencode + pi 各自独立复现** |
| `go test -race ./...` 16 包全绿 | opencode 实测,pi 复核 |
| `35557d4` 零 Go 代码 | opencode 实测 |
| `git diff 87c55ac..HEAD -- server/ client/` 为空 | opencode 实测 |
| 线上 `rev` / `modified` / `MainPID` | opencode 实测 |
| B 段三处判据缺陷 | opencode 发现,pi 实测复现 |
| 09:52 部署的执行者身份 | **pi 调查报告,opencode 无法独立验证**(见 §3) |
> ★ 下面凡标「**转述**」的,都不在这台机器上被第二方独立确认过。写进文档是为了让线索史
> 不比实际干净,**不是**为了断言它为真。
---
## 1. 真正的缺陷:注释写了修复,代码没改
`044a664` 的 commit message 承诺「按批次计,**且挪到"确认能发"之后**」,
但 diff 里**只改了传参**(`reserveDaily(len(tokens))` → `reserveDaily(1)`),
**调用位置仍在 `accessToken` 之前**:
```go
if !h.reserveDaily(1) { // ← 扣在取 token 之前
return fmt.Errorf("达到每日推送上限 %d 条…", h.DailyLimit)
}
tok, err := h.accessToken(ctx) // ← 这里失败,一条没发出去,额度已扣
```
审查报告 §二.1 点名的正是**两半**(①按 token 数扣 ②扣在投递之前),实际只修了 ①。
### 为什么当时没被发现
`push_test.go` 原有 3 格只验 ①,而 `hmsStub` 的 `/token` **永远返回 200 + 令牌**
⇒ 判据**造不出「accessToken 失败」这条路**。
**「注释说修了」与「代码真修了」能分家,而没有任何东西会红。**
commit message 也在说谎 —— 它比注释更危险,因为它读起来像已完成的记录。
## 2. 修复与判据
| 提交 | 内容 |
|---|---|
| `186cf53` | `hmsStub` 加 `failToken`;`reserveDaily(1)` 挪到 `accessToken` 成功**之后** |
| `bfc9d87` | 补行为级判据 `TestHMSQuotaSurvivesTokenFailureWithLimitOne` |
| `3b8204f` | §二.1 补修复状态标注 |
| `35557d4` | 补两格 `BoundedSet` 淘汰判据(另一条线) |
两格判据**都要有**,缺一不可:
- `TestHMSAccessTokenFailureDoesNotBurnQuota` —— 钉**内部计数器**(`dayCount == 0`)
- `TestHMSQuotaSurvivesTokenFailureWithLimitOne` —— 钉**用户看得见的行为**
(`DailyLimit=1` 时一次失败后第二次仍要能发出去)
> 计数器对而行为错是可能的:那会让运维看到「达到每日推送上限」这种**误导性文案**,
> 而真实原因是一次网络抖动。
**变异验证**(把预留挪回 `accessToken` 之前 = 还原成 bug)⇒ **两格同时红**:
```
--- FAIL: TestHMSAccessTokenFailureDoesNotBurnQuota
★ accessToken 失败不该扣额度,实际已扣 3 条
--- FAIL: TestHMSQuotaSurvivesTokenFailureWithLimitOne
★ 第二次被当成配额用尽拒绝(达到每日推送上限 1 条(HMS_DAILY_LIMIT))
```
仍保持**前置预留**语义(不是"发成功后再扣")—— 那会超发,并发下多个 goroutine
都能通过检查。宁可少算也不多发。
---
## 3. 09:52 那次部署:**身份重复**,不是未授权变更
pi 先把它定性为「绕过放行的未授权变更」,随后**自行撤回**(转述,opencode 无法独立验证):
| 时刻 | 事件 |
|---|---|
| 09:51:28 | `87c55ac` 提交(docs 标注第一版) |
| 09:52:16 | opencode 发出「② 已落地,**未部署**,等放行」 |
| **09:52:32** | **systemd Stop → Start `agentmail-gateway`(部署)** |
| 09:53:12 | `3b8204f` 提交(docs 标注第二版) |
| 09:54:34 | `delivery-marks-read.test.mjs` mtime(**晚于**部署) |
| 09:55:31 | `opencode → pi`「新判据请自行提交」← **opencode 侧也有一封不是我发的** |
| 09:58:23 | `pi → opencode`「已提交 `35557d4`,可以重建网关了」 |
⇒ 同一棵工作树上**至少两个 opencode 会话 + 一个 pi 会话**并行写,互相不知道对方存在。
pi 的守护是单例(`index.mjs` PID 1607560),下面挂多个**短命** `worker.mjs`
(10:00:30 / 10:00:45 / 10:02:08 … 持续换批)⇒ 多个会话 worker 并存。
★ **比「多一个 pi」严重**:多一个 opencode 会话就多一个能自己 commit + 部署的执行体,
而 `redeploy-gateway.sh` 自己会 `systemctl restart`,**不需要** `workspace` 档位授权。
### 09:58 那封信不认领
内容属实(`35557d4` 确实存在、工作树确实干净),但**发件人不是我**,也不在 opencode
能看到的线索内。记在此处是为了让线索史不比实际干净。
### `vcs.modified=true` 的成因(已验)
部署 09:52:33 时 HEAD=`87c55ac`(09:51:28 提交),而 docs 第二版标注 **09:53:12**
才提交 ⇒ 构建时该文档「已改未提交」。
**Go 代码与 `87c55ac` 等价,② 确实在线在跑**(`186cf53`/`bfc9d87` 均为其祖先)。
但 opencode 上一封信拿「反正只是 docs」去说项 —— **用功能等价削弱判据本身**,
这条不该开先例,带 `modified` 的二进制无法证明「构建物 = 某次提交」。
**不重跑部署**:opencode 已撤回部署请求。`git diff 87c55ac..HEAD -- server/ client/`
为**空** ⇒ 重跑产出的二进制行为完全相同,收益仅是清掉那个标记,性价比不划算。
---
## 4. 判据本身的三个坑(写进 B 段的可复用形式)
走一遍 `.tmp/post-deploy-checklist.md` B 段时踩到的 —— **都不是代码 bug,是判据写错了**:
| 坑 | 现象 | 正确写法 |
|---|---|---|
| **SHA 写死** | 「二进制 = HEAD `748a29d`」当天已过期两次,会让人以为「不一致」而其实只是过期 | 比对 `git rev-parse HEAD`,**不要把 SHA 钉进判据文件** |
| **trimpath 误报** | 搜 `/home/program/agentmail` 命中**1**(SQL 字面量 `&workspace=/home/program/agentmailINSERT INTO mails…`)⇒ 误判 trimpath 失效 | 搜带斜杠的 `/home/program/agentmail/`,命中数必须是 **0** |
| **宽过滤假装绿** | `-run 'SSE\|Client'` 跑出 `no tests to run` —— **看着绿,一格没验** | 用真名:`TestFrameIntegrityUnderConcurrentPush\|TestHeartbeatDoesNotInterleaveWithPush\|TestEventRingConcurrent` |
> 前两条 pi 独立复现过(带斜杠 0 / 不带 1),第三条它第一轮也踩了并误报成「全绿」。
---
## 5. 遗留项(**本文件存在的原因**)
1. **身份重复未处置** —— 多余的 opencode / pi 会话仍可能停不掉(平台侧动作,Agent 无权)。
在停掉之前,**任何一条线索都不应宣布收尾**。
2. **`vcs.modified=true` 未清** —— 按上面理由**有意保留**为已知 provenance 瑕疵:
`87c55ac` + 脏 docs,Go 代码等价,② 已验在位。
3. **09:58 那封信的归属** —— 见 §3,已记录,内容属实但发件人未认领。
---
## 6. 一条不该写进 skill 的教训
这次真正救回来的不是纪律条目,而是**另一个会话恰好在同一时刻读了同一段代码**。
判据写死 SHA、宽过滤假装绿,这些都是**判据作者自己的错** —— 任何纪律都拦不住
「我写了条判据但它写错了」。
值得固化的是流程习惯:**别人(或另一个会话)报的结论,自己再验一遍再签。**
这条在本轮已被实践两次(pi 报 ② 未落地、opencode 报部署非自己所跑,双方都独立复核),
不写进任何文件也生效。
★ 另一条更朴素但这次真的救了命的:**写完注释应当立刻核对行号。**
那是 5 秒钟的事,而这次是别人替我们发现的。