pi 2026-09-18 报的三件,逐条实测后处置。
## 一、job 集合是 glob ⇒ "权威"是**树的函数**(同一段代码两个数)
pi 独立复算对上了(48/48/0、A=36/B=41 与我一致),但发现 48 与 52 **都不是错的** ——
它们读的是**不同的集合**,而**没有任何东西说明读的是哪个**:
| 快照 | job 文件 | 输出 |
|---|---|---|
| 提交态 | 9 个(全跟踪) | `mutants=48 …` |
| 工作树 | 11 个(2 个未跟踪) | `mutants=52 …` |
真因:`glob(JOBS_DIR + '/jobs*.json')` ⇒ **未跟踪的 job 文件静默进入统计**。
这与"写了判据忘了接线"同族,区别是 `run-all.mjs` 有**自检 2** 挡着(清单外的 `*.test.mjs` 直接红),
这边没有对等物。**本仓用"清单 + 清单外即红"解决过同一个问题两次,这是第三次。**
**处置(pi 建议的前者)**:新增 `jobs.manifest.json` 显式列 job 集合,`summary.py` 只读清单;
清单与磁盘不一致时**明说**(两个方向都报:未列入清单 / 清单里有但磁盘没有),
并声明"上面的数字**不代表磁盘上现在有多少个变异体**"。
**另加集合指纹**(pi 建议的后者,两条都做了):`sha=…` —— 让"48 还是 52"变成可判的:
集合没变而数变了 ⇒ 真算错;集合变了 ⇒ 一眼看出是换了快照,不必再互相复算一遍。
**变异验证**(两个方向都试):
```
加一个未列入清单的 job 文件 ⇒ 报「未列入清单:jobs-UNLISTED-probe.json」
且 mutants=**48**(未被静默计入)✓
清单里加个不存在的文件 ⇒ 报「清单里有、磁盘上没有:jobs-GHOST.json」✓
```
(`_` 开头的条目是说明,读时滤掉 —— 否则会被当成文件名,`ghosts` 假报一堆。)
## 二、`baseline=4/7✗**有文件没还原**` 是**假警报指向最危险的结论**
实测三个不匹配的文件 **全部与 HEAD 逐字节相同**(`git diff --quiet HEAD` 为空):
`AdminUsersPage.ets`/`SettingsPage.ets` 是提交 `6861934`(09-17 21:15)改的、
`ApiClient.ets` 是 `9c6e9c6` 改的 ⇒ **底本过期,不是变异残留**。
★ 而原来一律打"✗**有文件没还原**" —— 那会让人去翻变异,而真因只是底本没跟上提交。
**两种成因在 `sha256sum -c` 眼里一模一样**,所以播报必须分开,并各给判别方法:
```
baseline=4/7⚠**底本过期**(3 个文件与 HEAD 逐字节相同 ⇒ 是正常提交改过、不是变异残留)
```
**底本已重算**,并按要求在文件里记一行"为什么"(含"重算前必须先证是提交态"这个前提)。
**重算后仍在校验**(变异验证):给 `AppearanceApi.ets` 追加一行 ⇒ 立刻 `FAILED`;还原 ⇒ `7/7 OK`。
## 三、`summary.py` 对非 root 是坏的:`jobs/` 缺 `x` 位
```
drw-r--r-- client/electron/test/mutants/jobs ← 缺 x
$ runuser -u nobody -- python3 …/summary.py
PermissionError: [Errno 13] Permission denied: '…/jobs/jobs-all.json'
```
⇒ 那份"口径的唯一权威"**只有 root 跑得起来**。已 `chmod 755`,非 root 复跑输出一致。
★ pi 的深层判断成立且我核实了:**这条落在任何判据的射程之外** ——
`deploy/check-file-modes.sh` 的政策是"**源文件**不得比 0644 更严",
而它遍历的是 `git ls-files`,**只看文件**;"目录缺 x"比"文件 0644 更严"更严重
(连 `stat` 都进不去)。
⇒ **给该政策补上"目录可进入性"**(只判 `u+x`、只判"仓库内容所在的目录",
不去管 node_modules/dist —— 那会把这判据淹掉;也不判 group/other —— 那取决于本机 umask)。
**★ 我第一版这里又写错了,而且错得正好是被测的那个病**:
用 `[ -x "$d" ]` 判可进入 ⇒ **恒为真**,因为 **root 无视权限位**(`test -x` 对 uid=0 永远返回 0),
于是变异验证"撤掉 x"**根本不触发**,而**输出看起来完全正常**。
⇒ 改成**直接读权限位**(与同文件里文件那段的位运算同一做法)。
改后变异验证:撤 x ⇒ `[FAIL] 目录不可进入:…(drw-r--r--,缺属主 x 位)`、退出码 1;还原 ⇒ 0。
## 四、顺带修好三处既有权限违规(`check-file-modes.sh` 原来一直红着)
```
.githooks/pre-push 711 ⇒ 755(保留执行位,**不是**去掉)
test/harmony-arkts.test.mjs 600 ⇒ 644
model/DeviceProbe.ts 600 ⇒ 644
```
三者都是**提交态**就这样(不是并发会话弄的)。按脚本自己的"药方按类型分岔"修的。
现在该政策 **退出码 0**。★ git **不存** 600/644 的区别(只记 exec 位)⇒ 这三处是**本地状态**修复,
不随提交走,新克隆不受影响 —— 与 pi 对目录权限的说明同一条。
## 五、pi 报的另两件:**在 HEAD 上已经不存在了**(是过时读数)
- **ArkTS 两处阻断**:`MainPage.ets` 的 import 现在在 71-74 行、最后一个 `const` 在 88 行
⇒ **位置正确**;`AdminUsersPage.ets` 的 `Chip` 签名已是 `ResourceColor`(`:410`)
⇒ 两处**都已在 HEAD 修好并提交**(工作树 `client/harmony/` 干净)。`harmony-arkts` 判据 3/3 绿。
- **套件现状**:现在 **29** 个判据文件(不是 23),`unreported=0 broken=0`。
## 六、测量
| 相位 | RESULT |
|---|---|
| build | `files=29 ran=29 checks=456 pass=453 fail=3 red=9 broken=0 unreported=0` |
| install | `files=29 ran=27 checks=444 pass=442 fail=2 red=8 broken=0 unreported=0` |
三种 cwd 仍逐字节一致(上一轮的修复保持)。
139 lines
8.2 KiB
Bash
Executable File
139 lines
8.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 源文件的权限位**政策**:不许比 0644 更严。
|
||
#
|
||
# # 为什么要这条(一个实测出来的实例)
|
||
#
|
||
# 这台机器上写文件的工具**不理会 umask**(umask 是 022,但它建的仍是 0600)。
|
||
# 后果不是"本地难看",而是它会**进生产**:`deploy/redeploy-plugin.sh` 用
|
||
# `cp -a "$SRC/." "$STAGING/"` 打快照,`-a` 保留权限位 ——
|
||
# 于是快照里 `src/paths.mjs`/`src/turn-cwd.mjs` 是 **0600**,而仓库是 0644。
|
||
#
|
||
# 两头都不报警:
|
||
# · `cmp` 只比**内容** ⇒ 报"逐字节相同"(实测就是这样);
|
||
# · `check-deploy-drift.mjs` 只比**内容**、且只在脚本上判**可执行位**(`mode & 0o111`)。
|
||
# ⇒ "线上与仓库不一致"里,**只有权限位这一类**完全没有判据看着。
|
||
#
|
||
# # 与漂移侧那条的分工(pi 评审 2026-09-15 §三 明确要求分开)
|
||
#
|
||
# · **本脚本 = 政策**:源文件不得比 0644 更严。它**不**看快照,
|
||
# 所以"两边都是 0600"这种情况它能抓到,而漂移侧永远抓不到(两边一致 ⇒ 恒绿)。
|
||
# · **漂移侧 = 一致性**:部署副本的权限位 = 仓库那一份。
|
||
# 它抓不到"两边都错"。
|
||
# 两条别合成一条:合成的结果是"看起来覆盖了、其实只覆盖一半"。
|
||
#
|
||
# # 为什么"更严"也算错
|
||
#
|
||
# 这些文件是**源码**,要被构建/测试/部署以不同身份读(`install.sh` 会切身份做可写性判定,
|
||
# 判据里还有以 nobody 跑的分支)。0600 在"跑的人恰好是属主"时不炸,一旦换身份就是
|
||
# EACCES —— 而那串报错看起来是**代码问题**。这条链一路上在消的就是这一类。
|
||
set -uo pipefail
|
||
|
||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
cd "$REPO"
|
||
|
||
# 允许的最严权限:0644。比它更严(组/其他读位缺失)即判红。
|
||
# 例外:**可执行**脚本的 0755 不是"更严",它多了执行位、读位齐全,属正常。
|
||
# 允许的最严权限:0644(非可执行)/ 0755(可执行)。
|
||
#
|
||
# ★ 可执行与否按 **git 记录的那个位**判(`git ls-files -s` 的 100755),不按当前文件系统权限
|
||
# —— 否则"执行位被谁弄丢了"会被这条判据**追认**成正常。
|
||
#
|
||
# ★ 这条我第一版写错了,代价是**真的弄坏了一个脚本**:`client/electron/scripts/release-linux.sh`
|
||
# 原本是 **711**(group/other 读位缺失 ⇒ 本判据判红,判得对),但我给的药方是无脑
|
||
# `chmod 644` —— 那是**去掉执行位**,等于把"读位缺失"换成"不能执行",比原来更坏,
|
||
# 而判据随后报"通过"。⇒ 一条判据说"这个文件被改错了"时,**药方必须按文件类型分岔**;
|
||
# 只有一个药方就会把另一类文件改坏,且同样显示"已修好"。
|
||
fail=0
|
||
worst=""
|
||
count=0
|
||
while IFS= read -r line; do
|
||
gmode="${line%% *}"
|
||
f="${line##* }"
|
||
[ -n "$f" ] || continue
|
||
[ -f "$f" ] || continue
|
||
mode="$(stat -c '%a' "$f" 2>/dev/null)" || continue
|
||
m="${mode: -3}"
|
||
o="${m: -1}"; g="${m:1:1}"; u="${m:0:1}"
|
||
bad=0
|
||
# group 与 other 必须有读位
|
||
[ "$(( o & 4 ))" -eq 0 ] && bad=1
|
||
[ "$(( g & 4 ))" -eq 0 ] && bad=1
|
||
# git 记为可执行的,三处执行位都要在
|
||
if [ "$gmode" = "100755" ]; then
|
||
[ "$(( u & 1 ))" -eq 0 ] && bad=1
|
||
[ "$(( g & 1 ))" -eq 0 ] && bad=1
|
||
[ "$(( o & 1 ))" -eq 0 ] && bad=1
|
||
fi
|
||
[ "$bad" -eq 0 ] && continue
|
||
count=$((count + 1))
|
||
[ -z "$worst" ] && worst="$f"
|
||
if [ "$gmode" = "100755" ]; then
|
||
printf ' [FAIL] 权限不对:%s 是 %s(git 记为可执行 ⇒ 应为 0755,注意别去掉执行位)\n' "$f" "$mode" >&2
|
||
else
|
||
printf ' [FAIL] 权限过严:%s 是 %s(政策要求不严于 0644)\n' "$f" "$mode" >&2
|
||
fi
|
||
done < <(git ls-files -s | awk '{print $1" "$4}')
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# ★★ 目录的**可访问性**(2026-09-18,pi 报的洞)
|
||
#
|
||
# 上面那段只遍历 `git ls-files` ⇒ **只看文件**。而"目录缺 `x`"比"文件 0644 更严"**更严重**:
|
||
# 目录没有 `x` 就**不能访问其中任何文件**(连 stat 都不行),而 root 绕过这一层,
|
||
# 所以维护者**永远不会在自己身上看到症状**。实测(pi 报、我复现):
|
||
#
|
||
# $ stat -c '%A %n' client/electron/test/mutants/jobs
|
||
# drw-r--r-- client/electron/test/mutants/jobs ← 缺 x
|
||
# $ runuser -u nobody -- python3 …/summary.py
|
||
# PermissionError: [Errno 13] Permission denied: '…/jobs/jobs-all.json'
|
||
#
|
||
# ⇒ 那份"口径的唯一权威"在这份工作树里**只有 root 跑得起来**,而它落在**任何判据的射程之外**
|
||
# (本政策只看文件;权限位判据当初就是为"该不该算漂移"立的,覆盖的形状是"文件")。
|
||
#
|
||
# ⚠️ 范围与取向(**保守,只判"不可访问"**):
|
||
# · git **不存目录权限** ⇒ 这是**本地状态**,不跟着提交走、新克隆不受影响。
|
||
# 所以这里判的是"**这份工作树此刻能不能被别人用**",不是"仓库内容对不对"。
|
||
# · 只判**有 `git ls-files` 条目的目录**(即"仓库内容所在的目录"),
|
||
# 不去管 node_modules / dist 这类产物的目录 —— 那会把这条判据淹掉。
|
||
# · 只判 `u+x`(**属主**可进入):这是"别把自己锁在外面"的最低要求,
|
||
# 不去管 group/other —— 那取决于本机 umask 与身份,判严了会到处误红。
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
dir_bad=0
|
||
dir_worst=""
|
||
while IFS= read -r d; do
|
||
[ -n "$d" ] && [ -d "$d" ] || continue
|
||
# ⚠️⚠️ **不许用 `[ -x "$d" ]`** —— 这是我第一版写的,而它**恒为真**(我实测抓到的):
|
||
# root **无视权限位**(这正是本政策存在的理由之一),所以 `test -x` 对 uid=0
|
||
# **永远返回 0**,哪怕目录是 `drw-r--r--`。⇒ 变异验证时"撤掉 x"根本不触发,
|
||
# 而**输出看起来完全正常**(这正是被测的那个病本身长在判据上)。
|
||
# ⇒ 必须**直接读权限位**(与上面文件那段的 `o/g/u` 位运算同一个做法)。
|
||
dperm="$(stat -c '%a' "$d" 2>/dev/null)" || continue
|
||
dm="${dperm: -3}"
|
||
[ "$(( ${dm:0:1} & 1 ))" -eq 1 ] && continue # 属主有 x ⇒ 可进入,放行
|
||
dir_bad=$((dir_bad + 1))
|
||
[ -z "$dir_worst" ] && dir_worst="$d"
|
||
printf ' [FAIL] 目录不可进入:%s(%s,缺属主 x 位)\n' "$d" "$(stat -c '%A' "$d" 2>/dev/null)" >&2
|
||
done < <(git ls-files | awk -F/ 'NF>1 { for (i=1;i<NF;i++) { p=""; for (j=1;j<=i;j++) p = (p=="" ? $j : p"/"$j); print p } }' | sort -u)
|
||
|
||
if [ "$dir_bad" -gt 0 ]; then
|
||
printf '\n 共 %d 个目录不可进入(首个:%s)。\n' "$dir_bad" "$dir_worst" >&2
|
||
printf ' 药方:chmod +x <目录>(**只加执行位,别动读位**)—— 目录的 `x` 是"能进入/能 stat 里面"。\n' >&2
|
||
printf ' 为什么单列一条:目录缺 `x` 时,**里面每个文件都读不到**,而 root 绕过它 ⇒\n' >&2
|
||
printf ' 维护者在自己身上永远看不到症状,只有换个身份(或别人来跑)才炸。\n' >&2
|
||
printf ' git 不存目录权限 ⇒ 这是**本地状态**,不跟提交走(新克隆不受影响)。\n' >&2
|
||
printf ' 这是**政策问题**(工作树应当能被其它身份使用),不是代码问题。\n' >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [ "$count" -gt 0 ]; then
|
||
printf '\n 共 %d 个源文件权限不对(首个:%s)。\n' "$count" "$worst" >&2
|
||
printf ' 药方(**按类型分岔**):非可执行文件补齐到 644;git 记为 100755 的补齐到 755。\n' >&2
|
||
printf ' 原因:写文件的工具不理会 umask,而 `cp -a` 会把这个权限位带进生产快照,\n' >&2
|
||
printf ' 且 `cmp` / ① 内容判据都看不见它。\n' >&2
|
||
printf ' 这是**政策问题**(源文件应当可被构建/测试/部署以不同身份读),不是代码问题。\n' >&2
|
||
exit 1
|
||
fi
|
||
|
||
printf ' 源文件权限政策:通过(已跟踪文件都够读;git 记为可执行的都保留了执行位)\n'
|
||
exit 0
|
||
|