Commit Graph

3 Commits

Author SHA1 Message Date
b39359d2e0 fix(deploy): recount 脚本的两个标签**漏写了 mail_id is not null** —— 标签与它打印的数不是同一个谓词
## 这个错的形状(本脚本最不该有的一类)
```
行210 标签: 类: kind<>'failure'(空真)=557;真判据(permission OR key NOT LIKE %failure%) = 459
行80 NAIVE: ... where mail_id is not null and kind<>'failure'
行81 REAL : ... where mail_id is not null and (kind='permission' or relay_key not like '%failure%')
⇒ 标签**省掉了 `mail_id is not null`** ⇒ 按标签字面算得 **558 / 460**,脚本却打 **557 / 459**
   (差的正是那 1 行未绑定 —— 两个标签口径都收它,而两个变量都不收)
★ 本脚本存在的全部理由就是"把口径写下来"(见文件头「口径声明」节),
  而标签省掉限定符 ⇒ 读的人拿这个数去对话里对账**必然对不上** ⇒ 已实际发生过一轮。
```
## 实测证据: 同一个数字 459 在两天指称不同集合
```
09-25 讨论: 459 = **loose**(pi 82c0f5a9: 349+69+25+15=458 已绑定 + 1 未绑定 = 459)
09-26 脚本: 459 = **bound**(REAL 带 mail_id is not null)
⇒ 各 +1(09-25 09:26 新增一行真实投递 723493b7)后**恰好撞上同一个数**
⇒ 所以"脚本打 459"与"讨论里说 459"看起来一致,实为两个集合 ⇒ 是对账分叉的温床
```
## 修法(含一处我自己先写错又改回的地方)
```
① 标签写进限定符: `已绑定 ∧ kind<>'failure'` / `已绑定 ∧ 真判据(...)`
② **同时打印 loose 口径**(新查两个变量 NAIVE_LOOSE / REAL_LOOSE),
   让"这个数是 bound、那个是 loose"在**输出里看得见**,不靠读源码才知道
★ 我第一版图省事写成 `loose = bound + 占位` —— **撤回**: 那等式只在"占位行恰好都满足该谓词"
  时成立(今天占位行 kind='permission' 所以成立),而那是**当下数据的性质、不是不变量**;
  占位行将来若是 failure 形状,`+占位` 就会算错。本文件 24-27 行记的正是这一类错
  ("把瞬时量当断言")⇒ 改为各查一次,不搞代数捷径。
```
## 边界与验证
```
· 输出改动仅两处(标签 + 新增一行说明); rc 修前=1、修后=1(既有 FAIL 来自"占位行年龄",非本次引入)
· 标签字面现可复现: 已绑定∧空真=557、已绑定∧真判据=459、loose∧空真=558、loose∧真判据=460 ✓
· bash -n 通过; **未改任何断言/阈值/判据语义** —— 只修"标签与数不一致"
```
2026-09-26 03:43:47 +08:00
2c34d6e368 fix(deploy): 占位行判据改用**行的年龄**(阈值 1 会把健康并发判成泄漏)+ 后置验证补 binrev==HEAD
两件都来自 pi 2026-09-25 的实测,我逐条复跑确认。

## ① `占位行 <= 1` 是阈值版假红(pi 127746b5,我复现)

`ClaimRelay`(INSERT 占位) 与 `BindRelayMail`(UPDATE 绑定) **不在一个事务里** ⇒
"同时有 2 个请求在飞"的正常形态**就是** 2 个占位行(t1 claim、t2 claim、t1 bind、t2 bind)。

    变异(/tmp/pl/t.db:清掉化石 + 插 2 个**合法在飞**占位)
      旧判据: PLACE=2 ⇒ rc=**1**  [FAIL] 占位行 <= 1     ← 把健康并发判成泄漏
      新判据: rc=**0**,打印"最老 = 0 分钟;其中 >10 分钟 = 0"

阈值 1 是"只有 1 个请求在飞的那天"的快照,与它要防的病(泄漏)**不正交**。

⇒ 与泄漏正交的判别量是**行的年龄**(pi 给的):正常占位行只活一个请求的时长,
  泄漏行活到**下一次请求之后**。年龄与并发度无关,也与"此刻是否恰好有请求"无关。
  阈值 10 分钟:远大于任何单请求(HTTP 有超时),远小于真实泄漏尺度(那行活了 12.7 天)。
  断言**上界**而非"==0"—— 0 是"恰好没请求在飞",不是健康的条件。

正反两面都验(只验一面会放过退化):
    健康并发 2 行 → rc=0 ✓      13 天泄漏 + 1 健康行 → rc=**1** ✓
  真库 → rc=1(那行确实 18259 分钟 ⇒ **真阳性**,非假红)
  并打印年龄 ⇒ 顺带答出"泄漏了多久"(旧判据只说"多了一行")。

## ② 抑制数丢了 keying(pi dd579a8e,我复核确认)

