修复: 环境预检标相位——干跑里只 gate 得住某一步的预检改为"记账并继续、末尾按环境 exit 2",不再让第一道环境红吃掉全部诊断

pi 2026-09-15 报的是**我上一轮修复的代价**,而它是我两轮前刚从 `CHECK_GATE_RC` 上修好的
**同一个病**、只换了个入口。

## 一、病:`--check` 一条诊断都不跑

```
$ bash deploy/install.sh --check   → EXIT=2,输出 7 行,`========== test/` 命中 0 次
```

`env-defaults.sh` 在**开头** source,③c 的 GOCACHE 预检在那里 `exit 2`
⇒ 整个干跑在第一步就结束。被吃掉的正好是与 GOCACHE **毫无关系**的前端门禁
(typecheck / 判据 / build)—— 而"别让第一道红吃掉后面的诊断"是我两轮前刚立的性质。

## 二、修法:逐条标相位(不是整体开关)

两条规则都对、撞在一起:**部署路径**要 fail-fast;**`--check`** 的产品**就是诊断本身**。
所以按"这条预检拦得住的是哪一步"**逐条标记**:

| 预检 | 拦得住什么 | 相位 |
|---|---|---|
| ③c `GOCACHE` | 只 gate 得了 **go 那一步** | **可推迟**(记账 → 继续 → 末尾 exit 2)|
| ②b `TMPDIR` 可写/空间 | 构建与测试都要写它 | 可推迟(未标,留给下一轮按同一形状处置)|
| ① `HOME`、② `TMPDIR` 存在、③b 外部命令 | **诊断本身** | **不标,仍立刻 exit 2** |

推迟后由 `agentmail_env_flush` 在**诊断跑完后**统一报出并给退出码 **2**,
报文里写明"这与**某道门红了**不是同一件事"。
**部署路径行为完全不变**(实测:同一环境下 6 行、exit 2、0 条诊断)。

## 三、四象限实测(同一台机,只改 `GOCACHE`)

| 路径 | 环境 | 退出码 | 诊断文件数 |
|---|---|---|---|
| `--check` | 坏 | **2** | **23**(原先 0)|
| `--check` | 好 | **1**(前端 3 真红) | 23 |
| 部署 | 坏 | **2** | 0(fail-fast,不变) |

`--check` 零副作用仍成立(`git status` + `dist` 全量 sha 指纹前后一致)。

## 四、★ 实现过程中又两次"判据在,但走不到"——都是我实测抓的

**① 相位标记设在了 `source` 之后。** 我第一版把 `AGENTMAIL_ENV_DEFER=1` 放在原来的
参数解析处(第 24 行附近),而 `CHECK_ONLY` 在那里**还没赋值** ⇒ 条件为假 ⇒ **标记根本没设上**;
更坏的是**输出看不出区别**(照样一条诊断都不跑)。真因:标记是给 **source 期间**跑的预检看的,
而 `env-defaults.sh` 在 source 时就跑完了预检 ⇒ **参数解析必须整段挪到 source 之前**。
⇒ 已把解析块上移到 `REPO=` 之后、`source` 之前。

**② 末尾的 flush 放在前端门禁的 early-return 之后。** 于是"环境不足 + 前端门禁红"时
它**永远不会执行**(实测:退出码 2、末尾环境报文一行都没有)。已移到 `exit "${CHECK_GATE_RC:-0}"` 之前。

**③ 又一例同族:分类代码自己 `rm` 了要 grep 的文件** —— 上一轮已修,这轮复述在注释里当参照。

## 五、去重(这条是被实测数字逼出来的)

第一次跑通后报文说"环境不足(**2 项**)",而实际只有**一个**根因:
GOCACHE 不可写 会响两次 —— ③c 的预检、以及 `go vet` 因它跑不起来(`install.sh` 的分类步)。
⇒ `agentmail_env_defer KEY MESSAGE` 改成**按 KEY 去重**,两处共用 KEY `gocache`。
理由写进代码:**"数出来的问题个数"也是一种读数,读数错了就是又造了一个假数字。**

## 六、状态

build `checks=401 pass=400 fail=1 red=4`;install `389/389 fail=0 red=3`;`criteria-hygiene` 6/6。
This commit is contained in:
2026-09-15 14:12:48 +08:00
parent 633150cf39
commit 3c8f18d4de
2 changed files with 151 additions and 39 deletions

View File

