修复: --verdict-selftest 第⑤条"口径一致性"**没读上游** —— 注释说"不写死",实现恰恰写死了一份码名单;两个方向都失真

pi 2026-09-18 报的。**成立**,我按它给的两个构造各复现一次,两个方向都坏。

## 一、缺陷本体:注释与实现相反

注释(原话):

> 这条把 `summary.py` 的退出码分类与 `run-all` 的表**绑在一起**验(**不在一边写死另一边的值**)。

实现:遍历 `DIAG` 表 + 跳过三个**硬编码**码名
(`if (code === 'none' || code === 'baseline-absent' || code === 'baseline-stale') continue;`)
—— **一个字节都没读 `summary.py`**。

⇒ 它实际断言的只是"**除了我手列的这三个,其它码都必须 `blocksGreen: true`**"。
**注释说"不写死",实现恰恰就是写死了一份名单。** 它是"按字面量裁射程"的又一例,
只不过这次长在**自检里的一条断言**上,而报文写着"两侧口径漂移",
读的人不会怀疑它其实没查上游。

## 二、两个方向都实测失真(都已复现)

· **假红**:加一个**上游退 0 的提示码**(与 `baseline-stale` 同类、完全合法)
  ⇒ 报 `上游退非零,下游却不红 ⇒ 两侧口径漂移`、rc=1。
  **那个码的上游根本不存在**(`summary.py` 永远不会打出它)——
  ⑤ 在这里**编造了一个上游事实**并据此判红。
· **假绿**:加一个 `blocksGreen: true` 的新码 ⇒ **rc=0,一条 RED 都没有**。
  同一句"上游退非零"也是编的 ⇒ 漂移从这个方向**完全看不见**。

## 三、修法:把 `(diag, rc)` 抽成唯一权威,链条每一跳都有判据

新增 `UPSTREAM_RC`(码 → 退出码),然后:

1. **`--exitcode-selftest` 把它逐个真跑验一遍**:每条案例用**真脚本真跑出来的 rc**
   去核对那张表,并**反向覆盖**——表里每个码都必须有案例真跑过,否则报
   "表里的值是没人验过的"。(这一跳是地基:若 `UPSTREAM_RC` 只是手抄的希望值,
   ⑤ 拿它比 `blocksGreen` 仍可能**两边一起错而且自恰地绿**。)
2. **⑤ 改为双向比对** `UPSTREAM_RC ↔ blocksGreen`:
   上游非零却不红 ⇒ 漂移;上游 0 却标阻断 ⇒ 也漂移(**上一版查不到的方向**);
   码没登记上游 ⇒ 红("加码要同时登记它退几",与 `blocksGreen` 那条"缺字段即红"是同一句纪律的另一半)。
   **白名单随之消失**(数据说话)。

★ 链条现在是 **真脚本 → `UPSTREAM_RC` → `DIAG.blocksGreen`**,两个方向真绑上了。

## 四、我写第一版时又犯的两处(都当场红)

1. **按"声明的"码去找**:我第一版用 `wantOut` 取码 ⇒ `wantOut=null` 的四条
   (一致 / 说明条目 / 未列入清单 / 清单有磁盘无)**一个码都没验到**,
   反向覆盖当场报出 `none`、`counts-unusable`、`manifest-mismatch` 三个码"没人验过"。
   而这三个码真跑出来是 0 / 2 / 1(我单独量过,**表里是对的**)⇒ **是验法漏了,不是值错了**。
   ⇒ 改成从**脚本真打出来的 stdout** 里取 `diag=` —— "验的是它真说的",而不是"我以为它会说的"。
2. **报文自相矛盾**:⑤ 无条件打 `ok` ⇒ 变异 (a) 输出长成
   `RED DIAG 里这些码没在 UPSTREAM_RC 登记…` 紧跟 `ok 口径一致(双向):9 个码…`。
   **与结构锚点那次是同一个错,我又犯了一遍** ⇒ 改成"这一格没红才打 ok"。

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

| 变异 | 期望 | 结果 |
|---|---|---|
| **(a)** 加一个上游退 0 的提示码 | 红 | **红**,报文说"**没登记上游退出码**"(不再编造"上游退非零") |
| **(b)** `baseline-stale` 标成 `blocksGreen: true` | 红 | **红**:`两侧口径漂移:baseline-stale(上游 rc=0,却 blocksGreen=true)` —— **上一版完全看不见的方向** |
| **(c)** `UPSTREAM_RC['baseline-residue']` 改成 0(与真脚本不符) | 红 | **红**:`UPSTREAM_RC=0 与真跑出来的 rc=1 不符` |
| **(d)** `UPSTREAM_RC` 里塞一个 `DIAG` 没有的码 | 红 | **红**:`两张表漂移` |

