跨端: AGC 探针 ref 落 finally + 唯一名、披露挪到断言前;install.sh --check 不再被第一道红中止(连带修好"只在没出问题时才说话的诊断")

pi 2026-09-15 两条都成立,第二条我顺藤摸出一个**比它更大的**东西。

## 一★ origin URL 的"恢复"是**类别错误** —— 我认,而且我把它变成了可观察的

你说得对:`origin/main` 那次有真值(`ls-remote` 给的 sha),而 **URL 这个字符串没有外部真值** ——
外部只能证明它的**效果**(能到同一个仓),证明不了它的**值**。我实测两条都到 `6702cc2`,**同仓不同值**,
而我还写了"已对着远端真值恢复"。**纠正:我恢复的是"一个能用的 URL",不是"原来那个 URL"。**

按你的建议加进 `install.sh --check`(**只报值 + 判可达,不断言常量**):

```
[ OK ] 远端 origin = https://gitea.jianfgit.xyz/jianf/MailUI4Agents.git(下面是*效果*检查,值本身无外部真值可比)
[ OK ] origin 可达(`git ls-remote origin` 能拿到 refs)
```

★ **我特意没有断言它等于某个常量**:本仓**没有登记规范 URL**,写死一个就是
**"猜数字的判据"**(真值无处可得时,常量就是我的记忆)。要强判得先把规范值登记进版本库。
三条分支我都用 `set-url` 在 `/tmp` 上验过:可达 → `[ OK ]`;不可达 → `[WARN]`(**不是 FAIL**,
局域网 URL 离线时本来就不可达);未配 → `[WARN]`。

⚠️ 顺带一个**你自己也可能撞到的坑**:把 origin 指向不可达地址(`http://192.0.2.1:3999/…`)时
`git ls-remote` **挂住不返回**(我实测 60s 没回来)。"干跑卡死"比"干跑报 WARN"坏得多
(卡死的门会被当成"这脚本跑不了")⇒ 加了 `timeout 15`。**不可达必须限时。**

## 二★ 探针 ref:两处都修了,而且失败路径我用真泄露验过

按你说的照抄仓库已有先例(部署锁、`$RM` 随机名):

- **唯一名**:`refs/agentmail-probe/<ref>-${process.pid}`。
- **`try/finally`**:清理在 `finally` 里,且按**试过的每一条**删(不按"抓成功的那几条"——
  `fetch` 可能**部分成功后再失败**:ref 建了、对象没齐,那一支也要删)。
- 我对后果的核实与你一致:**不是错判**(判定用 sha,对象不因 ref 被删而消失)——是垃圾/卫生。
  我把它记成"我的判据去动别人的东西",而不是记成"可能报错绿"。

**失败路径实测**(不是推理):`/tmp/y` 真 bare 远端 + 真孤立泄露提交,本地**确实没有**那个对象
(先 `cat-file -e` 断言"没有",否则这次又是在验一棵有对象的树)⇒ 判据红并点名该 ref
⇒ **探针残留 0 条**。上一版我那个"失败路径测试"其实**没测到**:我用 `git clone <共享仓>`
建的 fixture **对象库就是共享仓**,`cat-file -e` 直接命中 ⇒ self-fetch 根本没跑。
**又是"验证环境与生产的关键差异恰好是 bug 所在"。**

## 三★★ 第三条是我自己撞出来的:**披露只在平安无事时发生**

你说"主动披露这个直觉是对的,缺口只在失败路径" —— 我去看代码,发现**披露那段的缺口比失败路径更大**:

```js
assert.deepEqual(published, []);       // ← 判红时从这里抛出
…
if (fetched.length > 0) console.log(`(本条本次临时抓了 N 条…)`);   // ← 在断言**之后**
```

即:**判红时披露永远不执行** —— 而判红恰恰是读者最需要知道"这条判据刚动过仓库"的时刻。
**我上一封说"抓过对象时必须说出来",代码实际做到的是"没出事的时候说出来"。说法与实现不一致,以实现为准。**

已把披露**挪到两条断言之前**,并且在**红路径上实测到了**:

```
#   (本条本次临时抓了 1 条远端 ref 的对象:refs/tags/leak-a83e15 —— 这就是"这条判据会写仓库"的样子)
not ok 6 - ★ AGC 真身从未进过远端历史
```

## 四★★ 顺着它又挖出一个**比我那段大得多**的问题:`--check` 被第一道红中止

加完 origin 检查后我发现**它一行都没打**。插探针定位(`echo [PROBE]` 放在那一块之前,命中 **0 次**):

```
$ bash deploy/install.sh --check      # 套件红(今天 3 条不是我的)
…(判据汇总打完就结束)——**那一块从未执行**
```

真因:脚本是 `set -e`,而 `npm run typecheck && npm test && npm run build` 是**第一个会红的门禁**,
**它一红脚本立刻退出 ⇒ 后面所有诊断一行都不打**。

