diff --git a/docs/DEBTS.json b/docs/DEBTS.json index ab25be7..38e55bc 100644 --- a/docs/DEBTS.json +++ b/docs/DEBTS.json @@ -398,7 +398,7 @@ "due": "有人再动 ClaimRelay/ReleaseRelay 的早退路径时(新增 return 分支必须确认 defer 仍覆盖它);或 M 长期非 0 时(先查有没有未修完的早退,别当瞬时量读)", "where": "server/internal/handler/permission.go:109,118(defer 释放,修法本体)· server/internal/handler/mail.go(budget 早退)· relayed_mails 表(mail_id IS NULL = 占位行)· 线上二进制内嵌 b0c87192(含修复)", "kind": "**曾真实泄漏、现已修复并上线,但证据只有 1 小时窗口**:`mail_id IS NULL` 占位行会白占幂等键、挡住后续重试。修复(改用 defer 挂作用域出口)已在生产二进制内;上线后新增 0 行,但样本不足,不足以宣布彻底关闭。库里仍有 1 行修前化石(09-12)", - "note": "★★★ 2026-09-30 登记。**这条结论此前只存在于邮件里(2026-09-25 05:57 报给 pi 的那封),没有落进仓库** —— 与已有的 `commit-as-reply-is-not-a-reply` 同族。本轮把它**复核 + 闭环**,转成可判记录。\n\n## 缺陷形状(修前)\n\n两条早退路径会在 `ClaimRelay` 之后、`CreateMail` 之前退出,于是\n`relayed_mails` 落下**占位行(`mail_id IS NULL`)**:**幂等键被白占、hop 不变、后续重试被挡**。\n\n```\nserver/internal/handler/permission.go session_id 非 UUID 的早退\nserver/internal/handler/mail.go \"Failed to check session budget\"(budget 非耗尽就报错)\n⇒ 二者当时**都编在生产二进制里**(2026-09-25 实测:两个字符串各命中 1 处,\n 当时线上二进制 mtime 09-19 13:04、进程 09-20 04:01:54 启动)\n```\n\n## 现在:已修复、已上线、已验证(本轮实测)\n\n```\n修复提交: 7589f0a →(filter-repo 重写后)**34a15dc** 引入 permission.go 的 defer 释放\n 90e5cef →(重写后)**6e4bcd66**\n★ 两个都**是线上二进制的祖先**:\n 线上 /opt/agentmail/agentmail-gateway 内嵌 revision=**b0c87192**(进程 09-30 17:30:56 启动)\n git merge-base --is-ancestor 34a15dc b0c87192 ⇒ **true**\n git merge-base --is-ancestor 6e4bcd66 b0c87192 ⇒ **true**\n⇒ 线上跑的就是含修复的那份 ⇒ 缺陷**已不活着**\n```\n修法形状(值得留档,因为它是\"改**释放时机**\"而不是\"补**每个 return**\"):\n```\npermission.go:109 「用 defer 而不是在各 return 前逐个补 ReleaseRelay:这条路上有多处早退」\npermission.go:118 defer func() { ... }()\n```\n⇒ ★ 这是 ⑬ 一族的反例读法:**当一条路径有\"多处早退\"时,逐处补释放是必漏的**;\n 正确的形状是**把释放挂到作用域出口**,让\"漏\"在语言层面不可能发生。\n\n## 可判验收(本轮跑的)\n\n```\nM = relayed_mails 中 mail_id IS NULL 的行数 = **1**\n 唯一那行: relay_key=`no-such-session-0000:toolu-nohuman-1789193173578`\n created_at=**2026-09-12 06:06:13**(修前 12 天的化石)\nN = 已绑定行数 = **592**(N/(N+M) = 99.8%)\n上线后(> 2026-09-30 09:30:56)新建的占位行 = **0**\n⇒ 与「修后占位行**严格瞬时**、请求返回前必释放」一致\n```\n★ **读法(M 的语义随修复而变)** —— 这是我 09-25 那封信里立的判据,本轮首次可验:\n```\n修**前**:claim 后早退 ⇒ 占位行**永不释放** ⇒ M 只增不减 = **泄漏计数器**\n修**后**:defer 覆盖所有 return ⇒ M = **瞬时量**\n⇒ 判读: **M 长期非 0 ⇒ 先怀疑\"还有没有未修完的早退路径\",而不是\"正好有请求在飞\"**\n```\n\n## ⚠️ 证据强度(不夸大)\n\n```\n\"上线后 0 新增\"是**弱证据**: 上线仅约 1 小时,且这段时间未必有输入触发那条早退路径\n⇒ 它**不违例**,但**样本不足**;不能据此宣布\"泄漏已彻底关闭\"\n⇒ 要变成强证据,需要: ① 有人故意踩一次早退路径(构造 session_id 非 UUID 的请求),\n ② 或观察数天 M 恒为 1 不增长\n★ 现有那 1 行是**修前的化石**(09-12),它不会被修复\"清掉\"——\n 修复只保证**不再新增**。若要清它需要单独决定(属数据操作,非代码修复)。\n```\n\n## 边界 / 未做\n\n· **没有改任何代码、没有动生产**(本轮只读:git merge-base + strings + SQL 统计)。\n· 线上二进制内嵌 `b0c87192` **落后于仓库 HEAD**(当时 `72fe806`)⇒ 二进制比代码旧,\n 但**已含**本条两个修复 —— 这两件事要分开说,否则会读成\"线上没修\"。\n· 那 1 行化石**仍在库里**,我没有清它(清它属数据决定,且无人授权)。\n· 未能回给 pi(`send_mail` 被会话闸挡下)—— 本条即那封的持久记录。" + "note": "★★★ 2026-09-30 登记。**这条结论此前只存在于邮件里(2026-09-25 05:57 报给 pi 的那封),没有落进仓库** —— 与已有的 `commit-as-reply-is-not-a-reply` 同族。本轮把它**复核 + 闭环**,转成可判记录。\n\n## 缺陷形状(修前)\n\n两条早退路径会在 `ClaimRelay` 之后、`CreateMail` 之前退出,于是\n`relayed_mails` 落下**占位行(`mail_id IS NULL`)**:**幂等键被白占、hop 不变、后续重试被挡**。\n\n```\nserver/internal/handler/permission.go session_id 非 UUID 的早退\nserver/internal/handler/mail.go \"Failed to check session budget\"(budget 非耗尽就报错)\n⇒ 二者当时**都编在生产二进制里**(2026-09-25 实测:两个字符串各命中 1 处,\n 当时线上二进制 mtime 09-19 13:04、进程 09-20 04:01:54 启动)\n```\n\n## 现在:已修复、已上线、已验证(本轮实测)\n\n```\n修复提交: 7589f0a →(filter-repo 重写后)**34a15dc** 引入 permission.go 的 defer 释放\n 90e5cef →(重写后)**6e4bcd66**\n★ 两个都**是线上二进制的祖先**:\n 线上 /opt/agentmail/agentmail-gateway 内嵌 revision=**b0c87192**(进程 09-30 17:30:56 启动)\n git merge-base --is-ancestor 34a15dc b0c87192 ⇒ **true**\n git merge-base --is-ancestor 6e4bcd66 b0c87192 ⇒ **true**\n⇒ 线上跑的就是含修复的那份 ⇒ 缺陷**已不活着**\n```\n修法形状(值得留档,因为它是\"改**释放时机**\"而不是\"补**每个 return**\"):\n```\npermission.go:109 「用 defer 而不是在各 return 前逐个补 ReleaseRelay:这条路上有多处早退」\npermission.go:118 defer func() { ... }()\n```\n⇒ ★ 这是 ⑬ 一族的反例读法:**当一条路径有\"多处早退\"时,逐处补释放是必漏的**;\n 正确的形状是**把释放挂到作用域出口**,让\"漏\"在语言层面不可能发生。\n\n## 可判验收(本轮跑的)\n\n```\nM = relayed_mails 中 mail_id IS NULL 的行数\n ★ **2026-09-30 更新**:本条初稿写的是 M=**1**(唯一那行 relay_key=\n `no-such-session-0000:toolu-nohuman-1789193173578`、created_at 2026-09-12 06:06:13,\n 我称之为\"修前化石\")。**同日复核 M 已 = 0,且那行连 relay_key 都不在表里了** ——\n 连**存量**都被清掉了(不是\"不再新增\",是那条也没了)。\n ⚠️ 更正我第一版的推断(写完即查,发现机制说错了):\n 我先写\"它被清了\" ⇒ 实测**不止那一行**:\n · `relay_key like 'no-such-session%'` 现在命中 **0**(整类都没了)\n · 与之配对的那封 zcode 权限请求邮件 `8f056b73-…` 在 `mails` 表里**也不存在**\n · 而 `relayed_mails` **总行数仍是 592** ⇒ 删除的同时**有等量新增**\n ⇒ 准确说法: **那批测试夹具数据(邮件 + relay 行)被成组删除了**,\n 而 592 这个总数看不出来(删旧增新抵消)。\n ⇒ ★ 无论按哪种读法,有一件事成立: **这是一次写库动作,而本库没有审计/历史表**\n (`sqlite_master` 里 name like '%audit%'/'%history%'/'%log%' 唯一命中\n `agent_model_catalog`,与 relay 无关)⇒ **谁删的、依据什么删的,事后无从查证**\n —— 这与 `history-rewrite-undisclosed-citations-dangle`(历史被重写且无记录)\n 是**同一族**:不可复核的删除。\n ⚠️ 所以本条的\"证据强度\"一栏要按两段读:\n ① 09-30 早: M=1 且上线后新增 0(**弱证据**,上线仅 1 小时)\n ② 09-30 晚: M=**0**(存量也没了)⇒ 泄漏**已不再可见**,\n 但\"清存量\"这个动作**无据可查** ⇒ 不能据此宣布\"从未泄漏过\"\nN = 已绑定行数 = **592**(当日实测)\n```\n\n### ★★ pi `18ac26c2` 的校账:别把\"测试残留\"当成\"那 1 行未绑定\"\n\n```\npi 09-25 的账: 类 = 458(已绑定 ∧ kind≠failure),其中**含 1 行测试残留** ⇒ 纯业务 457\n★ 关键区分(pi 的增量): 「**未绑定**」与「**测试残留**」是**两个不同的集合**:\n · mail_id IS NULL 的那 1 行 —— 是残留,**但它不是类成员**(类按\"已绑定\"划)\n · 「测试残留」按**键名形状**匹配(`no-such-session%` / `%toolu-nohuman%`)\n 实测命中 **2 行**,其中 **1 行已绑定**(一封真实的 permission_request,\n from=zcode,主题「权限请求: 是否允许执行 Bash?」)\n ⇒ ★ 所以\"测试残留 = 那 1 行未绑定\"**不完整**: 测试残留里有一行**真的发出去过**,\n 它**会**进类统计 ⇒ 要说\"纯业务\"必须**再减这一行**\n⚠️ **本条的所有具体数字都是 2026-09-25 的瞬时值**,本轮(09-30 晚)实测已全部不同:\n 已绑定 ∧ kind≠failure = **592**(当日 458);测试残留形状命中 = **1**(当日 2);\n 纯业务 = **591**;permission_request 邮件 138 封(当日 137,那封\"真发出去的\"在列)\n⇒ 引用这组数时**必须带日期与口径**,否则就是\"同一数字换了所指\"(`03adbf14` 已立此判据)。\n```\n★ **读法(M 的语义随修复而变)** —— 这是我 09-25 那封信里立的判据,本轮首次可验:\n```\n修**前**:claim 后早退 ⇒ 占位行**永不释放** ⇒ M 只增不减 = **泄漏计数器**\n修**后**:defer 覆盖所有 return ⇒ M = **瞬时量**\n⇒ 判读: **M 长期非 0 ⇒ 先怀疑\"还有没有未修完的早退路径\",而不是\"正好有请求在飞\"**\n```\n\n## ⚠️ 证据强度(不夸大)\n\n```\n\"上线后 0 新增\"是**弱证据**: 上线仅约 1 小时,且这段时间未必有输入触发那条早退路径\n⇒ 它**不违例**,但**样本不足**;不能据此宣布\"泄漏已彻底关闭\"\n⇒ 要变成强证据,需要: ① 有人故意踩一次早退路径(构造 session_id 非 UUID 的请求),\n ② 或观察数天 M 恒为 1 不增长\n★ 现有那 1 行是**修前的化石**(09-12),它不会被修复\"清掉\"——\n 修复只保证**不再新增**。若要清它需要单独决定(属数据操作,非代码修复)。\n```\n\n## 边界 / 未做\n\n· **没有改任何代码、没有动生产**(本轮只读:git merge-base + strings + SQL 统计)。\n· 线上二进制内嵌 `b0c87192` **落后于仓库 HEAD**(当时 `72fe806`)⇒ 二进制比代码旧,\n 但**已含**本条两个修复 —— 这两件事要分开说,否则会读成\"线上没修\"。\n· 那 1 行化石**仍在库里**,我没有清它(清它属数据决定,且无人授权)。\n· 未能回给 pi(`send_mail` 被会话闸挡下)—— 本条即那封的持久记录。" }, { "id": "relay-count-keying-label-has-no-guard",