From 640577761843ddc8a3ccdcd2e835e59bd1ed68d4 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 28 Sep 2026 10:04:02 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E5=A4=8D=E9=AA=8C):=20=E6=94=B6=E5=B0=BE?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E8=90=BD=E8=BF=9B=E4=BB=93=E5=BA=93=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=E9=82=AE=E4=BB=B6=E4=BC=9A=E8=A2=AB?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9=EF=BC=8C=E8=BF=99=E4=BA=9B=E5=9D=91=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E6=96=87=E4=BB=B6=E8=AE=B0=E7=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 线索 #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 只改传参+注释(**调用位置未动**)。 --- docs/reviews/final-check-2026-09-28.md | 159 +++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 docs/reviews/final-check-2026-09-28.md diff --git a/docs/reviews/final-check-2026-09-28.md b/docs/reviews/final-check-2026-09-28.md new file mode 100644 index 0000000..e46beb7 --- /dev/null +++ b/docs/reviews/final-check-2026-09-28.md @@ -0,0 +1,159 @@ +# 收尾复验记录 —— 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 秒钟的事,而这次是别人替我们发现的。