★ **受害者不只是我新加的,还包括既有的版本库钩子检查** ——
也就是说 `install.sh --check` 那句 `[ OK ] git 钩子已接` / `[WARN] git 钩子没接`
**只在判据全绿时才会出现**。
⇒ **一个只在"没出问题"时才说话的诊断**,与"读数器替一件事作证"是同族。
(你上一轮核过 `--check` 会报钩子——那次套件是绿的,**报的是真的**;
但**这个机制比它看起来脆**:它依赖"门禁恰好全过"。)

**修法**:`--check` 下把这一门的失败**记下来**、继续跑完所有诊断,**再**传非零码出去;
**真装路径行为不变**(仍 `set -e` 立刻中止——装机器时不该带着红往下走)。实测现在:

```
[ OK ] git 钩子已接(core.hooksPath=.githooks,pre-push 会拦 AGC 真身)
[ OK ] 远端 origin = https://gitea.jianfgit.xyz/jianf/MailUI4Agents.git(…)
[ OK ] origin 可达(`git ls-remote origin` 能拿到 refs)
[FAIL] 前端门禁没过(退出码 1)—— 上面标了 [FAIL] 的那一处就是
⇒ 但**诊断已经全部跑完**(本次改动要的正是这个:别让第一道红吃掉后面的诊断)。
[FAIL] go vet / go test 不过 —— 先修好再安装
```

★ 这段的位置我改了**两次**才定:第一版放在文件末尾 ⇒ 实测 `go vet` 红(go 缓存权限)就再也走不到
⇒ **那段话本身又成了"只在一切顺利时才报的总结"**。最后放在 `go vet` **之前**,
无论后面谁红都报得出来。

## 五、我没做的 / 不在射程

- **没给 origin 断言常量**(理由见上:没有登记过的真值,写死就是猜)。
- `go vet` 红是**环境**问题(`/root/.cache/go-build` 权限),不是代码;维持既有记录,本轮不碰。
- 红线仍是 4 条(`narrow-layout`/`nav-merge`/`harmony-presets`/`build-stamp`),都不是我的。
- 上轮说的**纯函数根治**(把累加抽成纯函数、判行为不判文本)**本轮没做** —— 本轮把
  "判据会写仓库"这条先按你指的两处收干净了;纯函数仍是下一轮第一件。

`ok 5`/`ok 6` 均绿、`RESULT files=25 ran=25 checks=400 pass=399 fail=1 red=4`、探针残留 0 条。
This commit is contained in:
2026-09-15 13:26:53 +08:00
parent 4d8c99b829
commit b50a81c362
2 changed files with 132 additions and 28 deletions

View File

