diff --git a/docs/DEBTS.json b/docs/DEBTS.json index b9a849e..900bc15 100644 --- a/docs/DEBTS.json +++ b/docs/DEBTS.json @@ -383,6 +383,14 @@ "where": "server/internal/repo/thread.go:47,77,81(cap 定义 / 用作递归上界 / 取到后直接返回)· server/internal/handler/thread.go:99,113,118,123,174(五处消费 anchorDepth,无一与 cap 比较)· server/internal/repo/thread_test.go:95-100(只覆盖正常路径)", "kind": "**信号在手没人读**:`ThreadRootOf` 用 `descendantDepthCap` 兜底截断递归,却把触到上界的 `lvl` 原样返回并上报(anchor_depth),全仓 0 处与 cap 比较 ⇒ 根不可信时调用方无从知道。**当前影响为零**(现库最深 80,cap=10000,差 9920 倍)", "note": "★★★ 2026-09-30 登记。**这是 pi `df1788ec`(2026-09-25 05:57:34)指出的洞,我 `ccd4e4f6`(2 分 28 秒后)给了修法,四天过去代码未动。** 本轮复核确认它**至今仍然存在**,且账本里此前没有它。\n\n## 形状:信号在手,没人读\n\n```\nrepo/thread.go:47 const descendantDepthCap = 10000 ← 兜底上界(数据损坏时停止递归)\nrepo/thread.go:77 ... WHERE up.lvl < $2 ... Scan(&rootID, &lvl)\nrepo/thread.go:81 return rootID, lvl, nil ← ★ 取到 lvl 后**直接返回,从不判定**\nhandler/thread.go:99 rootID, anchorDepth, err := repo.ThreadRootOf(...)\nhandler/thread.go:113 if offset == 0 && anchorDepth > 0 && !containsMail(...)\nhandler/thread.go:118 path[i].Depth += anchorDepth\nhandler/thread.go:123 repo.TreeMailByID(..., anchorDepth)\nhandler/thread.go:174 \"anchor_depth\": anchorDepth ← ★ 照常进 API 响应\n⇒ 全仓 anchorDepth 与 descendantDepthCap 的比较 = **0 处**\n```\n★ 准确说法(pi 的措辞最准):**不是\"没产生信号\",是\"信号在手、没人读\"** ——\nCTE 对、返回对、调用方接得对,**每一段单独看都对**,\n错只存在于**段与段之间那个\"应该发生却没发生的比较\"**里。\n\n## 现状影响:**零**(先量过再说)\n\n```\n现库最大回复链深度 = **80**(递归 SQL 实测);cap = 10000 ⇒ 差 **9920 倍**\n⇒ 正常邮件永远触不到 cap ⇒ 当前**没有任何实际影响**\n⇒ 但数据损坏/成环时:CTE 靠 `WHERE up.lvl < $2` 兜底停止,\n 而 **lvl 仍被返回、照常累加、照常上报**(:174)⇒ 客户端拿到 anchor_depth≈10000\n 却**无从知道这个根不可信**\n```\n★ 所以这是「**判红但影响为零**」的一格:值得记,是因为它有可判落点,\n不值得立刻改,是因为触发条件离现状有 9920 倍。\n\n## 缺口本身也无判据\n\n```\nserver/internal/repo/thread_test.go:95-100 只验**正常路径**(anchorDepth != 1 才失败)\n⇒ 没有任何测试覆盖「深度触到 cap 时会发生什么」—— 缺口没有守它的东西\n```\n\n## 已有的修法(我 `ccd4e4f6` 提的,未实施)\n\n```\n路1: repo 包内加**已导出**判定(如 func DepthTrusted(lvl int) bool)⇒ handler 一行调用\n路2(我建议): **在 repo 包内就地判定** —— ThreadRootOf 发现 lvl 触到 cap 时返回 typed error\n★ 理由不是\"省代码\": 判定属于**知道 cap 的那一层**;\n 放 handler 就等于把 repo 的私有常量在 handler 复制一份 ⇒ 造出**第三个漂移点**\n```\n⚠️ 顺带记一条同源判据(⑩′):**\"在不在那个文件里\"与\"从调用点能不能拿到\"是两件事**。\n`grep -c` 只能验前者,验不了后者(`descendantDepthCap` 在 repo 里 grep=1,但从 handler 够不着)。\n\n## 边界 / 未做\n\n· **没有改任何代码**(本轮只读:递归 SQL + grep + 读源码)。\n· 深度 80 这个数是**本机此刻的库**;它是瞬时量,不是断言(今天深、明天可能浅)。\n· 触发条件(成环/损坏)**我没有构造**——只读了 `:43` 注释里声明的意图,未实测该分支。\n ⇒ 「截断后 lvl 照常上报」是**读码得出的**,不是**跑出来的**。\n· 若日后要修,先确认那个 10000 是\"兜底停止\"还是\"业务上限\"——两种含义对应不同修法。" + }, + { + "id": "criterion-action-upper-bound-below-claimed-scope", + "count": 1, + "due": "任何人写『要验 X』的新判据时(先答:所选动作能不能覆盖 X 的全部形态?不能就把上界写进文案)—— 尤其引跨包标识符时,grep 只覆盖存在性", + "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` 被会话闸挡下)。" } ] }