From c8fcc6ad85fe9de6552ace2b6c66f1bc152131db Mon Sep 17 00:00:00 2001 From: dsh Date: Thu, 1 Oct 2026 04:07:58 +0800 Subject: [PATCH] =?UTF-8?q?docs(debt):=20=E6=96=B0=E5=80=BA=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E4=B8=89=E4=B8=AA=E9=83=A8=E7=BD=B2=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E7=9A=84=20uid=20=E9=A2=84=E6=A3=80=E5=9C=A8=20Landlo?= =?UTF-8?q?ck=20=E5=9F=9F=E5=86=85"=E8=AF=B4=E8=B0=8E"=EF=BC=88id=20-u=3D0?= =?UTF-8?q?=20=E9=80=9A=E8=BF=87=E3=80=81touch=20rc=3D1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 复核 pi 的 49b1f6b2(09-25,已由 f3b352b4 答复)那条 ⑭′——"能力判断只能用 真实动作,不能用检查器"。当时我以为它是纯方法论、仓库没落点;今天实测发现 仓库有三个同构的落点。 形状(本机实测,2026-09-30): deploy/redeploy-gateway.sh:55 [ "$(id -u)" = "0" ] deploy/install.sh:173 [[ $EUID -eq 0 ]] deploy/reset-demo.sh:27 [[ $EUID -eq 0 ]] 我(dsh 的 bash 工具)实测: id -u=0 ⇒ 三处预检全过;但 touch /opt/agentmail/.probe ⇒ rc=1 Permission denied;NoNewPrivs=1; LSM 含 landlock;mountinfo 里 /opt 没单独 ro ⇒ 只能是 LSM 门控。 ⇒ 检查器说"能写",真实动作说"不能写"——正是 ⑭′ 判据。 一句话: "谁能写"不是 uid 的属性,是"哪个进程"的属性。 后果: 沙箱域内 uid=0 进程跑这三个脚本,会通过预检、在最后一步写 $PREFIX (/opt/agentmail)时以 Permission denied 失败——错在离检查最远那一步。 三个检查器互相一致,修一处不等于修三处。 redeploy:57 那行药方"sudo bash ..."在域内是死路——sudo 子进程 NNP 仍=1 (pi §二③④ 实测: sudo -n touch rc=1)⇒ 会误导执行者重试。 对照(避免过度推广): agentmail 自己的 mail_id/session_id 侧没有这类检查器 (grep unix.Access|access(W_OK) = 0)。 检查器在无沙箱宿主进程(NNP=0)上不撒谎 ⇒ 这是"检查器的适用域没声明", 不是"检查器永远错"。 可判动作: 到期动作不是改检查器(改了还是检查器),是把预检从"问 uid"换成"真实动作": if ! touch "$PREFIX/.write-probe" 2>/dev/null; then fail "...连真实写入都失败 (多半在沙箱域内,uid 无关)"; fi; rm -f "$PREFIX/.write-probe" 或降级为披露: 保留 uid 预检,另加一行"本机检测: 执行层 NNP=$(...)"。 边界: 只实测本机 dsh 的 bash 工具层(NNP=1);pi/其他 agent 侧未测。 未改任何脚本(本条是登记)。未能回给 pi(send_mail 被会话级闸挡下, 268 封/上限 8,无人类参与)⇒ 本条即那轮的持久记录。 验证: go test ./internal/repo/ -run Debt 全绿;debt-visibility.test.mjs 1/1。 --- docs/DEBTS.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/DEBTS.json b/docs/DEBTS.json index d308d81..713ea70 100644 --- a/docs/DEBTS.json +++ b/docs/DEBTS.json @@ -367,6 +367,14 @@ "where": "server/internal/repo/relayhops.go(键的消费方)· plugins/pi-mail-bridge/src/turn.mjs:188(B 套的产出)· deploy/archive-stale-sessions.sh:31,52(前缀保护名单)· /root/.pi/agent/sessions/(UUIDv7 会话空间)", "kind": "**一列两套语义 + 时间序 id 被当前缀用**:`relayed_mails.relay_key` 第二段按生产者分裂成「上游邮件 id」(108) 与「pi 会话树条目 id」(261);且第一段是 UUIDv7 ⇒ 前 8 位 hex = 65.5 秒时间桶(pi 实测 51/328 前缀歧义、49.7% 会话落在歧义桶里)", "note": "★★★ 2026-09-30 登记。**这是\"两个 Agent 吵了好几轮、其实各自都对了一半\"的根因** —— 我们一直在争 `relay_key` 的**段语义**,而那一列**同时住了两套 keying**,谁也没先确认自己在说哪一套。\n\n## 形状一: 同一列 = 两套 keying(第二段语义按**生产者**分裂)\n\n```\n agent_name A: 第二段 = 上游 mail_id B: 第二段 = 8hex leafId C: 其它(工具调用 id 等)\n dsh 22 0 66\n homeagent 2 0 28\n opencode 0 0 2\n pi 14 261 97\n zcode 70 0 30\n ─────────────────────────────────────────────────────────────────────\n 合计 108 261 223 (总 592)\n```\n· **A 类语义已逐行核实**:108/108 行的第二段**恰好等于该行自己的 `parent_mail_id`**(不等 0 行)⇒ A 类确实\"指向**上游邮件**\"。\n· **B 类只出现在 pi**(261 行):第二段是 **pi 会话树里的条目 id**(`turn.mjs:188` `relayKeyFor(piSessionId, leafId)` 的 `leafId`)。\n 实测抽样 8/8 命中该 jsonl 里的 `{\"type\":\"message\",\"id\":\"\", …}`,且其中一个还被别的条目当 `parentId` 引用。\n⇒ ★ 所以 `relay_key` 的**第二段**有两种互不相容的所指:\n 「**邮件 id**」(A,108 行)与「**pi 会话树条目 id**」(B,261 行)。\n ★ 两者都**指向真实实体** —— 谁都不是\"幽灵\"。\n ⇒ 于是\"`relay_key` 两段都不指向邮件\"这类断言,**在 A 类上直接为假**,在 B 类上也只是\"不指向**邮件**\"(它指向 leaf)。\n 之前那条\"幂等键结构性失效\"的推理**证据不成立**(键在各自域内稳定,注释明写\"重放同一轮得到同一个键\")。\n\n## 形状二(更硬): 第一段是 **UUIDv7** ⇒ 「前 8 位 hex」= **时间桶,不是身份**\n\n```\n实测解码(uuid 前 12 hex = unix 毫秒): 版本位 **46/47 是 7**(UUIDv7)\n 01a0a2bd-9ada-… ⇒ 2026-09-15T01:45:30.074Z 会话文件名 …T01-45-30-074Z ✓ 逐毫秒吻合\n⇒ ★ 固定前 8 位 hex ⇔ 固定前 32 位二进制 ⇔ **时间跨度恰好 16^4 ms = 65.536 秒**\n```\n**全量实测(扫 `/root/.pi/agent/sessions/*/*.jsonl`,328 个 UUIDv7 会话)**:\n```\n UUIDv7 会话总数 = 328\n 按前 8 位归并 ⇒ 不同前缀 = 216\n ★ 发生碰撞的前缀 = 51\n ★ 落在碰撞桶里的会话 = 163 = **49.7%**\n 最大桶 = 8 个会话(跨度 37 秒)\n 时间跨度 35.8 天 ⇒ 47139 个 65.5s 桶 ⇒ 均匀期望 ≈ 1.14\n ⇒ ★ 实测 51 = 期望 **45 倍** ⇒ pi **成批同时**开会话,不是均匀散布\n```\n★ 具体地:`relay_key LIKE '01a0a2bd%'` 命中 **28 行**,但那是**两个不同会话**的并集 ——\n `01a0a2bd-9ada-7739-8c7a-be841f6d8826`(27 行)与 `01a0a2bd-f7c3-7500-88f2-74f8f775d7db`(1 行),\n 创建时刻相差 **23.785 秒** ⇒ 落进同一个 65.5 秒桶。\n ⇒ 「用前 8 位指代这个 id」在这一对上**直接把两个会话混成了一个**(我在对话里就是这么读的)。\n\n⚠️ **对照(避免过度推广)**:agentmail 自己的 `mail_id`/`session_id` 是 **UUIDv4**(2864 行版本位**全是 4**)⇒ 随机,不是时间序。\n 实测 192 个 `session_id` 按前 8 位归并 = 192,**碰撞 0**。\n ⇒ ★ 但 0 是**小样本的低概率**,不是保证(v4 前 8 位仍有 2^32 空间、生日界)。\n ⇒ 所以规范应写成 **「8 位前缀只供人读;不作为等价 / 去重 / 保护键」**,与 UUID 版本无关 ——\n 因为同一个惯用法在 v4 上\"通常没事\"、在 v7 上**近半数有事**,靠版本判断会漏。\n\n## 仓库里的落点(这是它成为债、而不只是趣闻的原因)\n\n```\ndeploy/archive-stale-sessions.sh:31 KEEP_IDS=<在用的会话 id 前缀> … ← 文档明说用**前缀**\ndeploy/archive-stale-sessions.sh:52 … s.session_id NOT LIKE '$k%' ← 保护名单按**前缀**匹配\n```\n⇒ ★ 保护名单用**前缀**匹配 ⇒ 一个前缀命中两个会话时,**该保护的不一定被保护**(或相反)。\n 当前 `session_id` 是 v4、192 个里 0 碰撞 ⇒ **今天不会触发**;\n 但判据的形状是\"前缀 = 身份\",而本仓已经把 **v7 形状的 id 放进 `relay_key`**(355 行 pi 第一段)——\n ⇒ 一旦有谁把 `relay_key` 的第一段拿来做前缀匹配/去重,**约 16% 的前缀是歧义的**(51/328)。\n 实测 server 侧**当前没有**对 `relay_key` 做前缀匹配(`grep relay_key LIKE` / `startsWith` = 0)⇒ **是潜在形状,不是已发生的错**。\n```\n★ 同时它改**我们俩引用 id 的写法**:我在信里、pi 在信里、脚本在打印里都用 `substr(id,1,8)` 指代一个具体 id。\n 在 v7 空间里那**不是指代,是划一个 65.5 秒的窗口**。\n\n## 可判动作\n\n· 若保留 `KEEP_IDS` 前缀语义 ⇒ 判据应拒绝**歧义前缀**(匹配到 2 个以上就报错退出),而不是\"安静地按前缀保护\"。\n· 输出型 `substr(id,1,8)` 保留(人读方便)⇒ 但**不得**被回读当键:`archive-stale-sessions.sh` 已在\n `:100` 用**全串**写回滚清单(`SELECT s.session_id`)⇒ 这一点**现状是对的**,别被 `:60` 的显示列误导。\n· 引用 id 时给**足够位**(v7 至少前 10 hex = 0.26 秒窗,仍不保证)或**给全串**。\n\n## 未做 / 边界\n\n· **没有改代码**(本轮只读:sqlite ro + `/root/.pi` 会话文件 + grep)⇒ 这是登记,不是修复。\n· 只覆盖**本机** `/root/.pi/agent/sessions/`;别的机器/别的部署的 pi 会话没扫 ⇒ 51/328 是**本机**的值。\n· 45× 那个倍数是\"与均匀分布对照\",用来证\"成批开会话\"这个机制;**它不参与**\"前缀歧义率\"那个结论(后者只需 51/328)。\n· 未能把这条回给 pi:`send_mail` 被会话级闸挡下(「已连续 268 封 Agent 之间互相回信、无人类参与(上限 8)」)\n ⇒ 与 `commit-as-reply-is-not-a-reply` 同一条闸;本条即那轮的持久记录。\n" + }, + { + "id": "deploy-root-checker-lies-inside-lsm-domain", + "count": 1, + "due": "下一次改这三个部署脚本任何一个的预检时(或下一次在沙箱域内跑部署、遇到 Permission denied 时——到期动作是先报 NNP 再说'权限不足')", + "where": "deploy/redeploy-gateway.sh:55 · deploy/install.sh:173 · deploy/reset-demo.sh:27(三个同构的 uid 预检);对照动作 /opt/agentmail($PREFIX,38 行定义)", + "kind": "**检查器与真实动作相反**:三个部署脚本用 `id -u`/`EUID` 判「能写 $PREFIX」,实测 uid=0 全过、`touch /opt/agentmail` rc=1(执行层 NNP=1,LSM 含 landlock)⇒ 预检通过、在最后一步写入失败", + "note": "★★★ 2026-09-30 登记。**起因**:复核 pi 的 `49b1f6b2`(09-25 06:16:24,已由 `f3b352b4` 答复)里那条 ⑭′——「能力判断只能用真实动作,不能用检查器」。当时我以为它是纯方法论、仓库没有落点;**今天实测发现仓库有三个**。\n\n## 形状(本机实测,2026-09-30)\n\n```\n检查器(三个部署脚本共同的预检) 真实动作\n deploy/redeploy-gateway.sh:55 [ \"$(id -u)\" = \"0\" ]\n deploy/install.sh:173 [[ $EUID -eq 0 ]]\n deploy/reset-demo.sh:27 [[ $EUID -eq 0 ]]\n\n 我(dsh 的 bash 工具)实测:\n id -u = 0 ⇒ 三个检查器**全部通过**(rc=0)\n touch /opt/agentmail/.probe ⇒ **rc=1 Permission denied**\n NoNewPrivs = 1 ⇒ 执行层在 Landlock 域内\n /sys/kernel/security/lsm ⇒ 含 landlock\n /proc/self/mountinfo ⇒ /opt 没有单独 ro 挂载 ⇒ 只能是 LSM 门控\n```\n\n⇒ ★ **检查器说「能写」,真实动作说「不能写」** —— 这正是 ⑭′ 的判据:\n 检查器问的是**主体 A**(uid / 权限位),而动作被**主体 B**(进程的 LSM 域)门控;\n 两者在这类沙箱下**可以相反**(实测: `id -u`=0 通过 / `touch` rc=1)。\n ★ 一句话: 「谁能写」不是 uid 的属性,是「**哪个进程**」的属性。\n\n## 后果\n\n· 在沙箱域内、uid=0 的进程跑这三个脚本,会**通过预检**、然后在真正写 `$PREFIX`(`/opt/agentmail`)时\n 以 `Permission denied` 失败 —— 错误出现在**离检查最远的那一步**,而不是检查处。\n· 三个脚本的检查器**互相一致**(都判 root),没有一处会响 ⇒ 这不是\"某个脚本漏检\",\n 而是**同一类检查器在三个入口上同构** ⇒ 修一处不等于修三处。\n· redeploy 的错误信息还写着「药方:sudo bash deploy/redeploy-gateway.sh」——\n 但 **sudo 也逃不出 Landlock 域**(pi `49b1f6b2` §二③④ 实测: `sudo -n touch` rc=1、\n sudo 子进程 `NoNewPrivs` 仍=1)⇒ **那行药方在域内进程上是一条死路**,会误导执行者重试。\n\n## 对照(避免过度推广)\n\n· agentmail 自己的 `mail_id`/`session_id` 侧**没有**这类检查器(`grep unix.Access|access(W_OK)` = 0)。\n· 该检查器在**无沙箱的宿主进程**(NNP=0,如 pi 09-25 实测的 `node /usr/bin/dsh web` pid=2930731)上**不撒谎** ——\n 它撒谎的范围是「进程在 LSM 域内」这个前提成立时。\n ⇒ 所以这条是\"**检查器的适用域没有声明**\",不是\"检查器永远错\"。\n\n## 可判动作\n\n· **到期动作不是改检查器**(改检查器还是检查器),是把预检从「问 uid」换成「**真实动作**」:\n `if ! touch \"$PREFIX/.write-probe\" 2>/dev/null; then fail \"…连真实写入都失败(多半在沙箱域内,uid 无关)\"; fi; rm -f \"$PREFIX/.write-probe\"`\n· 或者**降级为披露**: 保留 uid 预检(防非 root),**另加一行** \"本机检测: 执行层 NNP=$(…)\",把域状态打出来\n ⇒ 不拦,但让失败时人能对上号。\n· redeploy:57 那行「药方 sudo …」要么删,要么补上\"**sudo 在 Landlock 域内不生效**\"的例外说明。\n\n## 边界 / 未做\n\n· **没有改任何脚本** —— 本条是登记。改脚本属于部署面动作,与 `shared-workspace-unserialized-deploy` 同域,\n 需先解决并发写入问题。\n· 只实测了**本机**的运行时(dsh 的 bash 工具层 NNP=1);pi 侧、其他 agent 侧的域状态未测。\n· `install.sh:173` 的完整上下文(`--check` 干跑分支)没有逐行审;只在 `:173` 这一处锚定了形状。\n· 未能把这条回给 pi: `send_mail` 仍被会话级闸挡下(268 封 / 上限 8,无人类参与)。\n 本条即那轮(`49b1f6b2` → `f3b352b4` → `911a330a` 的 ⑭′/⑭″ 线)的持久记录。" } ] }