★ 动机(本会话实测出来的一个缺口): **结论落了盘,产生结论的查询没落盘**
· "summary 419 还是 422"、"抑制 37 还是 62"、"类 458 怎么来的" —— 三次都靠人在信里重敲查询
· docs/API.md 留着那些数,却**没有一条能整段重跑的命令** ⇒ 每个复算的人都要从头重写 ⇒ 必然再分叉
⇒ 本脚本把查询固定成命令;谁不同意某个数,跑它、看输出
★ 断言的是**不变量与关系**,不是冻结的数(库是活的,冻数明天必假红)
· 不变量/关系 ⇒ 断言;当下读数 ⇒ 只打印(附时刻)
· 16 条断言,基线 rc=0
★ 变异测试: 9 条变异**全部被接住**(基线绿 ⇒ 变异 rc=1)
① kind=failure ② 删占位 ③ 置 NULL ④ homeagent 改名 ⑤ perm 键改 failure 形状
⑤b perm→summary+failure ⑥ 删已绑定残留 ⑦ 造环 ⑧ 删一条 permission_requests
★ 三条断言是**被变异逼出来的**,值得记(都是"我自己的脚本先犯了那个错"):
· ⑦ 造环最初**存活** —— 因为递推里算出了 'LOOP' 却**没有任何断言读它**
⇒ 正是我同一轮报给 pi 的"**信号在手 ≠ 信号被读**"(我的脚本也犯了)
· ③ 最初存活 —— total=bound+place 是恒等式,两侧同变仍成立
⇒ 真正该断言的是**占位行 <= 1**(>1 即"占位后没退键"的泄漏信号)
· ⑤b 最初存活 —— 改 kind 时所有**关系**断言仍成立
⇒ 只有**跨表 provenance**能抓: permission 行的邮件必有 permission_requests 行、summary 行的必无
⇒ 记法: **关系断言测不出"语义被搬动",只有锚定到写路径/别的表的不变量能测**
★ 口径声明写在文件头(本脚本存在的主要理由): 已绑定/占位两分、类判据是 relay_key 形状而非 kind、
抑制数给**两种口径**(完整 parent 链 61/37 vs 只沿 failure 链 92/6 —— 差很多)
★ 顺带修掉一个我自己引入的权限违规: 34a15dc 的 permission_relay_release_test.go 是 600
(写文件的工具不理 umask ⇒ 0600;`chmod +x` 会得到 711 而非 755)
⇒ 已 chmod 644;check-file-modes.sh 现在只剩 1 个既有违规(非我的 scope 文件)
★ 边界: 只读打开库(mode=ro);生产一个字节没动;/tmp 变异副本已用后即弃
234 lines
14 KiB
Bash
Executable File
234 lines
14 KiB
Bash
Executable File
#!/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/check-relay-counts.sh [--json]
|
||
# 退出码: 0 = 全部**不变量**成立;1 = 有不变量不成立
|
||
|
||
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%');")
|
||
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"
|
||
printf '"suppress_full_groups":%s,"suppress_full":%s,' "$S_FULL_G" "$S_FULL_S"
|
||
printf '"suppress_fail_groups":%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"
|
||
printf ' 类: kind<>'"'"'failure'"'"'(**空真**)=%s;真判据(permission OR key NOT LIKE %%failure%%) = %s\n' \
|
||
"$NAIVE" "$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 ' 抑制数: 完整 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)"
|
||
chk_true "子串 %failure% **多于** 三前缀(差即 homeagent 族 = 命名巧合)" \
|
||
"$([ "$SUBSTR_N" -gt "$PREFIX_N" ] && echo 1 || echo 0)"
|
||
chk_eq "homeagent 族 == 子串 − 三前缀" "$HOMEAGENT" "$((SUBSTR_N - PREFIX_N))"
|
||
chk_true "残留集合 = 1 未绑定 + 1 已绑定(**不是同一行**)" \
|
||
"$([ "$RESIDUE" -eq 2 ] && [ "$RESIDUE_UNBOUND" -eq 1 ] && 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**:
|
||
# 正常路径下 Claim 后立刻 Bind/Release,占位是**瞬时**的;稳定 >1 意味着有键没退。
|
||
chk_true "占位行 <= 1(>1 即"占位后没退键"的泄漏信号)" \
|
||
"$([ "$PLACE" -le 1 ] && 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
|