生成器键是 `(ag, prefix(k), r)`(**已含 agent**),但**序列化/打印时把 agent 丢了** ⇒
任何人引用这个数都会以为是"仅根"口径。实测两种 keying 全局差 6 封(61/37 vs 55/43,
6 组**全是跨 agent 合并**)⇒ 名字里不写,下一个人**必重犯**。

    文本: `抑制数 [keying=(agent,根) scope=全局]: …`
    JSON: `suppress_full_groups_keying_agent_root_scope_global`
  ★ 同时补 **scope**:本脚本报的是全局;session 侧同 keying 是另一个数(4/18)——
    同 keying 两个 scope 两个数,少写任一个都只能靠猜。
  ★ 只改名字,**数的值不动**(这是防再犯,不是纠错)。

## ③ 后置验证补 `binrev == HEAD`(pi 127746b5 §一,落在脚本而非只落检查器)

§7 那几项(服务 active / `/health` 200 / 无 panic / SSE 重连)**全都与代码版本无关** ⇒
**旧二进制跑这套清单会全绿**。2026-09-25 实证:线上是 09-19 的二进制而 §7 全过。

pi 建议落在 `redeploy-gateway.sh`,我采纳 —— 因为我昨天已把同一件事实现为
`check-deploy-drift` 判据 ⑤b,但**那个检查器没有任何自动入口**(要人手跑);
落在部署脚本里才能"装完当场就判"。

三态与 ⑤b 同口径:相等→绿 / 不等→**红**(就地回滚)/ 取不到任一侧→**不判**且必须说出来
(判红会让 `-buildvcs=false`、老构建、非 git 构建恒红)。

    实测逻辑: 线上 e8b260dd vs HEAD ⇒ 红 ✓;/nonexistent/binary ⇒ warn 不判 ✓

## 验证

`bash -n` 两文件通过;`recount-relay-counts.sh --json` 合法(23 键);
`criteria-hygiene.test.mjs` 7/7 通过(改名后仍满足 check-*.sh 命名约定)。
2026-09-25 06:27:09 +08:00
ba5720573e 修我自己的 60d59f9: check-relay-counts.sh **改名为 recount-relay-counts.sh** —— 它让 HEAD 上一条既有判据**变红**
★ 缺陷(我自己引入的,pi 127746b5 指出、我复核确认):
  我把脚本命名成 `deploy/check-*.sh`,而本仓那条命名族由
  `client/electron/test/criteria-hygiene.test.mjs` 用 readdirSync().filter(/^check-.*\.sh$/)
  **强制接线**:凡 check-*.sh 必须被 install.sh / redeploy-*.sh 在可执行位置调用。
  ⇒ 我的脚本 0 调用者 ⇒ **实测该判据由 7/7 绿变 6/7 红**("这些政策门禁写了却没有任何入口脚本调用")。
  ⇒ 即: 我一边在信里报"判据在但走不到",一边**自己造了一个走不到的判据**。

★ 但接线是**错的解** —— 它按性质就不是门禁(只读、依赖生产库、断的是库的不变量):
   · 接 install.sh ⇒ 读 /opt/agentmail/data/agentmail.db,**新机器没有该库**
                     ⇒ 实测 AGENTMAIL_DB=/nonexistent 时 rc=1 ⇒ **每次全新安装假红**
   · 接 redeploy-* ⇒ 库是活的、计数会漂 ⇒ 会去**回滚一次正确的部署**
  ⇒ 正确修法是**改名**,进入"按需工具"族(prune-* / archive-* / reset-* 同族),
    并把这条理由写进文件头,免得下一个人再犯。

★ 顺带暴露那条判据的一格(它的自称 vs 它的机制不是一回事):
   自称: 按**性质**分 —— "宣称是政策/门禁的 check-*.sh;不管 check-deploy-drift.mjs 这类按需手动工具"
   机制: 按**扩展名**分 —— /^check-.*\.sh$/
   ⇒ 两个方向都可观察:
     误报(FP): 按需工具 + .sh 后缀 ⇒ 被当门禁(**我这次**)
     漏报(FN): 真门禁 + .mjs 后缀 ⇒ **逃过检查**(实测 check-plugin-snapshot.mjs 被
              redeploy-plugin.sh:218 真实调用,是真门禁,却因 .mjs 不被该判据覆盖)
   ⇒ 这是 pi 那条⑬"判据的动作与它被许诺的范围不匹配"的又一实例。判据本身未改(不在本次范围)。

★ 同时确认别人的修正: 2f0d77a 把我两条 **假红** 断言修对了(我复核成立):
   · `HOMEAGENT == SUBSTR_N - PREFIX_N` ⇒ ⊆: 注入第五族 `pi-failure:` 时差集 +1 而 homeagent 不变,
     旧式**假红**(新增命名是正常演进)✓
   · `RESIDUE == 2 && UNBOUND == 1` ⇒ 只打印 + 用"未绑定 ⊆ 占位"承接:
     清掉那行化石是**正确维护动作**,旧式把正确动作判成失败 ✓
   ⇒ 我的两条确实是把"恰好如此"当成了不变量。**如实记**: 代价是变异④(rename homeagent 族)
     从"被抓"变成"存活" —— 那是"过严"与"漏报"的取舍,我取后者(假红会让人不信判据)。

★ 门禁族数量仍为 3(判据反空真要求 >=3)✓;脚本 rc=0;13 条断言全绿。
★ 边界: 只读查库;**生产未动**;未改那条既有判据(属 client/electron/,非本次范围)。
2026-09-25 06:25:21 +08:00