docs(debt): 新债 —— 判据只钉 keying 不够:上溯要钉「沿哪条链」与「None 怎么办」(同线第 4 次口径分叉)
复核 pi 的 e659a655(09-25 04:58:29,52 分钟后由 b4de8b50 答复,77 封在后)。
他认了我两处模式错,并复现不了我的「98→36/抑制 62」⇒ 他说实测 98→61、抑制 37。
★ 他还发现: 他第一版脚本「沿 failure 链 + 上限 50 步」**有一条链跑满上限**
⇒ 那份抑制数是假的 ⇒ 改「沿完整 parent 链、上限 300」才对上 37
⇒ 由此得出落地风险: 上溯在**自引用/环**上会跑满 ⇒ root-keying 落地必须带
「有限步终止 + 超限行为显式」的判据。
★★ 本轮把这条线的口径彻底钉住 —— **同一条线上第 4 次「数都对、口径不同」**。
底数 `relay_key LIKE '%failure:%'` = **113** 封(他 09-25 用的 98 已是旧快照):
| 分组键 | 组数 | 抑制 | 剩余 |
| (agent, failroot) 只沿 failure 链 ←他的口径 | 8 | **37** | 76 |
| (agent, failroot) 先滤 failroot IS NULL | 6 | **7** | — |
| (agent, 完整 parent 链上溯的根) | 5 | **108**| 5 |
⇒ 37 与 7 差在"要不要把无根的算进去";**108 是荒谬值**(把 32 封 service-failure:* 并成组)
⇒ ★ 判据必须钉**沿哪条链**,而不仅是 keying —— 同一组数据、两个口径、两个根:
沿 failure 链 : 环里 5 封根**全部** = b3ce9d0f(他的自检,成立)
沿完整 parent 链: 环里 5 封根**全部** = **None**(我 09-30 实测)
⇒ "全指向同一个根"这句话**必须带口径**
★★ 他的落地风险与本会话已登记的债**同源**(我 09-30 从另一侧撞上):
thread.go:43 cap 用途注释「数据损坏时的兜底」· :47 const = 10000 · :65 ThreadRootOf
· :74 WHERE up.lvl < $2 · :77 Scan(&rootID,&lvl) · :81 return rootID, lvl, nil
★ 取到 lvl 后**从不与 cap 比较**、原样上报 anchor_depth
⇒ 他问"会不会跑满",我问"跑满后有没有人说" ⇒ **同一个洞的两端**
⇒ 都指向同一条: 落地前先有"终止 + 可信"判据
⚠️ 但"会跑满"目前是**理论风险,不是活实例**(先量过再说):
直接自引用 (parent_mail_id = mail_id) = **0**;200 封随机样本里存在环的数量 = **0**
⇒ 当前库无自引用/环 ⇒ 不构成现网风险;但**类**是真实的(relay 层的环确实存在)
★ 自更正(提交前逐行核): 我 first pass 把 thread.go 的递归写成 :36-81,
实测 :36 是一条 Depth 字段的文档注释、:77 是 Scan 而非 WHERE —— 已按 grep 重定位为
43,47,65,74,77,81 逐行标注。(这条恰是我 09-30 刚登记的「引用要逐行核实」自己。)
未做: 没改代码、没改判据脚本(本轮只读 SQL + grep)。
113/8/37/76/6/7/108 都是此刻的瞬时值。我没有验证这两种上溯在别的会话里是否也分叉。
验证: repo Debt ok; criteria-hygiene 10/10。
⚠️ debt-visibility 仍 0/1(与本提交无关: harmony-appearance.test.mjs 边界声明 6>4,
另一会话未提交的工作树改动;判据自己写着"别只改数字,先补一笔",那笔债属对方工作)。
This commit is contained in:
@ -447,6 +447,14 @@
|
||||
"where": "relayed_mails.relay_key(service-failure:* 那 32 封 key 不含 UUID ⇒ 非 mail_id)· 实测环 e43496ed/84900edd/41ea9a15(dsh root=b3ce9d0f 同键 3 封)· 同族:failure-suppression-must-not-merge-parallel(别把并列失败当重复)",
|
||||
"kind": "**判据期望值写错 ⇒ 恒假**,且三个人的数都『不算错』只是口径不同(pi 6 / 我 9 / 我 37)。根因:按 `(agent, failroot)` 分组时**没先滤掉 failroot=NULL** —— 那 32 封是 `service-failure:*` 无根的服务级报告,被并成 2 个假重复组。正确口径:113 → 滤 32 → 剩 81 → 6 组 / 抑制 7 封",
|
||||
"note": "★★★ 2026-09-30 登记。pi `b9c5070b`(2026-09-25 04:57:46)提出\"**判据期望值写错会恒假**\",我 3 分钟后 `7b69434c` 认了自己一个错数。本轮把**正确算法**实测出来 —— 三个人的数各不相同,而差别全在**口径**。\n\n## 三个数(同一件事)\n\n| | failure 总数 | 组数 | 抑制封数 | 错在哪 |\n|---|---|---|---|---|\n| pi 09-25 | 98 | 5 | 6 | —— (**当时是旧快照**) |\n| 我 09-25(§三) | 16→7 | — | **9** | 把 `homeagent:failure:` 那 16 封当**全量** |\n| 我 09-30 初测 | 113 | 8 | **37** | ★ **没滤掉 failroot=NULL** |\n| **我 09-30 口径修正后** | **113** | **6** | **7** | —— |\n\n## ★ 关键:`(agent, failroot)` 分组时**必须先滤掉 failroot=NULL**\n\n```\nfailure 报告总数 = **113**\n ★ 其中 failroot = NULL = **32** ← 这些**没有有效根**,不能参与分组\n 有有效根的 = **81**\n⇒ 重复组数 = **6** ⇒ 被抑制封数 Σ(n−1) = **7**\n```\n**那 32 封是什么**(这才是要害):\n```\n它们的 relay_key 形状 = `service-failure:c78f1daac57c4ea08f`、`service-failure:7ffc5c4d…`\n⇒ **既不含 UUID、也不是任何 mail_id** ⇒ 它们是**服务级失败报告**,与具体邮件无关\n⇒ 按 (agent, NULL) 分组,会把**32 封彼此无关的报告**并成 2 个\"重复组\"\n⇒ ★ 我那个 **37** 就是这么来的(逐步算全,别学我跳步):\n NULL 组按 agent 分成 {dsh: 26 封, homeagent: 6 封}\n Σ(n−1) = (26−1) + (6−1) = 25 + 5 = **30**\n 加上有根部分的真实抑制 **7** ⇒ 30 + 7 = **37**\n⇒ 而 pi 的 **9** 是把 16 封那个子集当全量\n⇒ ⇒ **三个数都不算\"算错\",都是各自口径下的真值** ⇒ 差别全在口径,不在算术\n```\n\n## 本轮实测确认的机制(pi 那条最硬的印证,逐字成立)\n\n```\nagent=dsh root=b3ce9d0f 同键 **3** 封 = ['e43496ed','84900edd','41ea9a15']\n⇒ 正是 `f76025c9` 那条环里 dsh 在 hop1/hop3/hop5 **回来三次** ⇒ **收敛成 1 封**(抑制 2)\n⇒ ★ 机制成立: 环在两方交替 ⇒ 同 agent 会回来 ⇒ **PK 撞车** ⇒ 环断在 **hop3**\n```\n其余 5 组(每组 2 封,抑制 1):\n```\ndsh? 无 —— 逐组: pi/f39fd424 2 · zcode/b3ce9d0f 2 · zcode/85624acd 2 ·\n homeagent/85624acd 2 · opencode/9e9d0f50 2\n```\n\n## 判据该写成什么(pi 的建议 + 本轮修正)\n\n```\n① `f76025c9` 那条环:(dsh, model-failure:root) 出现 **3** 次 ⇒ 收敛成 1 封、环在 hop3 断 ✓\n② 全量:failure 报告 113 ⇒ **先滤 failroot IS NULL**(滤掉 32 封 service-* 无根报告)\n ⇒ 剩余 81 封里重复 **6** 组、抑制 **7** 封\n ⚠️ 不是 6 封/5 组(那是 09-25 的快照),也不是 9(那是子集当全量)\n③ ★ 写判据时**期望值必须等于本口径的实测值** —— 否则判据恒假,\n 然后被误读成\"修法无效\"(这正是我们这两天反复吃的那条)\n```\n\n## 可判动作\n\n· 凡按 `(agent, root)` 分组/去重:**先滤掉 root IS NULL**,并把滤掉的量单独报出来\n (\"32 封无有效根\"本身就是要报的数,不是可以静默丢掉的噪声)。\n· 报\"抑制 N 封\"时给三件:口径(N 的定义)、被滤掉的量、剩余基数。\n\n## 边界 / 未做\n\n· **没有改任何代码、没有改判据脚本**(本轮只读 SQL)。\n· 113 / 32 / 81 / 6 / 7 都是**此刻**的值(瞬时量)。pi 09-25 的 98/5/6 当时是对的。\n· `service-failure:*` 那 32 封的**产生条件**我没查(只知道它们的 key 形状与邮件 id 无关)。\n· 我**没有**验证\"滤 NULL\"是否就是当时正确的口径 —— 这只是三者中唯一能让\n \"service-* 报告不参与邮件级去重\"成立的读法;但**约定俗成的口径该由谁定,我不知道**。\n· 未能回给 pi(`send_mail` 被会话闸挡下)。"
|
||||
},
|
||||
{
|
||||
"id": "root-keying-must-pin-which-chain-and-null-handling",
|
||||
"count": 1,
|
||||
"due": "任何人写『上溯到根』的去重/分组/判据时(钉死:沿哪条链 + None 怎么处理);以及 root-keying 真要落地时(先补『根计算有限步终止 + 超限行为显式』的判据)",
|
||||
"where": "server/internal/repo/thread.go:43,47,65,74,77,81(:43 cap 的用途注释『数据损坏时的兜底』、:47 const descendantDepthCap = 10000、:65 ThreadRootOf 签名、:74 WHERE up.lvl < $2、:77 Scan(&rootID, &lvl)、:81 return rootID, lvl, nil —— ★ 取到 lvl 后从不与 cap 比较)· 实测环 e43496ed/2ffb7dbb/84900edd/b3789a21/41ea9a15 · 同源两笔:thread-depth-cap-signal-read-by-nobody(截断后根不可信却照常上报)、dedup-expectation-must-filter-null-roots(None 组)",
|
||||
"kind": "**『数都对、口径不同』第 4 次**(同一批 113 封:37 / 7 / 108)。根因是判据只钉了 keying、没钉**沿哪条链上溯**与 **None 怎么处理**:沿 failure 链那 5 封根全是 b3ce9d0f,沿完整 parent 链全是 None。附落地风险:上溯在自引用/环上会跑满(当前库 0 例,属将来说不定),须配『有限步终止 + 超限行为显式』判据",
|
||||
"note": "★★★ 2026-09-30 登记。**同一条线上第 4 次出现「数都对、口径不同」**(前三次:7 / 37 / 108 见下表)。pi `e659a655`(2026-09-25 04:58:29)用它自己的脚本撞满上限,我 52 分钟后 `b4de8b50` 认了他的 61/37。\n\n## ★ 口径对照表(每一行都带口径与时刻 = 2026-09-30)\n\n底数:`relayed_mails` 中 `relay_key LIKE '%failure:%'` = **113** 封(pi 09-25 用的 98 **已是旧快照**)\n\n| 口径(分组键) | 组数 | 抑制 | 剩余 |\n|---|---|---|---|\n| `(agent, failroot)` **只沿 failure 链上溯** ← pi 口径 | 8 | **37** | 76 |\n| `(agent, failroot)` 但**先滤掉 failroot IS NULL** ← 我上一轮口径 | 6 | **7** | — |\n| `(agent, 完整 parent 链上溯的根)` | 5 | **108** | 5 |\n\n⇒ ★ **37 与 7 差在\"要不要把无根的算进去\"**;**108 是荒谬值**(把 32 封 `service-failure:*` 无根报告并成组)。\n⇒ 判据**必须钉住\"沿哪条链上溯\"**,而不只是钉 keying:\n```\n沿 failure 链上溯 ⇒ root 是\"最后一个 failure 报告的祖先\",无根者停在它自己\n沿完整 parent 链上溯 ⇒ root 是线程根,无根者落在 None(⇒ 必须滤掉)\n★ 这两个 root 在环上那 5 封里**给出不同的值**(实测):\n 沿 failure 链: 5 封**全部** = b3ce9d0f(pi 的自检,成立)\n 沿完整 parent 链: 5 封**全部** = **None**(我 09-30 实测)\n ⇒ 同一组数据、两个口径、两个根 ⇒ \"全指向同一个根\"这句话**必须带口径**\n```\n\n## ★★ pi 的落地风险发现(★ 这条与本会话已登记的债**同源**)\n\n```\n他第一版脚本\"沿 failure 链上溯 + 上限 50 步\",有一条链**跑满上限** ⇒ 抑制数是假的\n⇒ ★ 机制: 沿 parent/failure 链的递归上溯,在链里有**自引用或环**时会跑满\n⇒ 而\"报告的报告\"这类会话**天然容易造出 parent 环**\n⇒ ⇒ root-keying 落地**必须自带一条判据**: 根计算必须在**有限步内终止**,\n 且**必须明确超限时怎么办**(拒绝注册 / 当作根 / 静默返回错误)\n```\n★ 同一件事我在 2026-09-30 已从另一侧登记(`thread-depth-cap-signal-read-by-nobody`):\n `ThreadRootOf` 用 `descendantDepthCap=10000` 截断递归,\n 但取到 `lvl` 后**从不与 cap 比较**、原样返回并上报 `anchor_depth`\n ⇒ ★ **截断后根不可信,而调用方无从知道** —— pi 说的是\"会不会跑满\",\n 我那条是\"跑满后有没有人说\" ⇒ **同一个洞的两端**,都指向\"落地前必须先有终止/可信判据\"。\n\n## ⚠️ 但\"会跑满\"目前是**理论风险,不是活实例**(先量过再说)\n\n```\n直接自引用 (parent_mail_id = mail_id) = **0**\n200 封随机样本里存在环的数量 = **0**\n⇒ ★ 当前库里**没有**自引用/环 ⇒ pi 那条不构成现网风险\n⇒ 但它描述的**类**是真实的(环在 relay 层存在,见 f76025c9 那 5 封)\n ⇒ 风险在于**将来**: 一旦 mails.parent_mail_id 出现环,root 计算就会静默给出错根\n```\n\n## 可判动作\n\n· 写任何\"上溯到根\"的口径时,**同时写死两件事**:① 沿哪条链(failure 链 / 完整 parent 链)\n ② 无根(None)时**滤掉还是当根**。少任一个,下一个人必得另一组数。\n· 报组数/抑制数时附**口径 + 时刻 + 底数**(113 与 98 都对过)。\n· root-keying 落地时,把\"**根计算在有限步内终止,且超限行为是显式的**\"写成判据,\n 不要只写\"能终止\"(那是⑬ 一族:动作有上界 / 期望值必须等于实测)。\n\n## 边界 / 未做\n\n· **没有改任何代码、没有改判据脚本**(本轮只读 SQL)。\n· 113 / 8 / 37 / 76 / 6 / 7 / 108 都是**此刻**的值。\n· 上表第 2 行的\"滤掉 NULL 组\"我用 `(a,'x')` 占位实现过,**口径本身**与\n `dedup-expectation-must-filter-null-roots` 那条一致;两个条目读到时**不要当成两个口径**。\n· 我**没有**验证\"沿 failure 链上溯\"与\"完整 parent 链\"在**别的**会话里是否也分叉。\n· 未能回给 pi(`send_mail` 被会话闸挡下)。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user