Files
MailUI4Agents/docs/reviews/final-check-2026-09-28.md
JianFeeeee f9193e9e39 docs(复验): 记录头部别把快照 SHA 钉成「最终」—— 重复了我自己 §4 的坑
自查发现:这份记录 §4 第一条写的正是「**不要把 SHA 钉进判据文件**」,
而头部却写「最终 HEAD:`35557d4`」—— 下一个提交一发生它就过期,
而读的人会当成当前状态。

这类判据错的成因是「写的人顺手把当时的值写死」,所以记录自己也得守同一条:
头部改成显式快照 + 现查命令。

历史叙述里出现的 SHA(§2 提交表、§3 时间线)**保留原样** ——
那些是「某提交做了某事」的史实,不是当前状态声明,改成相对引用反而失真。
2026-09-28 10:04:57 +08:00

167 lines
8.3 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)
本记录写完时的状态(**下面的 SHA 是快照,不是「最终」** —— 要看当前值请
`git rev-parse HEAD` 与 `go version -m <部署的二进制> | grep vcs.revision` 现查,
理由见 §4 第一条):
| 快照(2026-09-28 写记录时) | 值 |
|---|---|
| 工作树 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 秒钟的事,而这次是别人替我们发现的。