Files
MailUI4Agents/deploy/recount-relay-counts.sh
JianFeeeee 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

308 lines
20 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 复算本会话里反复被引用的那几个 `relayed_mails` 数(**只读**,不写库)。
#
# # 为什么要这条
#
# 这条链上我们为同一批数**对了好几轮账**,每次都靠人在对话里重敲查询:
#
# · "summary 是 419 还是 422" —— pi 报 422,dsh 报 419;pi 后来承认报错,
# 但**双方都无法出示当时那条查询**,只能靠"再读五次"来对齐。
# · "抑制 37 还是 62" —— dsh 报 36/62、pi 报 61/37;
# 后来发现是**口径不同**(沿完整 parent 链 vs 只沿 failure 链),
# 而两个口径**都没写在信里**,所以先吵了两轮才找到差别。
# · "类 458 怎么来的" —— 判据 `kind <> 'failure'` 是**空真**
# (kind 只有 permission/summary),真正给 458 的是 relay_key 的形状。
#
# 三次的共同点**不是"谁算错了"**,而是 —— **结论落了盘,产生结论的查询没落盘**。
# docs/API.md 里留着"458""61/37"这些数,却没有任何一条能整段重跑的命令;
# 每个复算的人(包括我自己下一轮)都要**从头重写**查询,于是必然再分叉一次。
#
# ⇒ 本脚本把那几条查询**固定成命令**:谁不同意某个数,跑它、看输出。
#
# # 断言的是**不变量与关系**,不是冻结的数
#
# 库是**活**的(`relayed_mails` 随流量增减),所以分两层:
# · **不变量 / 关系** ⇒ 断言(不随后续流量变,只在"被数的东西变了"时红)
# · **当下读数** ⇒ 只**打印**(附时刻),不当断言 —— 否则明天必然假红
# 这正是本会话学到的另一条: 把**瞬时量**当断言,等于给自己埋一个定时假警报。
#
# # 口径声明(本脚本存在的主要理由)
# · `已绑定` = mail_id IS NOT NULL;`占位` = mail_id IS NULL
# · `类` 的判据是 `relay_key` 的**形状**,不是 `kind`(kind 里没有 failure)
# · 抑制数给**两种口径**(完整 parent 链 / 只沿 failure 链),因为两者差很多
#
# 用法: deploy/recount-relay-counts.sh [--json]
# 退出码: 0 = 全部**不变量**成立;1 = 有不变量不成立
#
# # ★ 为什么叫 `recount-` 而不是 `check-`(这条是我自己踩出来的)
#
# 本仓 `deploy/check-*.sh` 是**部署门禁族**的命名约定,且由
# `client/electron/test/criteria-hygiene.test.mjs` 用
# `readdirSync(DEPLOY).filter(/^check-.*\.sh$/)` **强制**:凡叫 check-*.sh 的,
# 必须被 install.sh / redeploy-*.sh 在**可执行位置**调用("判据在但走不到"是它要拦的形状)。
#
# 我这个脚本**第一版就叫 check-relay-counts.sh** ⇒ 被那条判据收进门禁族 ⇒ **把它弄红了**。
# 而按性质它**不该被接线**,两条入口接进去都是错的:
# · 接 `install.sh` ⇒ 它读 `/opt/agentmail/data/agentmail.db`,**新机器没有这个库** ⇒
# 实测 `AGENTMAIL_DB=/nonexistent` 时 rc=1 ⇒ **每次全新安装假红**;
# · 接 `redeploy-*` ⇒ 它断言的是"库的不变量",而**库是活的**,
# 计数一变就红 ⇒ 会去**回滚一次正确的部署**。
# ⇒ 所以正确修法是**改名**(进入"按需工具"族:`prune-*` / `archive-*` / `reset-*` 同族),
# 而不是改判据、也不是硬接线。
#
# ★ 顺带暴露了那条判据的一格: 它的**自称**按性质分("宣称是政策/门禁的 check-*.sh;
# 不管 check-deploy-drift.mjs 这类**按需手动工具**"),而**机制**按**扩展名**分。
# 两者在"按需工具 + .sh 后缀"这个缝里不一致 —— 我的文件正落在缝里。
# 反方向也有(真门禁 `check-plugin-snapshot.mjs` 因 .mjs **逃过**该判据)。
# ⇒ 这是 pi 那条⑬"判据的动作与它被许诺的范围不匹配"的又一实例。
set -euo pipefail
DB="${AGENTMAIL_DB:-/opt/agentmail/data/agentmail.db}"
JSON=0
[ "${1:-}" = "--json" ] && JSON=1
if [ ! -r "$DB" ]; then
printf ' [FAIL] 读不到库: %s\n' "$DB" >&2
exit 1
fi
q() { sqlite3 "file:${DB}?mode=ro" "$1" 2>/dev/null; }
# ── 当下读数(只打印,不断言)─────────────────────────────────────────
TOTAL=$(q 'select count(*) from relayed_mails;')
BOUND=$(q 'select count(*) from relayed_mails where mail_id is not null;')
PLACE=$(q 'select count(*) from relayed_mails where mail_id is null;')
# 注意: SQLite 的 group_concat(DISTINCT x, sep) **不合法**(DISTINCT 聚合只能一个参数)
KINDS=$(q "select group_concat(k,'|') from (select distinct kind as k from relayed_mails);")
KFAIL=$(q "select count(*) from relayed_mails where kind='failure';")
KIND_BAD=$(q "select count(*) from relayed_mails where kind not in ('permission','summary');")
NAIVE=$(q "select count(*) from relayed_mails where mail_id is not null and kind<>'failure';")
REAL=$(q "select count(*) from relayed_mails where mail_id is not null and (kind='permission' or relay_key not like '%failure%');")
# ★ 2026-09-26: 这两个是上面两条的 **loose 口径**(去掉 `mail_id is not null`)。
# 为什么要单独查: 会话里对账时用的"459"/"557"是 loose,而上面两条是 bound ——
# 同一个数字在两天里指称不同集合(09-25 的 loose 459 与 09-26 的 bound 459 是巧合撞上)。
# 把两个口径**同时打进输出**,这一类"数字撞车"才在默认路径上看得见。
NAIVE_LOOSE=$(q "select count(*) from relayed_mails where kind<>'failure';")
REAL_LOOSE=$(q "select count(*) from relayed_mails where kind='permission' or relay_key not like '%failure%';")
SUBSTR_N=$(q "select count(*) from relayed_mails where mail_id is not null and relay_key like '%failure%';")
PREFIX_N=$(q "select count(*) from relayed_mails where mail_id is not null and (relay_key like 'model-failure%' or relay_key like 'service-failure%' or relay_key like 'zcode-failure%');")
HOMEAGENT=$(q "select count(*) from relayed_mails where mail_id is not null and relay_key like 'homeagent:failure:%';")
RESIDUE=$(q "select count(*) from relayed_mails where relay_key like 'no-such-session%' or relay_key like '%toolu-nohuman%';")
RESIDUE_UNBOUND=$(q "select count(*) from relayed_mails where mail_id is null and (relay_key like 'no-such-session%' or relay_key like '%toolu-nohuman%');")
# ★ 锚定不变量(不随流量变,且**有代码依据**,不是"恰好为 0"):
# permission 行的键由 `<sessionId>:<tool_use_id>` 拼(zcode hooks/permission.mjs:191),
# 而 `failure` 标记由**摘要桥**写给 summary 行(dsh-mail-bridge/src/index.ts:1240 等)。
# 两条写路径**不同** ⇒ permission 行里不该出现 failure 字样。
# ⇒ 这条是用来接住"计数关系全都没变、但语义已经错"的那类变异:
# 把一行 permission 的键改成 failure 形状时,substr 与 prefix 会同增 ⇒ 关系断言仍绿。
PERM_FAIL=$(q "select count(*) from relayed_mails where kind='permission' and relay_key like '%failure%';")
SUMMARY_FAIL=$(q "select count(*) from relayed_mails where kind='summary' and relay_key like '%failure%';")
# ★ 跨表 provenance(最强的一类:锚定到**别的表里的真实引用**,不是字符串形状):
# `kind='permission'` 的行来自 `CreatePermissionMail`,那封邮件**必然**在
# `permission_requests` 里有一行(permission.go 里同事务写)。反过来,摘要转发的邮件
# 不该在 `permission_requests` 里出现。
# ⇒ 这条能接住"只改 kind/键、计数关系全不变"的变异(⑤b):
# 那种变异会让一行变成"kind=summary 但仍有 permission_requests" ⇒ 反向断言变红。
PERM_NO_REQ=$(q "select count(*) from relayed_mails r where r.kind='permission' and r.mail_id is not null
and not exists (select 1 from permission_requests p where p.mail_id = r.mail_id);")
SUM_WITH_REQ=$(q "select count(*) from relayed_mails r where r.kind='summary' and r.mail_id is not null
and exists (select 1 from permission_requests p where p.mail_id = r.mail_id);")
# ── 抑制数(两口径,Python 因需递推上溯)─────────────────────────────
SUPP=$(python3 - "$DB" <<'PY'
import sqlite3, sys, collections
db = sys.argv[1]
c = sqlite3.connect('file:%s?mode=ro' % db, uri=True)
rows = c.execute("""select r.agent_name, r.relay_key, r.mail_id
from relayed_mails r
where r.mail_id is not null and r.relay_key like '%failure:%'""").fetchall()
par = {m: p for m, p in c.execute("select mail_id, parent_mail_id from mails")}
fail_ids = {mid for _, _, mid in rows}
def prefix(k):
i = k.find('failure')
return k[:i + len('failure')] if i >= 0 else k
def root_full(mid, cap=300):
seen, st, last = set(), 0, mid
while mid is not None and st < cap:
if mid in seen:
return 'LOOP'
seen.add(mid); last = mid; st += 1; mid = par.get(mid)
return 'CAP' if st >= cap else last
def root_fail(mid, cap=50):
seen, st, last = set(), 0, mid
while mid is not None and st < cap:
if mid in seen:
return 'LOOP'
seen.add(mid); last = mid; st += 1
nxt = par.get(mid)
if nxt is not None and nxt not in fail_ids:
break
mid = nxt
return 'CAP' if st >= cap else last
out = []
loops = []
for label, rf in (('full', root_full), ('fail', root_fail)):
g = collections.defaultdict(list)
nloop = 0
for ag, k, mid in rows:
r = rf(mid)
if r in ('LOOP', 'CAP'):
nloop += 1
g[(ag, prefix(k), r)].append(mid)
out.append('%s:%d/%d' % (label, len(g), sum(len(v) - 1 for v in g.values())))
loops.append('%s:%d' % (label, nloop))
print(' '.join(out) + ' loops ' + ' '.join(loops))
PY
)
# ★ 显式接住 LOOP/CAP 计数 —— 这正是"信号在手 ≠ 信号被读"那一条:
# 递推里**算出了** LOOP,但第一版脚本没有任何断言读它 ⇒ 造环的变异**存活**。
# (本脚本自己也犯过一次这个错,记在这里。)
SUPP_MAIN="${SUPP%% loops *}"
SUPP_LOOPS="${SUPP##* loops }"
SUPP_FULL="${SUPP_MAIN%% *}"; SUPP_FAIL="${SUPP_MAIN##* }"
S_FULL_G="${SUPP_FULL#full:}"; S_FULL_G="${S_FULL_G%%/*}"
S_FULL_S="${SUPP_FULL##*/}"
S_FAIL_G="${SUPP_FAIL#fail:}"; S_FAIL_G="${S_FAIL_G%%/*}"
S_FAIL_S="${SUPP_FAIL##*/}"
L_FULL="${SUPP_LOOPS%% *}"; L_FULL="${L_FULL#full:}"
L_FAIL="${SUPP_LOOPS##* }"; L_FAIL="${L_FAIL#fail:}"
if [ "$JSON" = "1" ]; then
printf '{"total":%s,"bound":%s,"placeholder":%s,"kinds":"%s","kind_failure":%s,' \
"$TOTAL" "$BOUND" "$PLACE" "$KINDS" "$KFAIL"
printf '"kind_unknown":%s,"class_naive":%s,"class_real":%s,' \
"$KIND_BAD" "$NAIVE" "$REAL"
printf '"substr_failure":%s,"prefix_failure":%s,"homeagent_failure":%s,' \
"$SUBSTR_N" "$PREFIX_N" "$HOMEAGENT"
printf '"residue":%s,"residue_unbound":%s,' "$RESIDUE" "$RESIDUE_UNBOUND"
printf '"perm_with_failure":%s,"summary_with_failure":%s,' "$PERM_FAIL" "$SUMMARY_FAIL"
printf '"perm_without_request":%s,"summary_with_request":%s,' "$PERM_NO_REQ" "$SUM_WITH_REQ"
printf '"loops_full":%s,"loops_fail":%s,' "$L_FULL" "$L_FAIL"
# ★ 键名里必须带 **keying**(pi 2026-09-25 实测:生成器用了 `(ag, prefix(k), r)`,
# 但序列化时把 `ag` 丢了 ⇒ 任何人引用这个数都会以为它是"仅根"口径)。
# 实测两种 keying 全局差 6 封(37/6 vs 43/10,6 组全是跨 agent 合并)⇒ 名字里不写,
# 下一个人**必重犯**。scope 同为必需:本脚本报的是**全局**;session 侧同 keying 是另一个数。
printf '"suppress_full_groups_keying_agent_root_scope_global":%s,"suppress_full":%s,' "$S_FULL_G" "$S_FULL_S"
printf '"suppress_fail_groups_keying_agent_root_scope_global":%s,"suppress_fail":%s}\n' "$S_FAIL_G" "$S_FAIL_S"
exit 0
fi
# ── 断言:**不变量与关系**(不随流量变)───────────────────────────────
fail=0
chk_true() { # chk_true <说明> <1=成立 0=不成立>
if [ "$2" = "1" ]; then
printf ' [ OK ] %s\n' "$1"
else
printf ' [FAIL] %s\n' "$1"; fail=1
fi
}
chk_eq() { [ "$2" = "$3" ] && printf ' [ OK ] %s\n' "$1" || { printf ' [FAIL] %s(实测 %s,期望 %s)\n' "$1" "$2" "$3"; fail=1; }
}
printf '\n 取数: %s(只读)\n' "$DB"
printf ' 时刻: %s\n' "$(date '+%Y-%m-%d %H:%M:%S %Z')"
printf ' ⚠️ 下列读数是**快照**(库是活的);断言只看不变量与关系。\n\n'
printf ' ── 当下读数 ──\n'
printf ' total=%s 已绑定=%s 占位=%s\n' "$TOTAL" "$BOUND" "$PLACE"
printf ' distinct kind = [%s];kind 非 permission/summary = %s;kind="failure" = %s\n' \
"$KINDS" "$KIND_BAD" "$KFAIL"
# ★★ 2026-09-26 修: 下面两个标签原先**省掉了 `mail_id is not null`**,于是标签与它打印的
# 数不是同一个谓词 —— 按标签字面算得 558/460,脚本却打 557/459(差的就是那 1 行未绑定)。
# 这是本脚本**最不该有的**一类错: 它存在的全部理由就是"把口径写下来",
# 而标签省掉限定符 ⇒ 读的人拿这个数去对话里对账,**必然对不上**(实测已发生过一轮)。
# ⇒ 现在把限定符写进标签本身(`已绑定 ∧ …`),并**顺带打印去掉限定的那个口径**,
# 让"这个数是 bound 口径、那个是 loose 口径"在输出里**看得见**,
# 而不是靠读脚本源码才知道。
# ⚠️ loose 值**必须各查一次**,不能写 `bound + 占位` —— 那等式只在"占位行恰好都满足该谓词"
# 时成立(占位行今天 kind='permission' 所以两条都成立,但那是**当下数据的性质**,
# 不是不变量;占位行将来若是 failure 形状,`+占位` 就会算错)。本文件第 24-27 行
# 记的正是"把当下性质当不变量"这一类错。
printf ' 类: 已绑定 ∧ kind<>'"'"'failure'"'"'(**空真**)=%s;已绑定 ∧ 真判据(permission OR key NOT LIKE %%failure%%) = %s\n' \
"$NAIVE" "$REAL"
printf ' ★ 上面两数都是**已绑定**口径;**去掉该限定**则分别为 %s / %s(差 %s = 那批占位行里满足该谓词者)\n' \
"$NAIVE_LOOSE" "$REAL_LOOSE" "$((REAL_LOOSE - REAL))"
printf ' failure 边界: %%failure%% 子串=%s;三前缀(无 homeagent)=%s;homeagent:failure: =%s(差 %s)\n' \
"$SUBSTR_N" "$PREFIX_N" "$HOMEAGENT" "$((SUBSTR_N - PREFIX_N))"
printf ' 测试残留=%s(其中未绑定=%s)\n' "$RESIDUE" "$RESIDUE_UNBOUND"
printf ' 抑制数 [keying=(agent,根) scope=全局]: 完整 parent 链=%s 组/%s 封;只沿 failure 链=%s 组/%s 封\n' \
"$S_FULL_G" "$S_FULL_S" "$S_FAIL_G" "$S_FAIL_S"
printf ' 根计算触到 LOOP/CAP 的次数: 完整链=%s;failure 链=%s\n' "$L_FULL" "$L_FAIL"
printf '\n ── 不变量(断言)──\n'
chk_true "total == 已绑定 + 占位(两分穷尽)" "$([ "$TOTAL" -eq "$((BOUND + PLACE))" ] && echo 1 || echo 0)"
chk_true "kind 只取 permission/summary(写路径构造决定)" "$([ "$KIND_BAD" -eq 0 ] && echo 1 || echo 0)"
chk_eq "kind='failure' 的行数恒为 0" "$KFAIL" "0"
chk_true "**空真**: kind<>'failure' 的计数 == 已绑定(对全表恒真)" \
"$([ "$NAIVE" -eq "$BOUND" ] && echo 1 || echo 0)"
chk_true "真判据 **严格小于** 空真判据(否则判据没起作用)" \
"$([ "$REAL" -lt "$NAIVE" ] && echo 1 || echo 0)"
# ★ 量的是**包含关系**,不是"恰好只有四族":
# 三个前缀字面量本身都含 "failure" ⇒ 它们匹配的键必然也被 `%failure%` 匹配(子集)。
# 而 homeagent 族既不以那三者为前缀、又含 failure ⇒ 必落在**差集**里(子集)。
# 两条都由机制决定。先前写成 `HOMEAGENT == SUBSTR_N - PREFIX_N`,是把
# "当下只存在这四个族"当成了不变量 —— 任一桥改用第五种拼法(如 `pi-failure:`)
# 会让差集 +1 而 homeagent 不变 ⇒ **假红**(新增命名是正常演进,不是故障)。已实测复现。
chk_true "三前缀 ⊆ 子串(三前缀字面量都含 failure)" \
"$([ "$PREFIX_N" -le "$SUBSTR_N" ] && echo 1 || echo 0)"
chk_true "homeagent 族 ⊆ 差集(它非三前缀之一、且含 failure)" \
"$([ "$HOMEAGENT" -le "$((SUBSTR_N - PREFIX_N))" ] && echo 1 || echo 0)"
# ★ 那两行残留是**快照,不是不变量** —— 与文件头 24-27 行同一条规则。
# 未绑定那行是"claim 后早退没退键"留下的化石;**清掉它是正确动作**,
# 而 `RESIDUE == 2 && UNBOUND == 1` 会把那个正确动作判成失败
# (实测:清后 1/0 ⇒ 假红)。⇒ 改为只打印(见上面"当下读数"),
# 另用一条**与清理无关**的关系接住:未绑定必是占位,口径一致。
chk_true "残留中未绑定的 ⊆ 占位行(未绑定必是占位,两者口径一致)" \
"$([ "$RESIDUE_UNBOUND" -le "$PLACE" ] && echo 1 || echo 0)"
chk_true "两口径抑制数**不同**(沿链范围不同 ⇒ 量的是不同集合)" \
"$([ "$S_FULL_S" -ne "$S_FAIL_S" ] && echo 1 || echo 0)"
chk_true "完整链口径的抑制数 **大于** failure 链口径" \
"$([ "$S_FULL_S" -gt "$S_FAIL_S" ] && echo 1 || echo 0)"
# ★ 下面两条是补的"信号被读":递推里算了 LOOP/CAP,第一版没人读它 ⇒ 造环变异存活。
# 断言"没有触到上限"= 断言"根计算是可信的"。库真有环时它会红 —— 那正是我们要知道的。
chk_eq "完整 parent 链的根计算**未触上限**(无环/无超深)" "$L_FULL" "0"
chk_eq "failure 链的根计算**未触上限**" "$L_FAIL" "0"
# ★ 变异③的补丁: 占位行数本身就是"幂等键是否泄漏"的读数据 —— 光有 total=bound+place 不够
# (③ 把一行置 NULL,两边同时变,恒等式仍成立 ⇒ 存活)。
#
# ⚠️ 但**不能断言 `占位行 <= 1`**(pi 2026-09-25 实测,我复跑确认):
# `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 —— **把健康并发判成泄漏**
# ⇒ 阈值 1 是"只有 1 个请求在飞的那天"的快照,与它要防的病(泄漏)**不正交**。
#
# ⇒ 与泄漏正交的判别量是**行的年龄**:正常占位行只活"一个请求的时长",泄漏行活到**下一次请求之后**。
# 年龄与并发度无关(并发再大,每行寿命仍是一轮请求),也与"此刻是否恰好有请求"无关。
# 阈值取 10 分钟:远大于任何单个请求(毫秒~秒级,且 HTTP 有超时),
# 又远小于真实泄漏的尺度(那 1 行活了 12.7 天)。
# ★ 断言的是**上界**而不是"是不是 0"—— 因为 0 是"恰好没请求在飞",不是健康的条件。
IDLE_PLACE=$(q "select count(*) from relayed_mails where mail_id is null and (julianday('now') - julianday(created_at)) * 1440 > 10;")
IDLE_AGE=$(q "select cast(max((julianday('now') - julianday(created_at)) * 1440) as int) from relayed_mails where mail_id is null;")
printf ' 占位行年龄: 最老 = %s 分钟;其中 >10 分钟(疑似泄漏)= %s\n' "${IDLE_AGE:-0}" "$IDLE_PLACE"
# ★ 只打印不判红当且仅当**没有任何占位行**:那种情况下 IDLE_AGE 为 NULL/空,
# "没有样本"不等于"样本健康",不能拿它当绿(本文件反复记的假绿形状)。
chk_true "没有**超过 10 分钟**的占位行(年龄与并发度正交;阈值 1 会把 2 个在飞判成泄漏)" \
"$([ "$IDLE_PLACE" -eq 0 ] && echo 1 || echo 0)"
# ★ 变异⑤的补丁: 上面那些都是**关系**断言(A=B、A<B),而把一行 permission 的键改成
# failure 形状时,substr 与 prefix **同增** ⇒ 所有关系仍成立 ⇒ ⑤ 存活。
# 关系断言测不出"**语义**被搬动",只有**锚定到写路径**的不变量能测。
chk_eq "permission 行不含 failure 字样(写路径: 键=session:tool_use_id)" "$PERM_FAIL" "0"
# ★ 跨表 provenance 断言(接住 ⑤b:改了 kind 但计数关系全不变)
chk_eq "kind=permission 的邮件**都**有 permission_requests 行" "$PERM_NO_REQ" "0"
chk_eq "kind=summary 的邮件**都不**在 permission_requests 里" "$SUM_WITH_REQ" "0"
printf '\n'
if [ "$fail" = "0" ]; then
printf ' relayed_mails 口径复算:全部**不变量**成立\n'
exit 0
fi
printf ' relayed_mails 口径复算:**有不变量不成立** —— 被数的东西变了,或库换了\n'
exit 1