Files
MailUI4Agents/deploy/check-shared-libs.sh
JianFeeeee 42f01c7478 fix: 三处"判定对、但信号假"——豁免的自检覆盖、rm 失败仍报"已删除"、show_diff 截断证据
pi 通读后逐处指出,三条都实测复现:

**1. `jsonTestOnlyChange`/`testOnlyDrift`/`runtimeOnly` 没有任何自检碰过。**
他的质疑成立:我上一封说"配了六个样本",那六个样本是**开发时的内联脚本、没进文件**
(他读了全文,找不到——我核了,确实没有)。而这段逻辑是文件里**唯一一处"把红变成绿"
的代码**,也是唯一没有判据的代码,失效方向恰好是"比恒黄更坏"那个(假绿)。
已补 **4 条走真路径的样本**(真临时树 + `diffSummary(d,{repoDir,snapDir})`):
★只差 `scripts.test` ⇒ 不算运行时漂移且必须说出豁免了哪条键;
★`scripts.start` 变了 ⇒ 必须算运行时;★解析不了 ⇒ 不许豁免;★不传 ctx ⇒ 不豁免。

★ 写样本时被一对**同义不同数**的字段绊住:断言 `runtimeDrift === 0` 却得到 1 ——
`diffSummary` 报的是**原始**检测数,而 `checkHost` 自己又减了一遍豁免。
判据自己产出两个矛盾口径,与"注释里两组矛盾的写点计数"同族 ⇒ **统一**:
`runtimeDrift` = 减掉豁免后的结论,被豁免的只出现在 `testOnly` 里;
`checkHost` 删掉自算的 `runtimeOnly`。不传 ctx 时行为与旧版逐字相同。

**2. `prune-deploy-artifacts.sh` 的 `del()`:`rm` 的结果没人看。**
脚本是 `set -uo pipefail`(**无 -e**)⇒ `rm -rf` 失败(权限/只读挂载/immutable)后
照样打"删除 …(NMB)"、收尾汇总"**已删除 N 项,释放约 X MB**"
——**一次失败之后仍产出成功措辞**(与 `redeploy-plugin.sh` 里"被拒还打 [ OK ]"同族)。
已改:判失败即报错 `exit 2`;`removed`/`freed_kb` **只在成功后累加**;并加抽验
(`rm` 报成功但路径还在 ⇒ 报错),不信单一退出码。
⚠️ **这一条的失败路径我没能实测**:本机以 root 跑,权限拦不住 `rm`;
`unshare -r` 建只读挂载被拒(`/proc/self/uid_map: Permission denied`);
tmpfs 无 `chattr +i`。要覆盖得在带 CAP_LINUX_IMMUTABLE 的环境用 immutable 文件,
或给 `del()` 加 `RM` 注入点。**我没有把"改过"说成"验过"。**

**3. `check-shared-libs.sh` 的 `show_diff` 被 `set -e` 当场中止。**
`cmp` 有差异返回 1 ⇒ `pipefail` 让函数返回 1 ⇒ 独立调用触发 `set -e` ⇒ **脚本当场死**。
实测(脚本级,fixture 树里造两处分叉:pi 与 zcode):
    旧版:报告 1 处分叉、收尾汇总 0 次
    新版:报告 2 处分叉、收尾汇总 0 次(收尾那句本来只在成功时打,见下)
两者退出码都是 1(判定一直是对的)—— 所以**光量退出码看不见这个 bug**,
正是 pi 说的"判定对、证据被截断"。而且旧版连 `fail=1` 都执行不到:
退出码 1 是 `set -e` 给的。加 `|| true` 后遍历跑完。
2026-09-14 20:35:01 +08:00

