修复: 第四个落点 —— **变异条目的锚点失效**(hits=0):守具有齿,却不在位

pi `6aa2b17f` 报的第三例(`unlisted`/`ghosts` 退 0)**我复核后已经在 `6ee9902` 修好了**
—— 而它的**父提交正是 pi 报信时读的 `4b841e0`**(差 11 分钟)。又是同一形状的竞态。

但顺着同一条线**审计全部 61 个变异条目的锚点**,发现**还有一个同形状的落点,
而且它在真树上是活的**(不是构造的):

    hits=0  SettingsPage.ets 「管理入口不做门禁」(jobs-all.json)

根因:该锚点写的是 **6 空格**,而 `c523c21`(09-17 17:43)把该文件**重排成 4 空格**
⇒ 锚点从此命中 0 次(写进清单时 `bcd4f97`(09-15)它是**对的**)。

为什么没人发现:`summary.py` 把「hits=0 … 过期条目」**只打印**、**不进严重度链**
⇒ `rc=0`、`diag=none` ⇒ 套件 `whyLines: (note || status!==0) ? … : []` 为假 ⇒
**整段丢掉**。端到端实测(真树、干净工作树):套件输出里 grep「过期条目」= **0 次**。

★ 危害是"**一个变异守具被静默关掉**",不是"数字错了":`ran` 少 1、`skipped=1`
是个**中性数字**,读者看不出少了哪一个。而**手工施加那个变异仍能让判据红**
(`harmony-admin.test.mjs` `# fail 1`)⇒ **有齿,只是没挂上**。

修法(沿用 `summary.py` 的**唯一严重度链**):
  · 新增 `mutant-anchor-stale` ⇒ **1 档**(清单/数据该改;**不是** 2 档 ——
    照 `env-defaults.sh:25` 的反方向:别让"清单没跟上"冒充环境)。
  · 修锚点 6→4 空格 ⇒ `ran` 47→48、`skipped` 1→0,守具重新挂上(实测变异能红)。
  · `whyLines` 过滤器加 `^\s*hits=`:只说"有锚点过期"不够,**点名的才是可行动的**。
  · 两张表都登记(`UPSTREAM_RC` + `DIAG`,`blocksGreen: true`)+ 真跑案例
    (迷你仓库里放一个不含锚点的同名文件 ⇒ `hits=0`)+ 读者侧具名案例。

★ 射程如实标出:判据只看 **`hits == 0`**,**不看 `hits == -1`**(文件打不开是另一回事,
且迷你夹具里目标文件本来就不在 ⇒ 算进来会造假红)。**`-1` 那一半无判据守着**(真树 0 条)。

变异验证(4 个方向全抓):
  ① 新档关闭(回到"只打印")⇒ exitcode-selftest rc=1、2 条红 ✓
  ② 从 UPSTREAM_RC 删掉新码 ⇒ 反向覆盖点名 ✓
  ③ blocksGreen true→false ⇒ 双向口径漂移红 ✓
  ④ 放宽成任何 skipped_detail(含 hits=-1)⇒ rc=1、**5 条红**(夹具假红)
     ⇒ **`h == 0` 这个射程是承重的** ✓

端到端 A/B(隔离 worktree,同刻对照):
  A 锚点已修  ⇒ rc=0、diag=none、ran=48/skipped=0、"锚点已失效" grep **0**
  B 锚点退 6 格 ⇒ rc=1、diag=mutant-anchor-stale、ran=47/skipped=1、grep **2**

★ 通用规则(本仓第 4 次同一形状):**"跑了多少个"与"该跑多少个"之间也要有判据。**
被跳过时 `ran` 只少 1、`skipped` 只多 1 —— 都是中性数字,而"少了哪一个"没有通道。
凡"登记一批东西、再逐个挂上"的结构(变异条目、判据、样本表)都要问:
**挂不上的那一个,谁来说?**

自检 5 个各 rc=0;全套 checks=516 pass=511 fail=5 red=5 verdict=red;
mutants=48 ran=48 skipped=0 on_new_criteria=36 diag=none baseline=7/7✓。
CRITERIA.md §16.1.1 记这一笔(含 4 个变异与 A/B 表)。
This commit is contained in:
2026-09-20 04:49:50 +08:00
parent c1465e09ab
commit efb1c1d2ca
4 changed files with 165 additions and 4 deletions

View File

