修复: 第四个落点 —— **变异条目的锚点失效**(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

@ -335,7 +335,7 @@ def main():
#
# 顺序按**严重度递减**:
# 2 档(**没读数**,连是环境还是代码都判不了):blind / unreadable / baseline-unrunnable|unknown
# 1 档(**数据/清单该修**):unlisted / ghosts / baseline-residue|deleted
# 1 档(**数据/清单该修**):unlisted / ghosts / mutant-anchor-stale / baseline-residue|deleted
# 0 档:其余(`baseline-stale`/`baseline-absent` 仍带码,但**不必红**)
if blind:
severity = ('counts-unusable', 2)
@ -347,6 +347,34 @@ def main():
severity = ('manifest-mismatch', 1)
elif bl_diag in ('baseline-residue', 'baseline-deleted'):
severity = (bl_diag, 1)
# ★★ 第五类(dsh 2026-09-19,**同一形状的第四个落点**):**变异条目的锚点失效**。
#
# 这一格原来**只打印**(上面那段 `hits=0 … 过期条目`)、**完全不进严重度链**
# ⇒ `rc=0`、`diag=none` ⇒ 套件那边 `whyLines: (note || status!==0) ? … : []`
# 把它整段丢掉。端到端实测(真树、干净工作树):
# `summary.py` 说「hits=0 … 过期条目」 · 套件输出里 grep **0 次** · `rc=0`
# ⇒ 后果不是"数字错",是**一个变异守具被静默关掉**:
# 实测那一条(`SettingsPage.ets` 的「管理入口不做门禁」)锚点写成 6 空格,
# 而 `f4b8bc1`(09-17 17:43)把该文件重排成 4 空格 ⇒ 锚点从此命中 **0** 次、
# `ran` 少 1(47 而非 48)、`skipped=1`、**没有任何东西变红**;而手工施加那个
# 变异**仍能让判据红**(`harmony-admin.test.mjs` `# fail 1`)
# —— 也就是说**守具有齿,只是不再被挂上**。
#
# 归 **1** 档(不是 2):锚点过期是**清单/数据该改**(去改 `jobs/jobs-*.json`
# 里的锚点,或标 `retired`),与 `manifest-mismatch` 同类;
# 与"权限不够读不到"(2 档)**不是一回事** —— 照 `env-defaults.sh:25` 的
# 反方向:**别让"清单没跟上"冒充环境**。
# ★ 放在 `residue`/`deleted` **之后**:同为 1 档时,让最危险的那两个码优先报出来
# (锚点过期只是"守具没挂上",残留是"守具正在被绕过")。
# ★ 判据只看 **`hits == 0`**(旧的锚点写法漂移了 ⇒ 去改锚点),**不看 `hits == -1`**:
# `-1` 是"目标文件打不开"(文件没了/权限),与"锚点写法过期"是两回事,
# 而且 `--exitcode-selftest` 的迷你夹具里 `jobs-one.json` 指向的文件**本来就不在**
# (那是夹具的设计,不是仓库的缺陷)⇒ 把 `-1` 也算进来会造出**假红**。
# ⚠️ 如实标出射程:**`-1` 那一半没有判据守着**(真树实测 0 条,故暂不覆盖)。
elif any(h == 0 for _, _, h in skipped_detail):
severity = ('mutant-anchor-stale', 1)
elif bl_diag in ('baseline-residue', 'baseline-deleted'):
severity = (bl_diag, 1)
elif bl_diag != 'none':
severity = (bl_diag, 0)
else: