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

@ -902,6 +902,7 @@ const UPSTREAM_RC = {
'counts-unusable': 2,
'manifest-mismatch': 1,
'baseline-residue': 1,
'mutant-anchor-stale': 1,
'baseline-stale': 0,
'baseline-unrunnable': 2,
'baseline-unknown': 2,
@ -1050,6 +1051,18 @@ function exitcodeSelfTest() {
rmSync(join(d, 'baseline.sha'));
}
}
/*
* `anchorstale`:**变异条目的锚点失效**(`hits == 0`)——
* 标准夹具 `jobs-one.json` 指向 `client/harmony/.../api/ApiClient.ets`。
* 在迷你仓库里建一个**同名但不含那个锚点**的文件 ⇒ 锚点命中 **0** 次
* (`hits=-1`(文件不存在)是**另一回事**,见 `summary.py` 里那句射程说明)
* ⇒ `diag=mutant-anchor-stale`、`rc=1`:**守具没挂上必须显形**。
*/
if (mode === 'anchorstale') {
const anchorAbs = join(root, 'client/harmony/entry/src/main/ets/api/ApiClient.ets');
mkdirSync(dirname(anchorAbs), { recursive: true });
writeFileSync(anchorAbs, '// 故意不含锚点:模拟"锚点写法漂移后命中 0 次"\n');
}
if (mode === 'blind' || mode === 'unreadable') {
// 整棵给 nobody 可达(mkdtemp 默认 700),最后一层按场景收紧
spawnSync('chmod', ['755', root]);
@ -1177,6 +1190,14 @@ function exitcodeSelfTest() {
*/
['组合:未列入清单 + 跑不了 sha256sum ⇒ diag=baseline-unrunnable(2 档优先)且 rc=2',
['jobs-one.json'], ['jobs-one.json', 'jobs-extra.json'], 2, 'diag=baseline-unrunnable', 'no-sha256sum'],
/*
* ★★ 第五类(dsh 2026-09-19):**变异条目的锚点失效**(`hits == 0`)。
* 这条就是它的端到端锚点 —— 也是"反向覆盖"要求的那条真跑案例
* (加新码若没有案例真跑过,`--exitcode-selftest` 会当场点名)。
* 期望 `rc=1`(数据/清单该改),**不是 2**(那不是环境问题)。
*/
['变异条目锚点失效(hits=0)⇒ rc=1 且 diag=mutant-anchor-stale',
['jobs-one.json'], ['jobs-one.json'], 1, 'diag=mutant-anchor-stale', 'anchorstale'],
];
let bad = 0;
const seenDiags = []; // 反向覆盖用:本组案例**真跑出来**的码(不是声明的)
@ -2019,6 +2040,28 @@ const DIAG = {
note: '底本过期(文件与 HEAD 逐字节相同 ⇒ 是正常提交改过,不是残留)',
blocksGreen: false, // 不是缺陷;退非零会让"正常提交"天天假红(pi 与我 §七 同意)
},
/*
* ★★ 第五类(dsh 2026-09-19):**变异条目的锚点失效**(`hits == 0`)。
*
* 这一格原来只出现在 `summary.py` 的 stdout 里,**不进严重度链** ⇒ `rc=0`、
* `diag=none` ⇒ 套件那边 `whyLines` 的转印条件 `(note || status!==0)` 为假
* ⇒ **整段丢掉**。端到端实测(真树、干净工作树):`summary.py` 说「hits=0 …
* 过期条目」· 套件输出里 grep **0 次** · `rc=0`。
*
* 后果是**一个变异守具被静默关掉**:真树那一条(`SettingsPage.ets` 的
* 「管理入口不做门禁」)锚点是 6 空格,而 `f4b8bc1` 把该文件重排成 4 空格
* ⇒ 命中 0 次、`ran` 少 1、**没有任何东西红**;但手工施加那个变异**仍能让
* `harmony-admin.test.mjs` 红** —— 有齿,只是没挂上。
*
* `blocksGreen: true`:锚点过期是**清单该改**(去改锚点或标 `retired`),
* 与 `manifest-mismatch`(1 档)同类 —— **不是**环境问题(那才是 2 档)。
*/
'mutant-anchor-stale': {
checked: true,
note: '**注意:有变异条目的锚点已失效(`hits=0`)** —— 那个变异**根本没被挂上**'
+ '(守具有齿但不在位)⇒ 请改锚点或标 `retired`;`ran` 比登记的少',
blocksGreen: true,
},
'baseline-unknown': {
checked: false,
note: '**环境:判不了 baseline 那一格**(git 答不了 ⇒ 判不出是残留还是过期)—— '
@ -2071,7 +2114,12 @@ function summarizeMutants(stdout, status, stderr) {
*/
const whyLines = (stdout || '').split('\n')
.filter(l => !/^RESULT /.test(l.trim()))
.filter(l => /✗✗|⚠️|★ 清单与磁盘不一致|在但读不到|未列入清单|清单里有、磁盘上没有/.test(l))
/*
* ★ 加 `hits=` 这一支(dsh 2026-09-19):只说"有锚点过期"不够 ——
* 读者要知道**是哪一条**(哪个文件、哪句 why)才能去改。
* 与 `未列入清单`/`清单里有、磁盘上没有` 同形状:**点名的才是可行动的**。
*/
.filter(l => /✗✗|⚠️|★ 清单与磁盘不一致|在但读不到|未列入清单|清单里有、磁盘上没有|^\s*hits=/.test(l))
.map(l => l.trim());
/*
* 上游加了新码而这里不认识 ⇒ **报警,并且红**(不是静静当成"没问题")。
@ -2472,6 +2520,15 @@ function mutantsLineSelfTest() {
+ ' ★ 清单与磁盘不一致 —— 上面的数字**不代表"磁盘上现在有多少个变异体"**:\n'
+ ' 未列入清单:jobs-UNLISTED-probe.json(新加的 job 文件必须显式加进 jobs.manifest.json)\n'
+ ' 清单里有、磁盘上没有:jobs-ghost.json\n';
/*
* ⑤(dsh 2026-09-19)**变异条目锚点失效**:`hits=0` ⇒ 那个变异根本没被挂上。
* `whyLines` 的过滤器里 `hits=0` 那两行含 `⚠️` ⇒ 会被收进来;
* 关键是**转印条件必须为真**(`diag=mutant-anchor-stale` ⇒ `blocksGreen` ⇒ 红 ⇒ 转印)。
*/
const anchorstale = 'RESULT mutants=48 ran=47 skipped=1 on_new_criteria=35 diag=mutant-anchor-stale\n'
+ '跳过(锚点命中≠1 ⇒ 跑不起来):\n'
+ ' hits=0 client/harmony/entry/src/main/ets/pages/SettingsPage.ets 「管理入口不做门禁」 (1 条条目:jobs-all.json)\n'
+ ' ⚠️ hits=0 通常是**过期条目**(锚点是旧写法)—— 请标 retired 或删除,\n';
/*
* `baseline-stale`:底本过期(与 HEAD 逐字节相同)⇒ **退 0**(不是缺陷),
* 但那一格必须能被读者看见。注意它在 `RESULT` 行里有 `baseline=` 段落。
@ -2498,6 +2555,13 @@ function mutantsLineSelfTest() {
*/
['清单与磁盘不一致(status 1,正则匹配)', unlisted, 1,
{ line: '不代表磁盘上现在有多少个变异体', why: 3 }],
/*
* ⑤(dsh 2026-09-19)**变异条目锚点失效**:`status=1`、正则匹配。
* 这一格是"守具没挂上"—— 必须把「锚点已失效」转印给读者,
* 否则 `ran` 少 1 而读者只看得到 `mutants=… skipped=1` 这个**中性数字**。
*/
['变异条目锚点失效(status 1,正则匹配)', anchorstale, 1,
{ line: '锚点已失效', why: 2 }],
// ④ 异常退出码但正则匹配(既不是 0/1/2)⇒ 也要留痕,不许静默
['未知退出码', normal, 3, { line: 'status=3', why: 0 }],
// ⑤ 完全没打出 RESULT(如脚本不存在)⇒ 报 status 与 stderr 末行