修复: baseline-absent 同时是"关掉残留检测"的开关 —— 一个 rm 就能让残留仍在而那条红整条消失

pi 2026-09-18 报的。**成立,我端到端复现了**,而且是这条线上同一形状的**最后一跳**。

## 一、缺陷:结论不是"到不了读者",是**被关掉了**

`baseline-absent` 原来是 `{note: null, blocksGreen: false}` —— **既不说话、也不阻断**,
而它同时是"**关掉残留检测**"的开关。同一处残留,**只切 `baseline.sha` 在不在**:

| | summary.py | 套件 | 红清单里有 residue 吗 |
|---|---|---|---|
| `baseline.sha` 在 | `diag=baseline-residue` | `red=10 … verdict=red` | **有** |
| **删掉** | `diag=baseline-absent` | `red=9 … verdict=red` | **没有**(整条消失、`rc=0`、静默) |

`git status` 里 ` D baseline.sha` 与 ` M <被改文件>` **同时都在** ⇒ **残留还在**,
而唯一能报告它的机制被一个 `rm` 关掉,**不需要特权、不会红、也不说话**。

前几轮分别是"结论到不了读者"、"结论到不了 `verdict`";**这次是"结论被关掉了"**。

## 二、修法:把"缺席"分成两态,并给非阻断码再分两类

1. **`baseline-deleted`(新码,退 1)**:`baseline.sha` **受版本控制却被删**
   ⇒ 那不是"可接受的配置",是**缺文件**(该恢复它)。判别用既有的东西、不新造:
   `git ls-files --error-unmatch` —— 三态:**0=受跟踪 / 1=没跟踪 / 128=git 答不了**,
   `128` 时**不许猜**,退回缺席语义但**必须说话**。
2. **`baseline-absent` 补 `note`**(原来 `null` ⇒ 连读者都看不到):
   说清"**这一格没检查**:没有底本 ⇒ 残留检测是关着的",并区分"本来就没跟踪它"与"判不出"。
   —— 这是 pi 的"最小版",我认为它**不足以**独当(沉默正是这个洞的成因),所以两条都做。
3. ★ **`DIAG` 每个码新增 `checked` 字段**(pi 那条建议的落地):
   · `checked: true` = "**检查了、结论无害**"(`baseline-stale`:可以静默);
   · `checked: false` = "**根本没检查**"(`baseline-absent`/`counts-unusable`/
     `baseline-unrunnable`/`baseline-unknown`:**必须说话**)。
   ⇒ 把 pi 的"应当分两类"从**文案要求**变成**结构字段**。

## 三★ 我加第一版时漏了一条判据,自己变异出来(M34)

`checked` 只是一个字段,**它的内容当时没有任何判据守着**:我把 `baseline-absent` 的
`note` 改回 `null`(= pi 说的"最小版没做"),`--verdict-selftest` **全绿** ——
因为此前的判据只管 `blocksGreen` 存不存在、两侧口径对不对。
⇒ 补第 ⑦ 条:**`checked: false` 的码必须有 `note`**,且**每个码都必须声明 `checked`**。
又一条"判据在,但走不到":字段加上了,没人查它的内容。

## 四、变异验证(都已还原)

| 变异 | 结果 |
|---|---|
| **M32** `baseline-deleted` 退回"当配置缺席"(**即 pi 报的那个洞本身**) | **红**:`UPSTREAM_RC[baseline-deleted]=1 与真跑出来的 rc=0 不符` |
| **M33** 关掉 `bl_tracked` 判别(一律当缺席) | **红** 2 条:端到端恢复 pi 那个洞(`diag=baseline-absent`)+ 反向覆盖报"该码没案例" |
| **M34** `baseline-absent` 的 note 改回 `null` | **红**:`这些码根本没检查、却一句话都不说:baseline-absent`(**上一版这里是 rc=0**) |
| **M35** 删掉某个码的 `checked` 字段 | **红**:`这些码没声明 checked:baseline-stale` |

## 五、端到端锚点(新增一条 `--exitcode-selftest` 案例)