## 六、验证与状态

· 五个自检全 exit 0。`--exitcode-selftest` 现在逐条报"真打出 diag=…,UPSTREAM_RC=… ✓",
  并断言 `UPSTREAM_RC 的 8 个码都有案例真跑验过`;`--verdict-selftest` 报
  "口径一致(双向):8 个码逐条相符,且无未登记/多余码"。
· 全套件 `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 读数一致。
· 残留全清;`git status` 仅本文件;`HEAD=d07494e`。
This commit is contained in:
2026-09-18 06:30:35 +08:00
parent d07494e8fd
commit 8614903bc9

View File

@ -779,6 +779,44 @@ if (process.argv.includes('--probe-selftest')) {
process.exit(bad ? 1 : 0);
}
/*
* ★★ **上游退出码的唯一权威**:`diag` 码 → `summary.py` 真跑出来的退出码。
*
* pi 2026-09-18 报的那条:`--verdict-selftest` 第 ⑤ 条**注释**说
* "把 `summary.py` 的退出码分类与 `run-all` 的表**绑在一起**验(**不在一边写死另一边的值**)",
* 而它的**实现**只遍历 `DIAG` 表 + 跳过三个**硬编码**的码名:
* `if (code === 'none' || code === 'baseline-absent' || code === 'baseline-stale') continue;`
* —— **一个字节都没读 `summary.py`**,它断言的其实是"除了我手列的这三个,其它码都必须红"。
* 即:**注释说"不写死",实现恰恰写死了一份码名单。**
*
* 两个方向都实测失真(我按 pi 的构造各做了一次):
* · **假红**:加一个**上游退 0 的提示码**(与 `baseline-stale` 同类、完全合法)
* ⇒ 报 `上游退非零,下游却不红 ⇒ 两侧口径漂移`。**那个码的上游根本不存在**,
* `summary.py` 永远不会打出它 —— ⑤ 在这里**编造了一个上游事实**并据此判红。
* · **假绿**:加一个 `blocksGreen: true` 的新码 ⇒ **rc=0,一条 RED 都没有**。
* 同一句"上游退非零"也是编的,⑤ 没验也验不了 ⇒ 漂移从这个方向完全看不见。
*
* ⇒ 修法(照 pi 的建议):把 `(diag, rc)` 抽成**唯一权威**本表,然后
* ① `--exitcode-selftest` **真跑** `summary.py`,逐条断言 `rc === UPSTREAM_RC[diag]`
* ⇒ 本表的**每一个值都被真实脚本验过**(不是我在表里写的希望值);
* ② `--verdict-selftest` 第 ⑤ 条拿本表与 `DIAG.blocksGreen` **双向**比对
* ⇒ 两个方向都真绑上,**白名单消失**(数据说话)。
* 链条:**真脚本 → UPSTREAM_RC → DIAG.blocksGreen**,每一跳都有自检守着。
*
* ★ 加一个新码 ⇒ 必须在 `UPSTREAM_RC` 里登记"它退几",
* 否则 ⑤ 报"没登记上游"(与 `blocksGreen` 那条"缺字段即红"是同一句纪律的另一半)。
*/
const UPSTREAM_RC = {
'none': 0,
'counts-unusable': 2,
'manifest-mismatch': 1,
'baseline-residue': 1,
'baseline-stale': 0,
'baseline-unrunnable': 2,
'baseline-unknown': 2,
'baseline-absent': 0,
};
/*
* summary.py 退出码契约自检(`--exitcode-selftest`):**跑真的那个脚本**。
*
@ -1010,6 +1048,7 @@ if (process.argv.includes('--exitcode-selftest')) {
['jobs-one.json'], ['jobs-one.json'], 0, 'diag=baseline-absent', 'nobaseline'],
];
let bad = 0;
const seenDiags = []; // 反向覆盖用:本组案例**真跑出来**的码(不是声明的)
// 前提构造不出来 ⇒ 明说 + 计红(不许静默跳过)
if (!dropTo) {
console.log('RED 降权工具(runuser/setpriv)不可用 ⇒ **rc=2 的两条分支本机覆盖不到** —— ' +
@ -1034,11 +1073,61 @@ if (process.argv.includes('--exitcode-selftest')) {
}
const okStatus = got.status === want;
const okOut = wantOut === null || got.out.includes(wantOut);
const ok = okStatus && okOut;
/*
* ★★ 顺带把 `UPSTREAM_RC` **逐个真跑验一遍**(pi 2026-09-18 本封)。
*
* 这一跳是整条链的地基:`UPSTREAM_RC` 名义上是"上游退出码的唯一权威",
* 但若它只是我**手抄**的一张表,那 ⑤ 拿它去比 `blocksGreen` 仍然可能两边一起错
* (而且会**自恰**地绿 —— 本仓消过很多次的那种)。
* ⇒ 每条案例若声明了 `diag=<码>`,就用**真脚本真跑出来的 rc** 去核对那张表。
* 于是表里**每一个值都是被真脚本验过的**,不是我希望它等于几。
* (案例没带 `diag=` 的,只判上面的 rc/输出 —— 它们守的是别的性质,如 ghosts/unlisted。)
*/
/*
* ⚠️ 第一版我按**声明**的 `wantOut` 去找码 —— 于是 `wantOut` 是 `null` 的那四条
* (一致 / 说明条目 / 未列入清单 / 清单有磁盘无)**一个码都没验到**,
* 反向覆盖当场报出 `none`、`counts-unusable`、`manifest-mismatch` 三个码"没人验过"。
* 而这三个码真跑出来是 0 / 2 / 1(我单独量过)—— 表里写的是对的,**是验法漏了**。
* ⇒ 改成从**脚本真打出来的 stdout** 里取码:这样"验的是它真说的",而不是"我以为它会说的",
* 且对 `wantOut=null` 的案例同样有效。
*/
let rcCheck = '';
const seenDiag = (got.out.match(/diag=([\w-]+)/) || [])[1];
if (seenDiag) seenDiags.push(seenDiag);
if (seenDiag) {
if (!(seenDiag in UPSTREAM_RC)) {
rcCheck = `;**新码 \`${seenDiag}\` 没在 UPSTREAM_RC 里登记上游退出码**`;
bad++;
} else if (UPSTREAM_RC[seenDiag] !== got.status) {
rcCheck = `;**UPSTREAM_RC[${seenDiag}]=${UPSTREAM_RC[seenDiag]} 与真跑出来的 rc=${got.status} 不符**`;
bad++;
} else {
rcCheck = `(真打出 diag=${seenDiag},UPSTREAM_RC=${got.status} ✓)`;
}
} else if (!/Traceback/.test(got.err)) {
// 打得出来却没有 `diag=` ⇒ 契约被破坏(诊断码是判据的一部分)
rcCheck = ';**脚本打出了 RESULT 却没带 `diag=`**(诊断码是判据的一部分,不许漏)';
bad++;
}
const ok = okStatus && okOut && !rcCheck.includes('**');
console.log(`${ok ? 'ok ' : 'RED '} ${what}:rc=${got.status}` +
`${ok ? '' : `(期望 rc=${want}${wantOut ? ` 且输出含 "${wantOut}"` : ''})`}`);
`${ok ? rcCheck : `(期望 rc=${want}${wantOut ? ` 且输出含 "${wantOut}"` : ''}${rcCheck})`}`);
if (!ok) bad++;
}
/*
* ★ 反向覆盖:`UPSTREAM_RC` 里**每个码**都得有案例真跑过 ——
* 只登记不验,就等于表里有个"我希望它是几"的格子(正是上面那段要防的)。
*/
{
const covered = new Set(seenDiags);
const uncovered = Object.keys(UPSTREAM_RC).filter(c => !covered.has(c));
if (uncovered.length) {
console.log(`RED UPSTREAM_RC 里这些码**没有案例真跑过** ⇒ 表里的值是没人验过的:${uncovered.join('、')}`);
bad++;
} else {
console.log(`ok UPSTREAM_RC 的 ${Object.keys(UPSTREAM_RC).length} 个码都有案例真跑验过`);
}
}
process.exit(bad ? 1 : 0);
}
@ -1793,16 +1882,62 @@ if (process.argv.includes('--verdict-selftest')) {
}
console.log(`ok 码表每个码都有 blocksGreen(${Object.keys(DIAG).length} 个码)`);
/*
* ⑤ 口径一致性:`blocksGreen === true` 的码,其**上游退出码必须非零**。
* 两边漂移(上游退 0、下游却红)会让人去查一个"其实没被报错"的东西。
* 这条把 `summary.py` 的退出码分类与 `run-all` 的表**绑在一起**验(不在一边写死另一边的值)。
* ⑤★ 口径一致性(pi 2026-09-18 本封报的那条)。
*
* ⚠️ **上一版这条是坏的,两个方向都坏**,pi 报了、我复现了:
* 注释写"把 `summary.py` 的退出码分类与 `run-all` 的表**绑在一起**验
* (**不在一边写死另一边的值**)",而实现只遍历 `DIAG` 表 + 跳过三个**硬编码**码名 ——
* **一个字节都没读 `summary.py`**。它其实在断言"**除了我手列的这三个,其它码都必须红**":
* **注释说"不写死",实现恰恰写死了一份码名单。**
*
* 实测两个方向:
* · **假红**:加一个上游退 0 的**提示码**(与 `baseline-stale` 同类、完全合法)
* ⇒ 报"上游退非零,下游却不红 ⇒ 两侧口径漂移"。
* 而那个码的上游**根本不存在**(`summary.py` 永远不会打出它)——
* ⑤ 在这里**编造了一个上游事实**并据此判红。
* · **假绿**:加一个 `blocksGreen: true` 的新码 ⇒ **rc=0,一条 RED 都没有**。
* 同一句"上游退非零"也是编的 ⇒ 漂移从这个方向**完全看不见**。
*
* ⇒ 现在拿**唯一权威** `UPSTREAM_RC`(它的每个值都在 `--exitcode-selftest` 里被**真脚本**
* 逐条验过,见那一段的反向覆盖检查),与 `DIAG.blocksGreen` **双向**比对:
* · 上游退非零、下游却不红 ⇒ 漂移(该红的没红);
* · 上游退 0、下游却标阻断 ⇒ 漂移(反之亦然,上一版根本查不到这个方向);
* · 码没在 `UPSTREAM_RC` 里登记 ⇒ 红("加码要同时登记它退几")。
* **白名单随之消失**(数据说话)—— 这也是 `DIAG` 那条"一个码一行、一次定完"的延伸:
* 上游退出码也该是那一行的一部分。
*/
for (const [code, e] of Object.entries(DIAG)) {
if (code === 'none' || code === 'baseline-absent' || code === 'baseline-stale') continue;
if (!e.blocksGreen) {
console.log(`RED \`${code}\` 上游退非零,下游却不红 ⇒ 两侧口径漂移`);
{
const missing = Object.keys(DIAG).filter(c => !(c in UPSTREAM_RC));
const extra = Object.keys(UPSTREAM_RC).filter(c => !(c in DIAG));
if (missing.length) {
console.log(`RED DIAG 里这些码**没在 UPSTREAM_RC 登记上游退出码**:${missing.join('、')}`
+ ' ⇒ 加一个码必须同时登记"它退几",否则⑤无从比对');
bad++;
}
if (extra.length) {
console.log(`RED UPSTREAM_RC 里这些码在 DIAG 里不存在:${extra.join('、')} ⇒ 两张表漂移`);
bad++;
}
const drift = [];
for (const [code, e] of Object.entries(DIAG)) {
const rc = UPSTREAM_RC[code];
if (rc === undefined) continue;
if (rc !== 0 && !e.blocksGreen) drift.push(`${code}(上游 rc=${rc},却 blocksGreen=false)`);
if (rc === 0 && e.blocksGreen) drift.push(`${code}(上游 rc=0,却 blocksGreen=true)`);
}
if (drift.length) {
console.log(`RED 两侧口径漂移(**双向**比对 UPSTREAM_RC ↔ blocksGreen):${drift.join(';')}`);
bad++;
}
/*
* ⚠️ `ok` 只在**这一格完全没红**时打。我第一版无条件打 ⇒ 变异 (a) 的输出长成
* `RED DIAG 里这些码没在 UPSTREAM_RC 登记…` 紧跟 `ok 口径一致(双向):9 个码…` ——
* **同一格的报文自相矛盾**(读者会以为没红)。与结构锚点那次是同一个错,我又犯了一遍。
*/
if (!missing.length && !extra.length && !drift.length) {
console.log(`ok 口径一致(双向):${Object.keys(DIAG).length} 个码的`
+ '`UPSTREAM_RC ↔ blocksGreen` 逐条相符,且无未登记/多余码');
}
}
/*
* ⑥★ **结构锚点:生产路径必须真的经过 `applyMutants`**。