@ -42,6 +42,67 @@
# 否则"环境本来就齐"与"忘了 source"在输出上完全同形(又是"看起来在兜、其实没兜")。
AGENTMAIL_ENV_DEFAULTS=""
# ══════════════════════════════════════════════════════════════════════════════
# ★★ 环境预检的**相位标记**(pi 2026-09-15 提议;用的是本仓既有词汇)。
#
# 冲突原话(两条规则都对,撞在一起了):
# · **部署路径**:环境不足就该 fail-fast —— 这是本文件存在的理由;
# · **`install.sh --check`**:它的**产品就是诊断本身** ⇒ "只知道一件事"正是要避免的结果。
# 实测的坏结果(pi 报、我复现):③c 的 GOCACHE 预检在**开头** source 时 exit 2
# ⇒ `--check` **一条诊断都不跑**(输出 7 行、`========== test/` 命中 0 次)。
# 于是"别让第一道红吃掉后面的诊断"(我两轮前刚从 `CHECK_GATE_RC` 上修好的那条)
# **又以环境预检为入口重新成立了**,而被吃掉的恰是与 GOCACHE **毫无关系**的前端门禁。
#
# 口径(**逐条标记,不是整体开关**):每条预检自己声明"我拦得住的是哪一步"。
# · 只 gate 得了**某一步**(GOCACHE → go 那步)⇒ 标记为**可推迟**:记账、继续、
# 最后由调用者统一按**环境**退出(与 `CHECK_GATE_RC` 同形:先传出去、再跑完、最后按记录退出);
# · gate 得住**诊断本身**(HOME/TMPDIR 没了,"跑判据"这件事就不可信;外部命令缺了,套件根本起不来)
# ⇒ **不标记**,仍然立刻 exit 2。理由写在各自旁边。
#
# 这样"环境不足要说清"与"别让第一道红吃掉诊断"**各自都还在**。
# ⚠️ 变量必须在**任何预检之前**初始化为空 —— 否则第一条预检 append 到一个未定义变量上,
# 在 `set -u` 下会直接炸(而那会被读成"预检坏了",不是"环境不足")。
AGENTMAIL_ENV_DEFER_RECORD=""
AGENTMAIL_ENV_DEFERRED=0
AGENTMAIL_ENV_DEFER_KEYS=""
# 记一条"可推迟的环境问题"。
#
# ★ `agentmail_env_defer KEY MESSAGE`:**按 KEY 去重**(默认 KEY = 消息本身)。
# 为什么要去重:同一个**根因**会在两处各响一次 —— 例如 GOCACHE 不可写
# (③c 预检)与 `go vet` 因它跑不起来(`install.sh` 的分类步)。
# 不去重就会出现"环境不足(2 项)",而实际只有**一个**问题
# (我实测到了:报 2 项、两条说的是同一件事)—— 那会把一个根因读成两个,
# 而"数出来的问题个数"也是一种读数,读数错了就等于又造了一个假数字。
agentmail_env_defer() {
_am_key="${1:-}"
_am_msg="${2:-$1}"
case " $AGENTMAIL_ENV_DEFER_KEYS " in
*" $_am_key "*) return 0 ;; # 同一个根因已经记过 ⇒ 不重复计数
esac
AGENTMAIL_ENV_DEFERRED=$((AGENTMAIL_ENV_DEFERRED + 1))
AGENTMAIL_ENV_DEFER_KEYS="$AGENTMAIL_ENV_DEFER_KEYS $_am_key"
if [ -z "$AGENTMAIL_ENV_DEFER_RECORD" ]; then
AGENTMAIL_ENV_DEFER_RECORD="$_am_msg"
fi
return 0
}
# 调用者(干跑路径)在诊断跑完后调用:把推迟的环境问题**统一报出来**并给退出码 **2**。
# 与"第一道红"的区别写在输出里,免得下一个人把它读成同一种东西。
agentmail_env_flush() {
if [ "${AGENTMAIL_ENV_DEFERRED:-0}" -eq 0 ]; then
return 0
fi
printf '\n [FAIL] 环境不足(%d 项)—— **这是环境问题,不是代码缺陷**:\n' "$AGENTMAIL_ENV_DEFERRED" >&2
printf '%s\n' "$AGENTMAIL_ENV_DEFER_RECORD" >&2
printf ' ★ 与"某道门红了"**不是同一件事**:这里报的是"**有一步跑不起来**"。\n' >&2
printf ' ★ 上面的诊断**已经全部跑完**(推迟到末尾正是为了这个 —— 环境预检在开头 exit 2\n' >&2
printf ' 会把与它无关的门一并吃掉;那是本仓修过一次的老毛病,入口换成了环境预检而已)。\n' >&2
printf ' ⇒ 按仓库约定用**退出码 2(环境)**。\n' >&2
return 2
}
# ③b 本文件**自己**依赖的命令(pi 评审 2026-09-15)。
#
# ★ 要害:这张表原先只登记了**调用者**的命令(`AGENTMAIL_REQUIRE`),而本文件自己用了
@ -209,11 +270,16 @@ if command -v go >/dev/null 2>&1; then
case "$_am_gocache" in
/*) : ;;
*)
printf '\n [FAIL] 环境不足:GOCACHE 不是绝对路径(实得「%s」)\n' "$_am_gocache" >&2
printf ' 这是**环境问题**,不是代码问题。\n' >&2
printf ' 本机实测:沙箱里 `go env GOCACHE` 会给出字面量 `off` —— 那表示**它用不了 build 缓存**。\n' >&2
printf ' 药方:GOCACHE=<可写目录的绝对路径> 重跑。\n' >&2
exit 2
_am_gc_msg="GOCACHE 不是绝对路径(实得「${_am_gocache}」)——
本机实测:沙箱里 \`go env GOCACHE\` 会给出字面量 \`off\`,那表示它用不了 build 缓存。
药方:GOCACHE=<可写目录的绝对路径> 重跑。"
if [ "${AGENTMAIL_ENV_DEFER:-0}" = "1" ]; then
agentmail_env_defer "gocache" "$_am_gc_msg"
else
printf '\n [FAIL] 环境不足:%s\n' "$_am_gc_msg" >&2
printf ' 这是**环境问题**,不是代码问题。\n' >&2
exit 2
fi
;;
esac
mkdir -p "$_am_gocache" 2>/dev/null || true
@ -225,16 +291,23 @@ if command -v go >/dev/null 2>&1; then
# 判据永远说"能写"。实测:`GOCACHE=/proc/self/nonexistent-gc` 时
# `mkdir` 与重定向**都报 No such file or directory**,而探针 rc=0。
# ⇒ 用**显式**的 `|| exit 1`,把失败直接变成退出码,不依赖 shell 选项的上下文语义。
#
# ★ 这里标**可推迟**(`AGENTMAIL_ENV_DEFER`):GOCACHE 只 gate 得了**go 那一步**,
# 前端门禁(typecheck / 判据 / build)与它**毫无关系** ⇒ 干跑里没必要因为它全停。
if ! ( { : > "$_am_gocache/.agentmail-probe"; } 2>/dev/null || exit 1; rm -f "$_am_gocache/.agentmail-probe" ); then
printf '\n [FAIL] 环境不足:GOCACHE=%s 不存在或不可写\n' "$_am_gocache" >&2
printf ' 这是**环境问题**,不是代码问题。\n' >&2
printf ' 症状(会冒充代码缺陷):`go vet ./...` 报\n' >&2
printf ' internal/…: open %s/…-d: permission denied\n' "$_am_gocache" >&2
printf ' —— 它指着源码文件说 permission denied,而实际一行 .go 都没问题。\n' >&2
printf ' 药方:GOCACHE=<可写目录> 重跑(或修 %s 的权限)。\n' "$_am_gocache" >&2
exit 2
_am_gc_msg="GOCACHE=${_am_gocache} 不存在或不可写 —— 症状(会冒充代码缺陷):
\`go vet ./...\` 报 internal/…: open ${_am_gocache}/…-d: permission denied,
它指着源码文件说 permission denied,而实际一行 .go 都没问题。
药方:GOCACHE=<可写目录> 重跑(或修 ${_am_gocache} 的权限)。"
if [ "${AGENTMAIL_ENV_DEFER:-0}" = "1" ]; then
agentmail_env_defer "gocache" "$_am_gc_msg"
else
printf '\n [FAIL] 环境不足:%s\n' "$_am_gc_msg" >&2
printf ' 这是**环境问题**,不是代码问题。\n' >&2
exit 2
fi
fi
unset _am_gocache
unset _am_gocache _am_gc_msg
fi
# ②b TMPDIR **可写 + 有空间** —— 与 ① HOME 同一条规则,必须一起落。
@ -322,3 +395,5 @@ agentmail_env_report() {
printf ' 环境兜底:(无 —— 调用者已提供全部)\n'
fi
}