`runIn` 加 `'deleted'` 模式:**先提交 `baseline.sha` 纳入跟踪、再把目标文件改脏、然后 unlink**
⇒ 判 `rc=1 且 diag=baseline-deleted`。这条**就是 pi 那个实验的自动化版本**:
残留是真的存在的,而底本被删了。反向覆盖随之报 `9 个码都有案例真跑验过`。

**真树端到端复核**(真残留 + 真删 `baseline.sha`):`red=10`(修复前 9)、`verdict=red`、
红清单里出现 `(summary.py)baseline-deleted —— 底本 baseline.sha **受版本控制却被删** ⇒ **残留检测已失效**`。

## 六、验证与状态

· 五个自检全 exit 0(`--verdict-selftest` 新增第 ⑦ 条;`--exitcode-selftest` 11→12 条案例、9 个码全覆盖)。
· 正常态与 pi 读数一致:`checks=459 pass=455 fail=4 skip=0 red=9 broken=0 unreported=0 verdict=red`、
  `mutants=48 ran=47 skipped=1 on_new_criteria=35 diag=none baseline=7/7✓`。
· `baseline.sha` 的 7 条都是鸿蒙源码文件,**不含** `summary.py`/`run-all.mjs`
  ⇒ 本次改动**不需要重算底本**(`baseline=7/7✓` 亦为证)。
· 残留全清;`git status` 仅这两个文件;`HEAD=2870d47`。
This commit is contained in:
2026-09-18 06:49:10 +08:00
parent 2870d479f8
commit 62297e3d83
2 changed files with 153 additions and 6 deletions

View File

