修 install.sh 的"--git-hooks 装不上"(围栏 gate 激活落盘):相位前移 + 判据改成"git 会不会调用它"

★ 复现 pi da3fe374/76f5dcb8 的两半(都在 scratch clone 里,不碰主仓):
   §二 破坏性测试: 501 奇 ⇒ hook 打印"拦下"、git log 里 probe2 = 0 条 ⇒ 真 D 态 ✓
   §三 新 clone:   .githooks/pre-commit 在且可执行,但 core.hooksPath **空**
                ⇒ 在未激活的 clone 里制造 501 奇 ⇒ **提交被创建**(a947fa6)= A 态
   §三 安装路径:   --git-hooks ⇒ rc=2 "GOCACHE 不存在或不可写";GOCACHE=off 也 rc=2

★ 根因是**相位**不是 GOCACHE: --git-hooks 块原先在 source env-defaults.sh 之后(75 vs 49),
   而预检在 source 期 exit 2 ⇒ 到不了装 hook 那行。该分支只用 git/echo,根本不碰 Go
   ⇒ "能装的机器不需要装,需要装的机器装不上"
   修: 整块移到 source 之前 + 块内自判 git 在不在(否则 rc=127,不在本仓退出码词汇表里)

★ 判据升级: 从"$REPO/.githooks/<h> 在不在"改成"git 会不会调用它"
   git rev-parse --path-format=absolute --git-path hooks/<h>(不加 path-format 会给相对路径 ⇒ 误判)
   旧的写死路径只证明"文件在",配置指向别处时**照样绿** = 存在≠生效的又一次复现
   --check 分支同步改掉,并点明"提交前也不会拦"

★ 变异矩阵 6/6(实测): pre-push 不可执行⇒rc=1 / pre-commit 不可执行⇒WARN rc=0 /
   pre-commit 文件不在⇒WARN rc=0 / 全好⇒rc=0 / 无 git⇒人话+rc=2 /
   GOCACHE 坏⇒rc=0(修前 rc=2,本次主目标)

★ ⚠️ 校正 pi 的话里过强的一处: 不是"任何环境不足都挡住它" —— HOME 不可写时旧代码**仍能成功**
   (env-defaults 会把 HOME 兜底改判到 /tmp);精确说法是"GOCACHE 预检挡住它"

★ ⚠️ 我自己上一笔的两个缺陷(install.sh --check 亲口报出):
   ① c77d5b0 里 .githooks/pre-commit 与 deploy/check-fences.py 是 **711**(政策要 0755)
      ⇒ 已 chmod 755(内容零改动;git 只记可执行位,故 index 无变化,新 clone 得到 755)
      ⇒ `cp -a` 会把这个位带进生产快照,而内容判据看不见权限 —— 只有权限门能抓
   ② 把块前移让我失去了 env-defaults 免费提供的自检 ⇒ 自己撞出 rc=127 并补上
      ⇒ 教训: 代码移出某个上下文时,要重算它原来"免费"得到的那些保证

★ 残余(未做、留作待判): core.hooksPath 是本地配置 ⇒ 新 clone 仍须**主动跑一次** --git-hooks。
   我的修让它跑得起来,但没让它自动发生。"clone 即生效"要靠 --global(越权)或另设入口。
This commit is contained in:
2026-09-25 04:35:21 +08:00
parent 753310d12f
commit de1b07210e
2 changed files with 161 additions and 41 deletions

View File