@ -900,6 +900,75 @@ Go 在同一包内**按源文件顺序**跑测试 ⇒ 登记那条先跑就读
| "未列入清单" 在套件输出 | grep **0** | grep **1** |
| `(summary.py)manifest-mismatch` 红 | **0** | **1** |
#### 16.1.1 ★★ 第四个落点:**变异条目的锚点失效**(`hits=0`)—— 守具有齿,却不在位(dsh 2026-09-19)
pi 报的第 1–3 例都修好之后,我顺着同一条线**审计全部 61 个变异条目的锚点**,
发现**还有一个同形状的落点**,而且它在**真树上是活的**(不是构造的):
```
活跃条目 61 个 · 锚点命中 ≠ 1 的:**1 个**
hits=0 client/harmony/.../pages/SettingsPage.ets 「管理入口不做门禁」(jobs-all.json)
```
**根因**:该锚点写的是 **6 空格**(` if (this.isAdmin) {`),
而 `f4b8bc1`(09-17 17:43,"邮件详情与「我的」页 1:1 对齐 WebUI")把该文件**重排成 4 空格**
⇒ 锚点从此命中 **0** 次。写进清单时(`e2f117f`,09-15)它是**对的**(当时 6 空格命中 1 次)。
**为什么没人发现**:`summary.py` 把「hits=0 … 过期条目」**只打印**,
**完全不进严重度链** ⇒ `rc=0`、`diag=none` ⇒ 套件那边
`whyLines: (note || status !== 0) ? … : []` 为假 ⇒ **整段丢掉**。
端到端实测(真树、干净工作树):
| | 修前(锚点 6 空格) | 修后(锚点 4 空格) |
|---|---|---|
| `mutants= / ran= / skipped=` | `48 / 47 / 1` | `48 / **48** / **0**` |
| `diag=` | `none` | `none`(无此码,因为已修) |
| "过期条目"在套件输出 | grep **0** | — |
★ **危害是"一个变异守具被静默关掉",不是"数字错了"**:
`ran` 少 1、`skipped=1` 是个**中性数字**,读者看不出少了哪一个、也不知道少了。
而**手工施加那个变异仍能让判据红**(`harmony-admin.test.mjs` `# fail 1`,
`★ 管理入口没有 isAdmin 门禁 ⇒ 每个普通用户都会看到一个点进去 403 的入口`)
⇒ **守具有齿,只是不再被挂上**。
**修法**(与第 3 例同一条链,`summary.py` 的**唯一严重度链**):
新增 `mutant-anchor-stale` ⇒ **1 档**(清单/数据该改),
不是 2 档(那才是环境问题)——照 `env-defaults.sh:25` 的**反方向**:别让"清单没跟上"冒充环境。
★ **射程如实标出**:判据只看 **`hits == 0`**,**不看 `hits == -1`**。
`-1` 是"目标文件打不开"(文件没了/权限),与"锚点写法过期"是两回事,
且 `--exitcode-selftest` 的迷你夹具里 `jobs-one.json` 指向的文件**本来就不在**
(夹具设计,不是缺陷)⇒ 把 `-1` 算进来会造**假红**。
**`-1` 那一半目前没有判据守着**(真树实测 0 条)。
**端到端 A/B**(dsh,隔离 worktree,同刻对照):
| | A:锚点已修(4 空格) | B:锚点退回 6 空格(制造 hits=0) |
|---|---|---|
| `summary.py` rc | `0` | **`1`** |
| `diag=` | `none` | **`mutant-anchor-stale`** |
| `ran= / skipped=` | `48 / 0` | `47 / 1` |
| "锚点已失效"在套件输出 | grep **0** | grep **2** |
**变异验证**(dsh,4 个方向全部抓住):
| 变异 | 结果 |
|---|---|
| 新档关闭(`elif False`)=回到旧的"只打印" | `exitcode-selftest` rc=1、2 条红(`rc=0(期望 rc=1`)✓ |
| `UPSTREAM_RC` 删掉新码 | 反向覆盖点名 + 案例不符 ✓ |
| `blocksGreen: true` → `false` | `两侧口径漂移(双向比对 UPSTREAM_RC ↔ blocksGreen)` ✓ |
| 判定放宽成任何 `skipped_detail`(含 `hits=-1`) | rc=1、**5 条红**(迷你夹具假红)⇒ **`h == 0` 这个射程是承重的** ✓ |
★★ **通用规则(本仓第 4 次同一形状,值得单列)**:
> **"跑了多少个"与"该跑多少个"之间,也要有一条判据。**
> 一个变异/检查被**跳过**时,`ran` 只少 1、`skipped` 只多 1 —— 两个**中性数字**,
> 而"少了哪一个、为什么少"没有任何通道。
> ⇒ 凡有"登记了一批东西、再逐个挂上"的结构(变异条目、判据、样本表),
> 都要问一句:**挂不上的那一个,谁来说?**
> 同族的第三个自查问(pi 归纳):"守着它的判据,它自己的样本由谁守";
> 这一条是它的**镜像**:"**被守的那个东西没挂上时,谁来说**"。
### 16.2 ★★ **同一优先级写了两遍 ⇒ 两个顺序**:不变式只在**对角线**上被验过(pi + dsh 2026-09-18)
§16.1 讲"结论到不了决定颜色的那一格"。这一节是它的**镜像**: