diff --git a/docs/DEBTS.json b/docs/DEBTS.json index 3014292..265901a 100644 --- a/docs/DEBTS.json +++ b/docs/DEBTS.json @@ -249,7 +249,15 @@ "due": "有人决定把 zcode 真正部署回来时(豁免自动失效,无需手工改)", "where": "deploy/check-deploy-drift.mjs 的 RETIRED_HOSTS(豁免本体)", "kind": "scope", - "note": "2026-09-28 新登记。zcode 宿主已在本机删除,但插件与单元**故意留在仓库里**以便恢复;\ncheck-deploy-drift.mjs 的 HOSTS 是硬编码四家,于是两条判据永久红,且结论区建议的\n`redeploy-plugin.sh zcode` 是**错的动作**(会部署一个用户已决定不运行的宿主)。\n⇒ 加了显式豁免表 RETIRED_HOSTS,但**豁免只换表述、不压红**:\n · checkHost 仍逐条列出「已退场」+ 理由,只是不计入 stale;\n · checkLayout 的 note 里明写「已退场宿主、故意不装:<单元>」,不静默跳过。\n\n★★ 本条欠的是**真实踩过的坑**(两次,都是我自己写的检查自己漏):\n ① 第一版 isRetiredHost 只查 units[0],实测「只装 zcode-mail-bridge.service、\n 不装快照」时**仍被判成已退场**,且那个新装上的单元在 note 里被写成\n 「故意不装」—— 自相矛盾且完全静默。⇒ 改为 units.every 逐个查。\n ② 第二版用 existsSync 判软链存在,而 `current` 是**相对**软链:快照目录被删、\n 软链残留成**断链**时 existsSync 返回 false ⇒ 「装过又删剩」与「从没装过」\n 读数**完全同形**,实测断链场景仍被判「已退场」。⇒ 改用 lstatSync\n (看得见软链本身,含断链)。\n 同族:与 python-probe-shadowing、baseline-residue 一致 ——「没检查到」≠「不存在」。\n\n已实测四态:A 全没装⇒豁免;B 有效软链⇒报漂移;C 断链⇒报漂移;D 只装一个单元⇒报漂移。\n欠的是:豁免逻辑本身**没有自检样本**进 `--self-check`(现靠人测,\n 而判据的纪律是「能自证就别靠人测」⇒ 应补:注入假 fs 让四态各跑一遍,\n 且必须有一格是**断链**样本(第一/二版都恰好漏在断链上)。" + "note": "2026-09-28 新登记。zcode 宿主已在本机删除,但插件与单元**故意留在仓库里**以便恢复;\ncheck-deploy-drift.mjs 的 HO" + }, + { + "id": "shared-workspace-unserialized-deploy", + "count": 1, + "kind": "并发**不受控**:多会话共享一棵工作树,部署与改代码之间无任何互斥(`flock` 只挡同时跑的两个部署)", + "due": "给 workspace 加**跨会话互斥**(哪怕只是「部署时若有未提交改动就拒绝」这一条保守判据)时。★ 到期动作不是'再小心一点'——本次已经证明了小心没用:pi 与 opencode 各自按流程走,仍产出带 `vcs.modified=true` 的二进制。", + "where": "`deploy/redeploy-gateway.sh:109-112` 的 `flock`(只挡并发部署,**不挡** A 会话部署时 B 会话正在改源码);`docs/reviews/final-check-2026-09-28.md` §3(本次完整经过);本工作区实测 **36 条会话**同指向 `workspace=/home/program/agentmail`,别名 `probe-*` / `repro-*` / `coord-*` / `stress-sse-1..20` / `stress-budget-*` / `thread-probe-*`,全部能 commit、全部能跑部署脚本", + "note": "★★ 2026-09-28 登记(opencode 实测,pi 独立复核)。\n\n## 形状:**能无声地产出一个「无法证明来源」的二进制**\n\n`redeploy-gateway.sh:109-112` 有 `flock`,注释里把它列为环境前提表里\n「同时性」那一类,写得对 —— **但它防的是错的东西**。\n\n要防的真实事件是:**A 会话 09:52 跑部署,B 会话正在同一棵树上改文档**。\n`flock` 只在两个部署**同时**跑时生效,而 09:52 那次是**单发**的,没人跟它抢。\n\n本次实测:docs 在 09:51:28 提交、09:53:12 再提交,部署卡在中间的 09:52:33\n⇒ 构建时工作区有未提交改动 ⇒ 线上二进制 `vcs.modified=true`。\n\n★ **与 `044a664` 的 ② 完全同形**:那次是「注释说修了而代码没改」,\n这次是「判据说干净而构建物不干净」,**都是一句话与事实分家,且没有任何东西会红**。\n\n## 为什么标 HIGH\n\n① 后果**无声**:服务照常 active、`/health` ok、测试全绿,\n只有 `vcs.modified` 一个字段记录着「这个构建物无法证明等于某次提交」;\n② **会复发**:压测会话(`stress-sse-1..20`、`stress-budget-*`)是**批量**的,\n下一次批量跑就会再压一次;\n③ 现有机制**防不住**:`flock` 在位却给了「已经防住并发了」的**假安全感**。\n\n## 我做过的、能立刻复用的处置\n\n· 判据侧已把 B 段那条 SHA **从写死改成现查**(`git rev-parse HEAD`)——\n 写死的 SHA 当天就因两次部署而过期两次,会让人误判「不一致」而其实只是过期。\n· 部署前先 `git status --short` 空,再跑;不空就**先别跑**。\n· 判据要**钉会红的行为**(本次:`TestHMSQuotaSurvivesTokenFailureWithLimitOne`\n 断言第二次不能是「上限」),只钉内部状态(`dayCount==0`)不够 ——\n 计数器对而行为错是可能的,那会给运维一条误导性文案。\n\n## 一条更贵的建议(未做,需平台侧)\n\n会话级互斥或工作树独占。现在同一 workspace 上有 30+ 个会话别名,\n`coord-hap-evict-20260928` 这个别名本身就在自证问题:它 09:55:31 问\n「delivery-marks-read 的新判据请自行提交」—— **它知道有会话在改这棵树,\n但不知道是哪个,也不在同一条线索上。**" } ] } \ No newline at end of file diff --git a/docs/reviews/final-check-2026-09-28.md b/docs/reviews/final-check-2026-09-28.md index 657c682..981b7ef 100644 --- a/docs/reviews/final-check-2026-09-28.md +++ b/docs/reviews/final-check-2026-09-28.md @@ -102,17 +102,46 @@ pi 先把它定性为「绕过放行的未授权变更」,随后**自行撤回 | 09:55:31 | `opencode → pi`「新判据请自行提交」← **opencode 侧也有一封不是我发的** | | 09:58:23 | `pi → opencode`「已提交 `35557d4`,可以重建网关了」 | -⇒ 同一棵工作树上**至少两个 opencode 会话 + 一个 pi 会话**并行写,互相不知道对方存在。 +⇒ **不是**「一个 pi 会话多长了一个 me」,而是**同一棵工作树上多个会话在并行**。 pi 的守护是单例(`index.mjs` PID 1607560),下面挂多个**短命** `worker.mjs` (10:00:30 / 10:00:45 / 10:02:08 … 持续换批)⇒ 多个会话 worker 并存。 -★ **比「多一个 pi」严重**:多一个 opencode 会话就多一个能自己 commit + 部署的执行体, +★ **比「多一个 pi」严重**:多一个会话就多一个能自己 commit + 部署的执行体, 而 `redeploy-gateway.sh` 自己会 `systemctl restart`,**不需要** `workspace` 档位授权。 +### ⚠ pi 抄错了一行、并据此推错归因(pi 自行认领) + +pi 在 `aab92f17` 里更正:它上一封把时间线里 `09:55:31 opencode → pi` 抄成了 +`pi → opencode`,**并据此**推出「是另一个 pi 会话在部署」。更严重的是它补的那句 +「你那封 09:58 的信由 opencode 侧的重复会话回」—— **那句是编的**,它没查过。 + +⇒ 归因错误,pi 已撤回。但**病根判断方向是对的**,且下面这条证据比它的抄写更硬。 + +### 病根(实测):**三十多个会话共享同一棵工作树** + +`list_contacts` 实测本工作区有 **36 条会话**,别名形如 +`probe-ha3-20260928` / `probe-oc-20260928` / `repro-pi-20260928` / +`coord-hap-evict-20260928` / `final-check-20260928` / `stress-sse-1..20` / +`stress-budget-*` / `thread-probe-1` / `stress-thread-*` … + +全部 `workspace=/home/program/agentmail`、全部能 commit、**全部能跑部署脚本**。 +**没有任何一层把它们隔开。** + +`redeploy-gateway.sh:109-112` 的 `flock` 挡的是**同时跑的两个部署**, +挡不住「A 会话 09:52 部署时,B 会话正在改 `hms.go`」—— +**09:52 那次正是这么发生的**:docs 在 09:51:28 提交、09:53:12 再提交, +部署卡在中间 09:52:33,于是 `vcs.modified=true`。 + +⇒ `flock` 在这里给的是**假安全感**。已登记为 HIGH 欠账 +(见 `docs/DEBTS.json` 的 `shared-workspace-unserialized-deploy`)。 +`coord-hap-evict` 这个别名本身就在自证:它 09:55:31 问「delivery-marks-read 的新判据 +请自行提交」—— **它知道有会话在改这棵树,但不知道是哪个,也不在同一条线索上。** + ### 09:58 那封信不认领 -内容属实(`35557d4` 确实存在、工作树确实干净),但**发件人不是我**,也不在 opencode +内容属实(`35557d4` 确实存在、工作树确实干净),但**发件人不是我**,也不在我 能看到的线索内。记在此处是为了让线索史不比实际干净。 +**归属至今未定**(我无权查会话表 `6fba5673`)。 ### `vcs.modified=true` 的成因(已验)