@ -3236,4 +3236,93 @@ window.__AGENTMAIL_TOKEN__ = '<user_key>'; // 省略则走 Cookie
三份共用库**逐字节相同**(md5 5bdeb2e5…):dsh / opencode / pi ⇒ **改一处要改三处**(契约要求同步)
zcode **不走**共用库(自带 renderTurnFailure,注释写明理由=平台特有的建议不同)✓ 这一点它做对了
⇒ 所以"共用库的那段尾部"是**三桥同病**;zcode 已自行绕开
```
---
- ★★★ **复核 pi `da3fe374`/`76f5dcb8`(围栏 gate 的破坏性测试):两半我都独立复现了,
并按它给的判据**落了修**(`deploy/install.sh`);顺带查出我自己上一笔的两个缺陷**
## (A) pi §二 的破坏性测试我复现了 —— 它确实是 **D 态**(不是 B/C)
```
scratch clone(--no-local)+ 手工 core.hooksPath=.githooks ⇒ 制造奇数围栏(501)
hook 输出: "pre-commit: docs/API.md 围栏=501(奇)未配对=3240 —— **拦下**"
`git log` 里 "probe2" = **0 条** ⇒ **提交确实没被创建** ✓ 改变控制流的真判据
```
## (B) ★★★ pi §三 两半都成立,且我用**新 clone** 复现了"未激活 ⇒ 不设防"
```
实验A: git clone --no-local . /tmp/.../r
.githooks/pre-commit **在且可执行** ✓(文件随仓库走)
但 core.hooksPath = **空**(`.git/config` 里 0 处 hooksPath)⇒ **git 不会调用它**
实验B(决定性): 在那个新 clone 里追加未闭合围栏 ⇒ 围栏=501(奇)⇒ 直接 `git commit`
⇒ 提交**被创建了**(a947fa6 "probe: should be blocked if active",log 里 1 条)
⇒ **未激活的 clone = A 态(无判据)**;hook 文件的存在**不产生任何拦截**
```
⇒ 所以 pi 的 §五 拆分成立且我复现了:**"落盘"是两个动作**(文件落盘 ✓ / 激活落盘 ✗),
而**只做前一半时它看起来像全做完了** —— `ls .githooks/` 有文件、`--check` 里写死路径也判 OK。
## (C) pi §三 的安装路径我复现了,并**落了修**(根因是相位,不是 GOCACHE 本身)
```
修前实测(scratch clone):
bash deploy/install.sh --git-hooks ⇒ rc=**2**,报 "GOCACHE=/root/.cache/go-build 不存在或不可写"
换 GOCACHE=/proc/self/nonexistent-gc ⇒ rc=2;GOCACHE=off(本机 go env 的真实返回)⇒ rc=2
⇒ 根因: `--git-hooks` 分支原先在 **source env-defaults.sh 之后**(旧 75 行 vs 49 行),
而该文件在 source 期就把预检跑完 ⇒ **exit 2 到不了装 hook 那行**。
⇒ 而 `--git-hooks` 只用 `git` 与 `echo`(实测:用到的只有 git/echo/exit)**根本不碰 Go**
⇒ 这是"**能装的机器不需要装,需要装的机器装不上**"
修法(已落): 把 `--git-hooks` 整块**移到 source 之前**;并在块内自判 `git` 在不在。
⚠️ 反例校正(我一开始把 pi 的话说得太强): **不是所有环境错都挡得住** ——
实测 `HOME=/nonexistent-home` 时旧代码**仍能成功**(env-defaults 会把 HOME 兜底改判到 /tmp)
⇒ 精确说法是"**GOCACHE 预检挡住它**",不是"任何环境不足都挡住它"。
```
## (D) ★★ 我把判据从"**文件在不在**"改成"**git 会不会调用它**"——并做了变异测试
```
新增 git_hook_active(): git rev-parse --path-format=absolute --git-path hooks/<name> 再查可执行位
⚠️ 踩到的坑(实测): 不加 `--path-format=absolute` 时 rev-parse 返回**相对**路径
(从 / 调用得到 `.githooks/pre-commit`)⇒ 直接 test -x 会按 cwd 解析 ⇒ **误判**
变异矩阵(6/6,全部实测):
[M2] 去掉 pre-push 可执行位 ⇒ [FAIL] + **rc=1**(硬门)✓
[M3] 去掉 pre-commit 可执行位 ⇒ [WARN] + rc=0(旧 clone 该它不挡)✓
[M5] pre-commit **文件根本不在** ⇒ [WARN] + rc=0 ✓(真实的新 clone 场景)
[M4] 全好 ⇒ 两条 [ OK ] + rc=0 ✓
[P3] **PATH 里没有 git** ⇒ 见 (E),起初是 rc=127
⇒ `--check` 分支同步改掉(原先写死 `$REPO/.githooks/<h>` 逐条 `-x`):
那条只证明"**那个文件**在",不证明"**git 会调用它**" —— 配置指向别处时**照样绿**。
这正是"存在 ≠ 生效"在**我自己写的判据**里的又一次复现。
```
## (E) ★★★ 前移引入的新风险我自己撞上了:`git` 缺失 ⇒ rc=**127**(不在本仓退出码词汇表里)
```
实测(env -i PATH=<只有 bash/timeout/env>):
deploy/install.sh: line 78: git: command not found ⇒ rc=**127**
⇒ 把块前移到 source 之前,就**拿不到 env-defaults 的 `AGENTMAIL_REQUIRE` 检查**了
⇒ 缺 git 时得到的是 bash 的原生 127,而本仓约定是"环境问题=2 且要给人话"
修法(已落): 块内自判 `command -v git`,缺则打印人话 + **exit 2**
修后实测:同样 PATH ⇒ "[FAIL] 环境不足:找不到 git…" + rc=2 ✓
⇒ 教训: **把一段代码移出某个上下文时,要重算它原来"免费"得到的那些保证** ——
我前移时只想着"它不碰 Go",忘了它同时**也失去了环境自检**。
```
## (F) 顺带查出**我上一笔自己的错**:`ac58ab3` 里两个新文件是 **711**(政策要求 755)
```
`install.sh --check` 亲口报的: "[FAIL] 权限不对:.githooks/pre-commit 是 711(应为 0755)"
"[FAIL] 权限不对:deploy/check-fences.py 是 711(应为 0755)"
对照既有的 .githooks/pre-push = 755 ✓
⇒ 我写入时用了 711(group/other 无读位);`cp -a` 会把这个位**带进生产快照**,
而内容判据(cmp/哈希)**看不见权限** ⇒ 这类缺陷只有权限门能抓
已修: chmod 755(内容零改动,`git diff --numstat` 为空 ⇒ 仅 mode 变化)
修后权限门: 711 的两条消失;剩 1 条是别的会话的文件(persisted-cwd-…test.mjs=600,ab283c4)
```
## (G) 边界(不夸大)
```
· 我改的是 `deploy/install.sh`(我的 scope)—— **没有改** `.githooks/*` 的判定逻辑、
没有改 `check-fences.py`、**生产一个字节没动**(仍 09-19 13:04,pi 桥仍 20260915-094528)
· "激活落盘"这条**仍未解决**的残余:`core.hooksPath` 是本地配置 ⇒
新 clone 必须**主动跑一次** `--git-hooks`。我的修让那次**跑得起来**,但**没有让它自动发生**
⇒ 若要做到"clone 即生效",得靠 `git config --global`(越权、影响别人的仓库)或
在 `--check` 之外另设入口;**两条我都没做**,留作待判
· shellcheck 不在本机 ⇒ 只跑了 `bash -n` + 行为矩阵(6/6),**没跑静态检查**
```