修复: 第四个落点 —— **变异条目的锚点失效**(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:
@ -900,6 +900,75 @@ Go 在同一包内**按源文件顺序**跑测试 ⇒ 登记那条先跑就读
|
||||
| "未列入清单" 在套件输出 | grep **0** | grep **1** |
|
||||
| `(summary.py)manifest-mismatch` 红 | **0** | **1** |
|
||||
|
||||
#### 16.1.1 ★★ 第四个落点:**变异条目的锚点失效**(`hits=0`)—— 守具有齿,却不在位(dsh 2026-09-19)
|
||||
|
||||
pi 报的第 1–3 例都修好之后,我顺着同一条线**审计全部 61 个变异条目的锚点**,
|
||||
发现**还有一个同形状的落点**,而且它在**真树上是活的**(不是构造的):
|
||||
|
||||
```
|
||||
活跃条目 61 个 · 锚点命中 ≠ 1 的:**1 个**
|
||||
hits=0 client/harmony/.../pages/SettingsPage.ets 「管理入口不做门禁」(jobs-all.json)
|
||||
```
|
||||
|
||||
**根因**:该锚点写的是 **6 空格**(` if (this.isAdmin) {`),
|
||||
而 `f4b8bc1`(09-17 17:43,"邮件详情与「我的」页 1:1 对齐 WebUI")把该文件**重排成 4 空格**
|
||||
⇒ 锚点从此命中 **0** 次。写进清单时(`e2f117f`,09-15)它是**对的**(当时 6 空格命中 1 次)。
|
||||
|
||||
**为什么没人发现**:`summary.py` 把「hits=0 … 过期条目」**只打印**,
|
||||
**完全不进严重度链** ⇒ `rc=0`、`diag=none` ⇒ 套件那边
|
||||
`whyLines: (note || status !== 0) ? … : []` 为假 ⇒ **整段丢掉**。
|
||||
端到端实测(真树、干净工作树):
|
||||
|
||||
| | 修前(锚点 6 空格) | 修后(锚点 4 空格) |
|
||||
|---|---|---|
|
||||
| `mutants= / ran= / skipped=` | `48 / 47 / 1` | `48 / **48** / **0**` |
|
||||
| `diag=` | `none` | `none`(无此码,因为已修) |
|
||||
| "过期条目"在套件输出 | grep **0** | — |
|
||||
|
||||
★ **危害是"一个变异守具被静默关掉",不是"数字错了"**:
|
||||
`ran` 少 1、`skipped=1` 是个**中性数字**,读者看不出少了哪一个、也不知道少了。
|
||||
而**手工施加那个变异仍能让判据红**(`harmony-admin.test.mjs` `# fail 1`,
|
||||
`★ 管理入口没有 isAdmin 门禁 ⇒ 每个普通用户都会看到一个点进去 403 的入口`)
|
||||
⇒ **守具有齿,只是不再被挂上**。
|
||||
|
||||
**修法**(与第 3 例同一条链,`summary.py` 的**唯一严重度链**):
|
||||
新增 `mutant-anchor-stale` ⇒ **1 档**(清单/数据该改),
|
||||
不是 2 档(那才是环境问题)——照 `env-defaults.sh:25` 的**反方向**:别让"清单没跟上"冒充环境。
|
||||
|
||||
★ **射程如实标出**:判据只看 **`hits == 0`**,**不看 `hits == -1`**。
|
||||
`-1` 是"目标文件打不开"(文件没了/权限),与"锚点写法过期"是两回事,
|
||||
且 `--exitcode-selftest` 的迷你夹具里 `jobs-one.json` 指向的文件**本来就不在**
|
||||
(夹具设计,不是缺陷)⇒ 把 `-1` 算进来会造**假红**。
|
||||
**`-1` 那一半目前没有判据守着**(真树实测 0 条)。
|
||||
|
||||
**端到端 A/B**(dsh,隔离 worktree,同刻对照):
|
||||
|
||||
| | A:锚点已修(4 空格) | B:锚点退回 6 空格(制造 hits=0) |
|
||||
|---|---|---|
|
||||
| `summary.py` rc | `0` | **`1`** |
|
||||
| `diag=` | `none` | **`mutant-anchor-stale`** |
|
||||
| `ran= / skipped=` | `48 / 0` | `47 / 1` |
|
||||
| "锚点已失效"在套件输出 | grep **0** | grep **2** |
|
||||
|
||||
**变异验证**(dsh,4 个方向全部抓住):
|
||||
|
||||
| 变异 | 结果 |
|
||||
|---|---|
|
||||
| 新档关闭(`elif False`)=回到旧的"只打印" | `exitcode-selftest` rc=1、2 条红(`rc=0(期望 rc=1`)✓ |
|
||||
| `UPSTREAM_RC` 删掉新码 | 反向覆盖点名 + 案例不符 ✓ |
|
||||
| `blocksGreen: true` → `false` | `两侧口径漂移(双向比对 UPSTREAM_RC ↔ blocksGreen)` ✓ |
|
||||
| 判定放宽成任何 `skipped_detail`(含 `hits=-1`) | rc=1、**5 条红**(迷你夹具假红)⇒ **`h == 0` 这个射程是承重的** ✓ |
|
||||
|
||||
★★ **通用规则(本仓第 4 次同一形状,值得单列)**:
|
||||
|
||||
> **"跑了多少个"与"该跑多少个"之间,也要有一条判据。**
|
||||
> 一个变异/检查被**跳过**时,`ran` 只少 1、`skipped` 只多 1 —— 两个**中性数字**,
|
||||
> 而"少了哪一个、为什么少"没有任何通道。
|
||||
> ⇒ 凡有"登记了一批东西、再逐个挂上"的结构(变异条目、判据、样本表),
|
||||
> 都要问一句:**挂不上的那一个,谁来说?**
|
||||
> 同族的第三个自查问(pi 归纳):"守着它的判据,它自己的样本由谁守";
|
||||
> 这一条是它的**镜像**:"**被守的那个东西没挂上时,谁来说**"。
|
||||
|
||||
### 16.2 ★★ **同一优先级写了两遍 ⇒ 两个顺序**:不变式只在**对角线**上被验过(pi + dsh 2026-09-18)
|
||||
|
||||
§16.1 讲"结论到不了决定颜色的那一格"。这一节是它的**镜像**:
|
||||
|
||||
@ -141,8 +141,8 @@
|
||||
},
|
||||
{
|
||||
"file": "client/harmony/entry/src/main/ets/pages/SettingsPage.ets",
|
||||
"pat": " if \\(this\\.isAdmin\\) \\{",
|
||||
"repl": " if (true) {",
|
||||
"pat": " if \\(this\\.isAdmin\\) \\{",
|
||||
"repl": " if (true) {",
|
||||
"test": "admin",
|
||||
"why": "管理入口不做门禁"
|
||||
},
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 末行
|
||||
|
||||
Reference in New Issue
Block a user