Commit Graph

2 Commits

Author SHA1 Message Date
5753169077 docs(test): 去掉 permission.go:112 这个**同一提交内就失效**的行号引用(pi 2026-09-25)
pi 那条"引用位置要用**唯一标识**,行号只作辅助"我收 —— 而它在**我自己的提交**里
就有一个现成的反例:

    34a15dc^  : permission.go **112** = Error(w, …, "Invalid session_id")   ← 引用写下时是对的
    34a15dc   : 同一提交给它**前面**插了 16 行注释 ⇒ 真调用移到 **128**
                (而新注释自己占了 113 行,所以 112 现在落在注释文本里)
    HEAD      : 112 = 注释文本;真调用 = 128、另一处提到它的注释 = 113

⇒ **引用与使它失效的改动在同一个提交里** —— 这不是"时间久了漂移",
  是"写下时就错了",而且**任何 review 都看不出来**(数字看着很具体、很像核过)。
  判据也抓不到:112/301/39 全都在文件行数界内(弱形式"越界检查"无效)。

改法(按 pi 的写法):唯一标识用**函数名 + 错误字符串字面量**
(`RequestPermission` + `Error(w, http.StatusBadRequest, "Invalid session_id")`),
行号删除;并把"为什么故意不写行号"记在注释里,免得下一个人"顺手补回去"。

★ 顺带核实(**只核实、未改动**)另一处同类引用:
  `docs/HARMONY-ALIGN-PLAN.md:225` 引 `permission.go:301`,声称是 `DecidePermission`。
  实测: e07e3bf(引用写下时)301 = 该函数的文档注释、函数体在 302;
        现在函数体在 **318** ⇒ 该行号也已漂移 17 行,落在 `RequestPermission` 里。
  未改它(属于另一个 writer 的文档,且是否要改成"唯一标识"由那条线决定)。

验证: `go test ./internal/handler/` 通过。
2026-09-25 06:35:53 +08:00
34a15dc09c fix(网关): 占住幂等键后的早退路径没退键 —— 永久占键、重试永远 duplicate
## 缺陷

`ClaimRelay` 成功之后、邮件落库之前有若干条早退路径,只有**部分**在 return 前
调了 `ReleaseRelay`。漏掉的那些会把 (agent_name, relay_key) 永久占住:
之后同一上游消息的重试拿到 `ErrRelayDuplicate` ⇒ 返回 200 `duplicate_relay`
(文案是「该上游消息已转发过,本次调用未产生新邮件」),
而那条消息**从未发出去** —— 响应在陈述一件没发生的事。

野外实例(现库仍在,`relayed_mails` 唯一一行 `mail_id IS NULL`):

    agent_name = zcode
    relay_key  = no-such-session-0000:toolu-nohuman-1789193173578
    created_at = 2026-09-12 06:06:13

它的 session 位不是 UUID,于是 `RequestPermission` 走到
`uuid.Parse` 失败那条 `Invalid session_id`(permission.go:112)直接 return,
没有退键。该行从 09-12 留到现在。

`mail.go` 同类:`ClaimRelay`(:396)之后 `ConsumeSessionBudget` 返回
非「额度耗尽」错误时(:433)也没退键。

## 修法

不在各 return 前逐个补 `ReleaseRelay` —— 那正是缺陷的成因(漏一处就是一个静默的
永久占键,而漏掉的那处不会有任何报错)。改为占键成功后立刻 `defer` 一次释放,
让"还键"成为该作用域的唯一出口不变量。

成功路径不需要额外开关:`ReleaseRelay` 的 WHERE 含 `mail_id IS NULL`,
`BindRelayMail` 一旦成功该行就不再匹配,defer 自然什么都不做。

## 判据(两条独立用例,缺一即放过一类坏实现)

`TestRequestPermissionReleasesRelayKeyOnEarlyExit` —— 失败路径:早退后同一个
relay_key 必须还能再次占用(键被还回来了)。

`TestRequestPermissionClaimsRelayKeyOnSuccess` —— 成功路径:同一询问重复投递
必须被幂等键挡住,且键确实绑定到了第一封邮件上。

两条必须分开:早退时"正确实现"与"把 ClaimRelay 换成空操作"的坏实现在表里
**观测等价**(都留下一个空键),单条用例无法同时钉住"该退的时候退"和
"该占的时候占"。已逐个变异验证:

    基线(缺陷代码)  → 早退用例红、成功用例绿
    早退处补 Release  → 两条全绿
    ClaimRelay 置空   → 成功用例红(早退用例仍绿,符合预期)
    BindRelayMail 去掉 → 两条全红

`go test ./...` 13 包全绿;handler 组 -race 通过;gofmt/vet 干净。
2026-09-25 05:41:57 +08:00