136 lines
6.4 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
# 共用模块必须逐字节相同 —— 见 docs/PLUGIN-CONTRACT.md 第六节。
#
# 一侧改了另一侧没改,几个平台的行为就会悄悄分叉:同一封邮件在 opencode 那边
# 标了已读、在 DSH 那边没标,而两处代码看起来都"对"。
#
# 比对一律以 opencode 为基准逐个对比,而不是两两对比:后者在三方都不同时
# 会打出三条互相矛盾的差异,读的人无从判断谁是对的。
#
# # 为什么每个 peer 的清单不同
#
# 四个插件共用的是**协议无关**的那几个模块(地址解析、收件箱渲染、附件 id 归一…),
# 但各自用到的子集不同:
#
# - pi / dsh / opencode 是邮件驱动的**桥**,需要会话快照、中继去重、SSE 客户端等;
# - zcode-mail-bridge 目前是 **MCP 工具服务器**(模型侧的工具面),
# 不订阅 SSE、不管会话生命周期,所以只需要那 5 个协议无关模块。
#
# 用「对等清单」会逼它拷一份用不到的 relay/session 模块进来 —— 那些文件
# 永远不会被执行,却要跟着一起同步,反而让「哪些是真的共用」变得看不清。
# 于是改成显式列举:**声明共用就必须逐字节相同**,声明之外的模块不检查
# (因为根本没拷过来,结构上就不可能分叉)。
set -euo pipefail
BASE=plugins/opencode-mail-bridge
PEERS=(plugins/dsh-mail-bridge plugins/pi-mail-bridge plugins/zcode-mail-bridge)
ALL_LIBS="relay-dedup relay-policy relay-key permission-mode bounded inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt sse-client user-question attachment-ids"
ALL_TESTS="relay-policy relay-key permission-mode bounded inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt sse-client user-question attachment-ids"
# MCP 工具服务器 + 授权钩子 + 邮件驱动用到的子集(实现与测试都同步)。
#
# 钩子与驱动这一层额外需要:
# permission-mode / relay-key / permission-grants / sse-client
# —— 档位语义、relay_key 截断、决策判定、SSE 帧解析
# catchup / relay-policy / workspace / relay-dedup
# (relay-dedup 在基准那边也没有独立测试,故只比对实现)
# —— 停机补投、主动发信去重、回信策略、工作目录解析
#
# 这些都不能各平台自己写一份:「同意」的含义、谁该收到回信、同一封邮件
# 会不会被转两次 —— 分叉的后果都是只在单平台复现的行为差异。
MCP_LIBS="addressing inbox-format bounded discovery attachment-ids permission-mode relay-key permission-grants sse-client catchup relay-dedup relay-policy workspace"
MCP_TESTS="addressing inbox-format bounded discovery attachment-ids permission-mode relay-key permission-grants sse-client catchup relay-policy workspace"
# ── 判据:用 cmp,不用 diff ─────────────────────────────────────────
#
# 本机 PATH 上的 `diff` **不是 GNU diff**,而是鸿蒙 SDK 工具链里的
# /opt/huawei/harmonyos/ohos-sdk/linux/toolchains/diff。它不认 `-q`,
# 却对**内容不同的文件照样返回 0** —— 于是 `if ! diff -q ... >/dev/null`
# 恒为假,整支脚本变成永真输出(本检查器曾长期如此,实测才发现)。
# cmp 的语义明确:相同 0、不同 1。
same() { cmp -s "$1" "$2"; }
# 判据本身必须先被验证。否则「比较永远说相同」与「真的都相同」
# 在结果上完全一样,而这正是上面那个坑的形状。
selfcheck() {
local a b
a=$(mktemp) && b=$(mktemp)
printf 'x\n' >"$a"
printf 'y\n' >"$b"
if same "$a" "$b"; then
echo "检查器自检失败:比较函数无法发现差异(别用 PATH 上的 diff)" >&2
exit 1
fi
printf 'y\n' >"$a"
if ! same "$a" "$b"; then
echo "检查器自检失败:相同内容的两个文件被判为不同" >&2
exit 1
fi
rm -f "$a" "$b"
}
selfcheck
libs_for() {
case "$1" in
*zcode-mail-bridge) echo "$MCP_LIBS" ;;
*) echo "$ALL_LIBS" ;;
esac
}
tests_for() {
case "$1" in
*zcode-mail-bridge) echo "$MCP_TESTS" ;;
*) echo "$ALL_TESTS" ;;
esac
}
# 差异明细也走 cmp,不借 diff。
#
# ★ 末尾那个 `|| true` **是判据的一部分,不是风格**(pi 评审 2026-09-14 抓到,已实测复现):
# 本脚本是 `set -euo pipefail`,而 `cmp` 在"有差异"时返回 1 ⇒ `pipefail` 让这个函数
# 也返回 1 ⇒ 调用点 `show_diff …` 是一句独立命令,`set -e` 于是**当场中止整个脚本**。
# 实测形状(造一个分叉):
# 共用模块已分叉:f ← 打印了
# <cmp 头 3 行> ← 打印了
# ② 本该继续遍历后续文件 ← **没打印**(脚本已死)
# ③ 本该收尾报"四方同源" ← **没打印**
# 也就是说:判定(红)是对的,但**证据被截成一条**,而且连 `fail=1` 都执行不到
# —— 退出码 1 是 `set -e` 给的,不是那条赋值给的。
# 一条判据红了,却只报第一个对象、不报"我比完了全部"—— 那正是这套评审反复讲的
# "证据必须足以支持结论"。加 `|| true` 后,遍历跑完、汇总照打,红仍然是红。
show_diff() {
cmp "$1" "$2" 2>&1 | head -3 || true
}
fail=0
for peer in "${PEERS[@]}"; do
for f in $(libs_for "$peer"); do
if [[ ! -f "$peer/lib/$f.js" ]]; then
echo "共用模块缺失:$peer/lib/$f.js" >&2
fail=1
continue
fi
if ! same "$BASE/lib/$f.js" "$peer/lib/$f.js"; then
echo "共用模块已分叉:lib/$f.js($BASE vs $peer)" >&2
show_diff "$BASE/lib/$f.js" "$peer/lib/$f.js" >&2
fail=1
fi
done
# 测试同样要同源:共用模块的行为约定写在测试里,
# 只同步实现不同步测试,等于允许一侧偷偷放宽约定。
for f in $(tests_for "$peer"); do
if [[ ! -f "$peer/test/$f.test.mjs" ]]; then
echo "共用测试缺失:$peer/test/$f.test.mjs" >&2
fail=1
continue
fi
if ! same "$BASE/test/$f.test.mjs" "$peer/test/$f.test.mjs"; then
echo "共用测试已分叉:test/$f.test.mjs($BASE vs $peer)" >&2
fail=1
fi
done
done
[[ $fail -eq 0 ]] && echo " 共用模块四方同源(opencode 基准 vs dsh / pi / zcode)(判据已自检)" || exit 1