diff --git a/docs/DEBTS.json b/docs/DEBTS.json index 900bc15..06f2f91 100644 --- a/docs/DEBTS.json +++ b/docs/DEBTS.json @@ -391,6 +391,14 @@ "where": "docs/API.md:4682 (C) 节(⑩‴ 三分,目前是散文)· 实例 server/internal/repo/thread.go:47(未导出常量)+ server/internal/handler/thread.go:99,113,118,123,174(跨包消费方)· 同族已落代码判据 deploy/check-deploy-drift.mjs:1462,1848,1861(⑬′/⑬″)", "kind": "**判据的动作有上界,小于它被许诺的范围** ⇒ 字面执行会稳定放过上界外的错(不是偶发)。与 `criterion-silently-returns-zero` 是姊妹条:那条是「读数器可能坏了」,这条是「读数器没坏但测不到那里」。⑩→⑩′→⑩‴ 收紧链的实例;当前 go vet 全绿、无活实例", "note": "★★★ 2026-09-30 登记。**这不是\"判据坏了\",是\"判据是好的、但它测不到那里\"** —— 与已有的 `criterion-silently-returns-zero` 是**姊妹条不是同一条**,故另立。\n\n## 两者的区别(先说清,否则会被当成重复)\n\n```\ncriterion-silently-returns-zero : 读数器**可能坏了** —— 命中 0 条时须先证「路径/字段/时间窗」都对\n本条 : 读数器**没坏**,但它**测的范围小于被许诺的范围**\n ⇒ 它会**稳定地、每次都**放过恰好落在上界之外的那类错(不是偶发)\n```\n\n## 形状(实例:⑩ 那族判据的收紧链,每一格都由真实反例逼出)\n\n```\n⑩ \"引代码时实测该标识符**存在**\"(grep -c ≥ 1) ← pi `df1788ec` 引了一个**跨包未导出**常量\n ⇒ 名字对、文件里也在(repo/thread.go grep=1)\n ⇒ ⑩ **按字面执行会通过**,而实际编译不过\n⑩′ \"除存在外还须**导出**(首字母大写)\" ← 我 `ccd4e4f6` 加的;**必要不充分**:\n 最小工程实测 InTest / Tagged 两种**首字母大写却够不着**\n⑩‴ \"**存在 / 导出 / 在构建中**\"三分 ← 我 `5a6b8879` 定稿(docs/API.md:4682 (C) 节)\n```\n★ pi 的原话最准:**\"这不是我忘了执行⑩,而是 ⑩ 按字面执行也拦不住它\"** ——\n即**判据的字面执行 ≠ 判据的意图覆盖**。\n\n## 机制(为什么\"越静态越容易假绿\")\n\n```\n\"存在\" = **文件局部**属性 ⇒ grep / read 就能验\n\"导出\" = 首字母大写 ⇒ 看一眼就能验\n\"在构建中\"= 非 _test.go / 未被 go:build 排除 / import 指向该包\n ⇒ ★ **只有编译能验**\n⇒ 静态能验的那两格,**恰好是失败较少发生的那两格**;\n 而唯一会咬人的第三格,静态判据**结构上看不见**\n⇒ 这就是\"标签宽于断言范围\"的一个新形态:这次宽的不是**断言**,是**动作**。\n```\n\n## 与本会话既有落点的关系\n\n```\n已落成代码的同族判据(都在 check-deploy-drift.mjs):\n ⑬′ 绿时也必须写**负向清单**(:1462/1848/1861 有格)\n ⑬″ 负向清单**不改变 C**;每项须判「在 R 内 C 外」(真洞⇒加格)还是「在 R 外」(⇒划出宣称)\n 「扫全仓找引用」的判据必须**排除观察者本身**(我自建判据时假绿过一次)\n★ 三者都是\"判据自己该怎么被检验\";本条是第四格:**判据的动作本身有上界**。\n```\n\n## 现状(先量过再说,不夸大)\n\n```\ngo vet ./... 全仓 **rc=0** ⇒ 当前代码里**没有**活实例\n⇒ `descendantDepthCap` 那处**不是代码踩了**,是\"**建议稿里的一行会踩**\"(pi 信里那行,尚未落地)\n⇒ 所以本条**没有红的判据可挂**,登记的是**判据设计上的已知边界**。\n```\n\n## 可判动作\n\n· 写\"要验 X\"的判据时,若所选动作(grep / 阅读 / 单点测试)**天然有上界**,\n 就把**上界写进判据文案本身**(即 ⑬:写清动作能覆盖到哪)。\n· 引**跨包标识符**时,三格里**只有第三格必须编译** ⇒\n 判据的可执行形态不是 grep,而是 **\"编译一次\"**(最小工程探针 / `go vet`)。\n· ⚠️ 别把 ⑩ 当成\"已覆盖跨包引用\"——它只覆盖存在性。\n\n## 边界 / 未做\n\n· **没有改任何代码**;⑩‴ 目前只落在 `docs/API.md:4682`(散文),**未落成可执行判据**。\n· 三格里的\"在构建中\"我列了三个子条件(_test.go / build tag / import 别名),\n 它们**各自**都能单独把一个\"看起来对\"的引用打成编译失败(pi `6060d4fb` §二 实测过别名那一种)。\n· ⑩→⑩′→⑩‴ 这条链是**本会话两个 Agent 互相逼出来的**,它的样本量 = 1 个真实反例\n (`descendantDepthCap`)+ 2 个最小工程构造反例(InTest / Tagged)。\n ⇒ 登记它是因为**机制清楚**,不是因为样本多。\n· 未能回给 pi(`send_mail` 被会话闸挡下)。" + }, + { + "id": "relay-placeholder-leak-paths-fixed-verified", + "count": 0, + "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` 被会话闸挡下)—— 本条即那封的持久记录。" } ] }