Files
MailUI4Agents/client/electron/test
JianFeeeee d07494e8fd 修复: "该红的红"没到达 verdict —— summary.py 的 status 只喂了文案,没喂决定颜色的那一格
pi 2026-09-18 本封。它读代码 + 端到端对照得出的结论**成立**,我独立复核了:
`verdict` 的四条输入(`reds`/`brokens`/`dueFailed`/`selfCheckFailed`)**没有一条**被
`sp.status` 喂过 —— 全部消费者只有两处、都是文本(`mutantsLine` 一行、`whyLines` 几行打印)。
⇒ 退 1 或 2 **只改措辞、不改颜色**;把别的红清掉之后就会出现
`verdict=green` 与 `diag=baseline-residue`("优先按变异残留查")**并排**。

(★ 比我上一轮修的那几处**好一档**:读者**看得见**了 —— 前几例是连读者都看不到。
  但"变异可能没还原"坐在 green 旁边,仍是不能不管的形状。)

## 一、修法:诊断码表同时决定"红不红",接线**只此一处**

1. `DIAG` 表改成 `{ note, blocksGreen }` —— **一个码一行,两个字段一次定完**。
   分开写就是"同一事实两份实现"(本仓已反复消:`blurStyleFor`、`stripComments`、
   `want.why`、`.tmp` 台账路径)。放一起的结构性好处:**加一个新码就必须同时决定它红不红**。
   该红:`counts-unusable` / `manifest-mismatch` / `baseline-residue` /
   `baseline-unrunnable` / `baseline-unknown`(= **退非零的全部**);
   不红:`baseline-stale` / `baseline-absent` / `none`(stale 不是缺陷,退非零会让正常提交天天假红)。
2. `summarizeMutants` 多返回一个 `red`(人类可读的一句)。**不认识的码也红** ——
   上一版这里只改了**措辞**(打"请同步 DIAG")却仍然可以绿,**同一条缝的又一跳**。
3. `reds.push(r.red)` 走 pi 建议的那条路径(运行器自己推的红);
   顺手补上**连 `summary.py` 都起不来**那一支(原来只改文案 ⇒ 整个读数器没跑起来也照样能绿)。

## 二★★ `verdict` 判定抽成纯函数 ⇒ **pi 说"代价不值"的那个实验变得可构造**

pi 明说它没做"清掉全部红后 `verdict` 真变绿"的完整实验,因为要动 SUITE 登记数、会撞接线自检。
⇒ 把判定抽成 `verdictOf({reds,brokens,dueFailed,selfCheckFailed})`,那个实验**只要传空数组**:

```
情形            verdict   reds   该不该绿
正常            green     0      绿   ✓
残留            red       1      非绿 ✓     ← 修复前这里是 **green**(缺陷本体)
过期            green     0      绿   ✓
读数器没跑起来    red       1      非绿 ✓
```

★ **修复前的反面对照**(把接线退回"只改措辞"):残留 = `green`、读数器没跑起来 = `green`
⇒ 正是 pi 描述的那个"并存",**现在能跑出来了**。

## 三★★ 加自检时我自己的两处错(都当场红,照实记)

1. **自检验的是副本**:第一版自检自己写 `reds = r.red ? [r.red] : []`。
   ⇒ 我把生产那行 `if (r.red) reds.push(r.red);` **整行删掉**,自检**照样全绿**(实测 M24)——
   因为它验的是**自己复刻的接线**。本仓老形状(`parseSkips` 抄份、`want.why` 现算一遍)第 N 次。
   **修法不是再加断言,而是让接线住进被测函数**:抽出 `applyMutants(sp, reds)`,
   生产与自检调**同一个它** ⇒ M24 重做后**红 7 条**。
2. **结构锚点假红 + 报文自相矛盾**:
   · `indexOf('let mutantsLine')` 先命中**注释里提到这串字**的地方 ⇒ 扫的区间多包一个自检块
     ⇒ 报"1 处直接调"假红。改用 `lastIndexOf`(与自检 4 当年同一个坑,结论照抄)。
   · 结构锚点**无条件**打 `ok` ⇒ M27 的输出长成 `RED … RED … ok 结构锚点:…`,
     一个 `ok` 跟在自己的红后面。改成"没红才打 ok"。

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

| 变异 | 期望 | 结果 |
|---|---|---|
| **M24** 删掉 `applyMutants` 里的 push(上一轮逃掉的那个) | 红 | **红 7 条**(修法①之后) |
| **M25** `baseline-residue` 的 `blocksGreen` 改 false | 红 | **红**,`残留` 报 green |
| **M26** `verdictOf` 恒返回 green | 红 | **红 7 条** |
| **M27** 生产绕过 `applyMutants`(直接调 `summarizeMutants`) | 红 | **红 2 条**(结构锚点抓住) |
| **M28** 端到端:让 `summary.py` 不存在 / `PATH` 无 `python3` | 红 | **红**(`red=10`、`verdict=red`) |

★ **M28 顺带查清一件事并写进注释**:`catch` 那一支**本机覆盖不到** ——
`summary.py` 不存在 ⇒ `spawnSync` 退 **2**(python 自己报错);`PATH` 里没有 `python3`
⇒ **`spawnSync` 不抛**,返回 `status=null, error.code='ENOENT'` —— 两条都走 `!m` 那一支。
`spawnSync` 只在参数非法时才同步抛。⇒ 照 pi 那句"前提构造不出来就写明":
**catch 那条红是兜底、不是实测通路**,注释里说清哪条是实测的(`!m`,两条都端到端验过)。

## 五、口径固定(回 pi §五)

`stale ⇒ 提示(rc=0,不进 verdict)` / `residue ⇒ 红(rc=1,进 verdict)` ——
pi 说这条口径要等 §四 修完才能真正固定,**现在固定了**:`--verdict-selftest` 里
`baseline-stale` 判"**可以**绿"、`baseline-residue` 判"**不许**绿",两条同时绿才算数。

## 六、验证与状态

· **五个自检全 exit 0**(新增 `--verdict-selftest` **12 例**:9 个情形 + 码表字段完整性 + 口径一致性 + 结构锚点)。
· 全套件 `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✓` —— 与 pi 读数一致。
· 端到端对照:残留态 `red=9 → red=10`(多出的那条点名 `(summary.py)baseline-residue`);正常态仍 `red=9`。
· 残留全清;`git status` 仅本文件;`HEAD=93dabb4`。
2026-09-18 06:20:36 +08:00
..