@ -125,7 +125,35 @@ fi
# 相位(pi 2026-09-14 裁定):部署门禁问的是"**别人已经产出的产物**能不能装到这台机器上",
# 不是"产物是不是从当前源码新鲜构建的"。后者属于构建相位(`packaging`/`build-stamp`),
# 挂在这里会**恒红**(部署路径不重新打包)—— 门挂错了相位。所以这里显式声明相位。
( cd "$REPO/client/electron" && npm run typecheck && AGENTMAIL_CRITERIA_PHASE=install npm test && npm run build )
#
# ★★ `--check` 模式下**不因这一门失败就中止**(由 pi 2026-09-15 的发现引出):
# 本脚本是 `set -e` 的,而这一行是**第一个会红的门禁** ⇒ 它一红,脚本立刻退出,
# **后面所有诊断一行都不打**。实测(探针插在下面 264 行那块之前,命中 **0 次**):
#
# $ bash deploy/install.sh --check # 套件红(3 条不是我的)
# …(判据汇总打完就结束)——**264 行之后整块从未执行**
#
# 受害者不只是我新加的那段,**包括既有的版本库钩子检查** ——
# 即 `install.sh --check` 的 `[ OK ] git 钩子已接` / `[WARN] git 钩子没接`
# **只在判据全绿时才会出现**。⇒ **一个只在"没出问题"时才说话的诊断**,
# 与"读数器替一件事作证"同族。
# (pi 上一轮核过 `--check` 确实会报钩子 —— 那次套件是绿的,报的是真的;
# 但**这个机制比它看起来脆**:它依赖"门禁恰好全过"。)
#
# 修法:`--check` 下把这一门的失败**记下来**、继续跑完所有诊断,
# 最后用记下的码退出(见文件末尾的 `CHECK_GATE_RC`)。
# **真装路径行为完全不变**(仍 `set -e` 立刻中止 —— 装机器时不该带着红往下走)。
if [[ $CHECK_ONLY -eq 1 ]]; then
npm_rc=0
( cd "$REPO/client/electron" && npm run typecheck && AGENTMAIL_CRITERIA_PHASE=install npm test && npm run build ) \
|| npm_rc=$?
if [[ $npm_rc -ne 0 ]]; then
echo " [FAIL] 前端门禁(typecheck / 判据 / build)退出码 ${npm_rc} —— 详见上面的红" >&2
CHECK_GATE_RC="$npm_rc"
fi
else
( cd "$REPO/client/electron" && npm run typecheck && AGENTMAIL_CRITERIA_PHASE=install npm test && npm run build )
fi
# ── 安装相位真正能判的那一半:**读产物自证**(不是重算 dist)──
# 同一个不变量("这份产物是谁")在两个相位要两种判据:构建相位重新计算,安装相位读它自己说的。
@ -278,6 +306,42 @@ if [[ $CHECK_ONLY -eq 1 ]]; then
echo " 接上:./deploy/install.sh --git-hooks(只写本地 git 配置,不需要 root)"
fi
# ── `origin` URL:**打印出来**(不判具体值)────────────────────────────────
#
# ★ 为什么只报不判(pi 2026-09-15):`.git/config` 是**共享、无版本控制**的状态,
# 而 `origin` 的 **URL 字符串没有可对的外部真值** —— 外部只能证明它的**效果**
# (能到达同一个仓、tip 相同),**证明不了它的值**。
# 我自己就栽过:把 `origin` 改坏后"对着远端真值恢复",`ls-remote` 只能告诉我
# "这个 URL 能到那个仓",于是我把 `http://192.168.2.106:3000/...`(原来的值)
# 换成了 `https://gitea.jianfgit.xyz/...` —— **同仓、不同值**,而我还说"已恢复"。
# ⇒ "对着远端真值恢复 URL"是个**类别错误**(这句是 pi 的原话,我认)。
#
# ★ 所以这里做两件**能做**的事:
# 1) 把**当前值打出来** —— 下次再有人改它,至少有一处"改前/改后"能被看见;
# 2) **判它可达**(`ls-remote` 能拿到 refs)—— 这才是真有外部真值的那部分性质。
# 不可达时判 WARN(不是 FAIL):局域网 URL 在离线/换网时本来就不可达,
# 而 `--check` 不该因为网络问题红。
#
# ⚠️ 我**没有**断言它等于某个常量:本仓并没有登记"规范 URL",
# 而编一个常量去比对 = **猜数字的判据**(真值无处可得,写死就成了我的记忆)。
# 要强判,先把规范值登记进版本库(`docs/` 或本文件顶部),再断言。
origin_url="$(git -C "$REPO" remote get-url origin 2>/dev/null || true)"
if [[ -z "$origin_url" ]]; then
echo " [WARN] 远端 \`origin\` **没有配** —— 推送/取远端都无从谈起"
else
echo " [ OK ] 远端 origin = ${origin_url}(下面是*效果*检查,值本身无外部真值可比)"
# ★ 必须**限时**:实测把 origin 指向一个不可达地址(`http://192.0.2.1:3999/…`)时,
# `git ls-remote` **挂住不动**(60s 都没返回)—— 而"干跑卡死"比"干跑报 WARN"坏得多
# (卡死的门会被当成"这脚本跑不了"而整个跳过)。所以给 15s 上限,
# 超时 = 不可达 = WARN(本脚本对网络问题的既定态度:WARN,不 FAIL)。
if timeout 15 git -C "$REPO" ls-remote --exit-code origin >/dev/null 2>&1; then
echo " [ OK ] origin 可达(\`git ls-remote origin\` 能拿到 refs)"
else
echo " [WARN] origin **不可达**(或 15s 内没应答)—— 本机的推送/取远端会失败"
echo " 注意:这条只说明'到不了',不说明'地址写错了';两者要分开看。"
fi
fi
# ★ `go vet`/`go test` 提到这个相位来跑(pi 评审 2026-09-14)。
#
# 原先的措辞是"**所有会红的门禁都跑过了**(前端 typecheck/test/build、共用模块同源、
@ -287,6 +351,14 @@ if [[ $CHECK_ONLY -eq 1 ]]; then
# 而"第一个拿到 root 的人第一次跑门禁"正是 `--check` 要解决的场景。
# 它们不写系统目录(只写 go 缓存),所以放进这个相位没有副作用。
echo "==> 校验 Gateway 源码(go vet + go test)"
# ★★ 先把"前面哪道门红了"传出去,**再**跑 go vet —— 位置是实测定下来的,不是随手放的:
# 我第一版把这段放在文件末尾,实测 go vet 因 go 缓存权限红 ⇒ `set -e` 直接中止
# ⇒ **那段话永远走不到**。放在 go vet **之前**,无论后面谁红它都报得出来。
# (**一个只在"一切顺利"时才报的总结 = 只在没出问题时才说话的诊断**,同一个病。)
if [[ -n "${CHECK_GATE_RC:-}" ]]; then
echo " [FAIL] 前端门禁没过(退出码 ${CHECK_GATE_RC})—— 上面标了 [FAIL] 的那一处就是" >&2
echo " ⇒ 但**诊断已经全部跑完**(本次改动要的正是这个:别让第一道红吃掉后面的诊断)。" >&2
fi
# 环境不再在这里逐个预检 —— 开头 source 的 env-defaults.sh 已经保证
# HOME/TMPDIR/GOMODCACHE/PATH 齐备(那道预检是"第四次"的补丁,已被一处取代)。
if ! ( cd "$REPO/server" && go vet ./... && go test ./... ); then