@ -148,6 +148,38 @@ def main():
# ⇒ 两件事一起修:① `r` 先绑 None;② "**跑不了这项检查**"与"**检查了、没过**"
# 分成两种状态,不许合并(合并正是这个 bug 的成因)。
bl_unrunnable = False
# ★★ 缺席的**两种成因**必须先分开(pi 2026-09-18 实测到的那个洞)。
#
# 原来 `baseline.sha` 不在就一律 `baseline-absent` ⇒ 退 0、`note: null`、**连读者都看不到**。
# 而它同时是"**关掉残留检测**"的开关 —— 实测(pi 报、我复现):
# 同一处残留(`ImagePrep.ts` 末尾追加一行),**只切 `baseline.sha` 在不在**:
# 在 ⇒ `diag=baseline-residue`、`red=10`、红清单里**有**那条 `(summary.py)baseline-residue`
# 删掉 ⇒ `diag=baseline-absent`、`red=9`、那条红**整条消失**、`rc=0`、静默
# 而 `git status` 里 ` D baseline.sha` 与 ` M ImagePrep.ts` **同时都在**。
# ⇒ **残留还在,唯一能报告它的机制被一个 `rm` 关掉了**,不需要任何特权、不会红、也不说话。
#
# ⇒ 判法(pi 建议,且**不必新造**):`baseline.sha` 是**受版本控制**的
# (`git ls-files --error-unmatch` 有它)⇒ "跟踪着、却不在工作树里"**本身就是可疑**,
# 那是 ` D`(被人删了),不是"可接受的配置"。
# 只有"**本来就没跟踪它**"(别人 clone 的变体 / future)才保留"缺席即配置"的语义。
#
# ★ 判别是三态(与我修 `git diff --quiet` 那次同形):
# rc=0 ⇒ 受跟踪 ⇒ 删除 ⇒ **缺文件**(该改的是仓库/数据)
# rc=1 ⇒ git 说没跟踪它 ⇒ 真·配置缺席
# rc=128 ⇒ git 答不了(非仓库)⇒ **不许猜**,退回"配置缺席"但**必须说话**
bl_tracked = None # None=判不了;True=受跟踪;False=没跟踪
if not os.path.exists(bl):
try:
tr = subprocess.run(['git', 'ls-files', '--error-unmatch', '--',
os.path.relpath(bl, REPO)],
cwd=REPO, capture_output=True, timeout=30)
if tr.returncode == 0:
bl_tracked = True
elif tr.returncode == 1:
bl_tracked = False
# 其它(128 等)⇒ 保持 None = 判不了
except Exception: # noqa: BLE001 —— 判不了就退化为"配置缺席 + 说话"
bl_tracked = None
if os.path.exists(bl):
try:
r = subprocess.run(['sha256sum', '-c', bl], cwd=REPO,
@ -171,8 +203,20 @@ def main():
bl_note = (f' baseline=(**跑不了 sha256sum 校验**:{bl_unrunnable})'
f'—— 这一格**不是**"底本对"也**不是**"有残留",是**没读数**')
elif baseline_ok is None:
bl_diag = 'baseline-absent'
bl_note = ' baseline=(没有 baseline.sha)'
# ★★ 缺席分成两态(见上面 `bl_tracked` 那段的总说明)。
if bl_tracked is True:
# 受跟踪却被删 ⇒ **不是配置,是缺文件**(谁把它删了?残留检测因此关掉了)
bl_diag = 'baseline-deleted'
bl_note = (' baseline=(**底本被删了**:`baseline.sha` 受版本控制,'
'却不在工作树里 ⇒ **残留检测已失效**)')
else:
bl_diag = 'baseline-absent'
# ★ 即使是"真·配置缺席",也**必须说话** —— 原来这里是 `note: null`、连读者都看不到。
# "这一格没检查"与"检查了、结论无害"**不是一回事**(本仓反复立的那条:
# "跑不了" ≠ "检查了没过"),所以它不能再靠沉默通过。
bl_note = (' baseline=(**没有底本 ⇒ 这一格没检查**:'
+ ('判不出它是不是该受跟踪' if bl_tracked is None else '本来就没跟踪它')
+ ',但**残留检测是关着的**)')
else:
good, ok, total = baseline_ok
# ★ 措辞必须区分**两种完全不同的成因**(2026-09-18 实测):
@ -259,7 +303,15 @@ def main():
# baseline-residue 真·变异残留(与 HEAD 也不同)⇒ 1
# baseline-stale 底本过期(与 HEAD 相同)⇒ 0,但**必须让读者看见**
# baseline-unrunnable 跑不了 sha256sum ⇒ 2(没读数)
# baseline-absent 没有 baseline.sha ⇒ 0(这是可接受的配置)
# baseline-deleted `baseline.sha` **受跟踪却被删** ⇒ 1(缺文件,不是配置)
# baseline-absent **本来就没跟踪它** ⇒ 0,但**必须说话**
#
# ★★ `baseline-deleted` 与 `baseline-absent` 是这一轮(pi 2026-09-18)新分出来的:
# `baseline-absent` 原来同时是"**关掉残留检测**"的开关 —— 一个 `rm` 就能让
# 残留仍在、而唯一能报告它的那条红**整条消失**(实测 `red 10→9`、`rc=0`、静默)。
# ⇒ 按本仓反复立的那条口径分家:**"检查了、结论无害"**(`baseline-stale`)
# 与 **"根本没检查"**(`baseline-absent`)**不该同格**;
# 而"受跟踪却被删"更重一档 —— 它连"配置"都不是,是**缺文件**。
if blind:
print('RESULT mutants=NO-READING ran=NO-READING skipped=NO-READING '
'on_new_criteria=NO-READING diag=counts-unusable'
@ -385,7 +437,10 @@ def main():
# 与 `baseline-unrunnable` 同类 —— **没读数**,不是"有残留"。
if blind or unreadable or bl_diag in ('baseline-unrunnable', 'baseline-unknown'):
return 2
if unlisted or ghosts or bl_diag == 'baseline-residue':
# ★ `baseline-deleted` 归 **1**(pi 建议):`baseline.sha` 是**受版本控制**的,
# "跟踪着却不在工作树里"就是 ` D` —— 该改的是**仓库/数据**(把它恢复),
# 不是环境、也不是"没读数"。与 `manifest-mismatch` 同类(都是"清单/数据该修")。
if unlisted or ghosts or bl_diag in ('baseline-residue', 'baseline-deleted'):
return 1
return 0