修复: 守着锚的那张表**自己没人守** —— CAUSAL = [] 与基线逐字相同,层 7 的攻击被完整放回(层 8)
pi 2026-09-18 报的层 8。**成立,我复现,读数与它逐字相同。** ## 一、缺陷:我上一条那句"表本身也被真跑锚住了"**只对了一半** 被守的是"表里的锚文本在源码里唯一",而 **"表里该有几条"没有任何东西在问**。实测: ``` CAUSAL = [](整表清空) ⇒ red=10,与基线**逐字相同**、因果 RED 0 条 只留 1 对(删掉另外 3 对) ⇒ red=10,那三条自检**从此失去因果锚**、无任何提示 CAUSAL = [] + 假 ok×13 ⇒ red=10、因果/空壳 RED **0 条** ⇒ **层 7 的攻击被完整放回来了,而代价从"改函数体"变成"删掉表里一行"。** ``` ★ 根因:**锚点不能落在"被守对象的自述"上**(层 7), 而这里是 **"守着锚的那张表自己的内容"没有锚** —— 上一轮是"判据自述",这一轮是"**锚表的自述**"。 ★ 新轴:**守护者自己进入了被守集合** —— 前几层问的都是"**被守的东西**怎样", 层 8 问的是"**守它的东西**怎样"。 ## 二、修法:覆盖面必须有一个**不在表里**的来源 用**两个**,都是已有的、不必新写表: · ① `contractEntries` —— `CRITERIA.md` 契约行(**外部文件**,与层 5 **同一个锚点**; 一个外部来源同时守两件事); · ② `SELFTESTS` 的**键**(源码结构)—— 让"新加了一条自检却没配因果对"也能被抓。 ⚠️ **空真的坑**:只写"每个 `CAUSAL` 项都对应一个真自检"(反向)**挡不住 `[]`** —— `[]` 恰好满足那个空真("每个"在空集上恒真)。⇒ 必须**正向**要求覆盖。 ★ 与本仓那句同族:**"判据存在" vs "判据在路径上"**;这里是 **"表非空" vs "表够长"**。 ## 三、⚠️ 我实现时踩的坑:**两个命名空间别混**(被我自己刚写的判据当场抓住) `wired` 装的是**函数名**(`probeSelfTest`),`CAUSAL`/契约行装的是**旗标名**(`probe-selftest`)。 我第一版拿 `wired` 比 `CAUSAL` ⇒ **5 条全报"幽灵名"、另 5 条全报"没有因果锚"** (一次红 2 条、方向相反)。⇒ **判据没错,是我把两套名字当成了一套。** 修法是改用 `SELFTESTS` 的键(旗标名,与 `CAUSAL` 同命名空间)。 ★ 教训:**同一个东西在两个地方有两套名字时,"名字相等"不是可传递的。** 同一次还抓出第二个我自己的 bug:第 5 对的锚文本在 JS 源码里只写了 `\d`, 运行时会**塌成 `d`** ⇒ 锚文本永远匹配不上(唯一性报 0,而不是 1)。 ★ 这两处都是**新判据抓新判据**——判据互相盯着是有效的。 ## 四、补第 5 对(`mutants-line-selftest`) 层 7 时表里只有 4 对,`mutantsLineSelfTest` 一直没有因果锚。 ★ 我实测了三个候选锚:`未列入清单`(7 处)、`baseline-stale`(22 处)改坏后 **rc=0** ⇒ **它不读那两处**(那是别的函数在用);只有读 `RESULT` 行那个正则改坏后 rc=1。 ⇒ 这也是"**关系表写错就会被真跑抓住**"的又一次实例。 ## 五、变异验证(都已还原) | 变异 | 结果 | |---|---| | **层 8(a)** `CAUSAL = []` | **红**:`这些自检**没有因果锚**:…(要求 5 条,因果表只有 0 对)` | | **层 8(b)** 只留 1 对 | **红**(缺 4 条) | | **层 8(c) 最尖**:`CAUSAL=[]` + 假 ok×13 | **红**(修复前 0 条) | | 幽灵名(CAUSAL 放不存在的自检名) | **红 3 条**(幽灵名 + 覆盖不全 + 因果判据退出码 2) | | **方向②** 新自检 + 登记 + 契约项但**不给因果对** | **红**(`要求 6 条,因果表只有 5 对`) | | **`CAUSAL` 5 对逐条直验**(从源码解析表,防我手抄错) | 改坏后**每条** `--only-selftest` rc=1 | | 回归 层6 掏空 / 层7 假ok×13 / 层5 删整条 / 契约行删掉 | 各 1 条 | | 回归 M43 / M45 / M47 / M48 / 名字级未接线 | 各 1 条 | | 干净树假红检查 | `^RED` **0 条**、因果红 0、覆盖红 0 | ## 六、⚠️ 残留(如实登记,不声称层 8 封死) · **多处同步删仍可静默**:把 `CAUSAL` 对 + `SELFTESTS` 登记 + CLI guard + **`--only-selftest` 快入口那一项** + 函数体 + 契约项**全部**删掉 ⇒ 实测 `red=9`、`^RED ` **0 条**(与基线相同)、`自检段:4 个自检`。 ⇒ 与层 5/6/7 同形:我把成本从一次编辑抬到**五六处**,**性质没变**。 · ★ 顺带如实记一笔:**我的层 7 修法自己新增了一个引用点**(`--only-selftest` 的名字表)—— 它让删除**更贵了一点**(要一起删),但它本身也是一个新的"必须同步删的地方"。 这不是设计意图,是副作用,写在这里以免下一轮把它当成"防线"。 ## 七、验证与状态 · 五个自检单独跑全 rc=0;默认跑 `checks=462 pass=458 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✓`;正常态 `^RED ` **0 条**。 · ⚠️ 口径变化**不是**本提交造成的,是**另一个会话**的提交 `f2cddf4` (`harmony-deviceprobe` 期望条数 8→**11**,加了 3 条包名漂移回归锁)⇒ `checks 459→462`; 同一次也修掉了 `harmony-nav` 的**设备占用红**(那条红曾连红 42 轮)⇒ `fail 5→4`、`red 10→9`。 ⇒ 我这次没有引入新的红,也没有消掉别人的红。 · 本提交含 `client/electron/test/run-all.mjs`(第 5 对 + 覆盖判据)与 `client/electron/test/CRITERIA.md`(新增 §6.1.2)。 · 提交前 `HEAD=f2cddf4`。
This commit is contained in:
@ -254,9 +254,46 @@ const ALLOW = [ /* { name, replacement, why } */ ];
|
||||
- 关系表(自检 ↔ 它守的符号)也是手写数据,**但它被真跑锚住了**:
|
||||
表里每一对都必须实测"改坏了它真会红";把符号写错(写成它不读的)⇒ 那条自检改坏后**不红** ⇒ 红。
|
||||
⇒ 表自己也被因果判据守着,这是它与"下界数字"的**关键区别**。
|
||||
⚠️ **这句话上面那半句是错的**,见 §6.1.2 —— 被守的只是"表里的锚文本唯一",**"表里该有几条"当时没人问**。
|
||||
- ⚠️ 锚文本的唯一性必须在**除去 `CAUSAL` 表本身**的源码里数,
|
||||
否则会**匹配到表里那一行字面量**(我第一版就踩了:probe/verdict 立刻报"不唯一",
|
||||
真因是表自己,不是生产里有两处)。★ 又是那句:"提到"与"就是"在文本上长得一样。
|
||||
⇒ 可推广成一句:**任何"在源码里找自己写的那串字"的判据,都必须先把自己那串字挖掉** ——
|
||||
否则它就是在给自己数数。
|
||||
|
||||
### 6.1.2 **守着锚的那张表,自己没人守**(pi 2026-09-18,层 8)
|
||||
|
||||
§6.1.1 我写"表本身也被真跑锚住了" —— **只对了一半**。被守的是"锚文本在源码里唯一",
|
||||
而 **"表里该有几条"没有任何东西在问**。实测(`be49e07`):
|
||||
|
||||
```
|
||||
CAUSAL = [](整表清空) ⇒ red=10,与基线**逐字相同**、因果 RED 0 条
|
||||
只留 1 对(删掉另外 3 对) ⇒ red=10,那三条自检**从此失去因果锚**、无任何提示
|
||||
CAUSAL = [] + 假 ok×13 ⇒ red=10、因果/空壳 RED **0 条**
|
||||
⇒ **层 7 的攻击被完整放回来了,而代价从"改函数体"变成"删掉表里一行"。**
|
||||
```
|
||||
|
||||
⇒ 根因:**锚点不能落在"被守对象的自述"上**(层 7),
|
||||
而这里是 **"守着锚的那张表自己的内容"没有锚** ——
|
||||
上一轮是"判据自述",这一轮是"**锚表的自述**"。
|
||||
★ 新轴:**守护者自己进入了被守集合** —— 前几层问的都是"**被守的东西**怎样",
|
||||
层 8 问的是"**守它的东西**怎样"。
|
||||
|
||||
**修法:覆盖面必须有一个不在表里的来源。** 这里用**两个**,都是已有的:
|
||||
|
||||
- ① `contractEntries` —— `CRITERIA.md` 契约行(**外部文件**,与层 5 **同一个锚点**;
|
||||
一个外部来源同时守两件事,不必再写第二张表);
|
||||
- ② `SELFTESTS` 的**键**(源码结构)—— 让"新加了一条自检却没配因果对"也能被抓。
|
||||
|
||||
⚠️ **空真的坑**:只写"每个 `CAUSAL` 项都对应一个真自检"(反向)**挡不住 `[]`** ——
|
||||
`[]` 恰好满足那个空真("每个"在空集上恒真)。⇒ 必须**正向**要求覆盖。
|
||||
★ 与本仓那句同族:**"判据存在" vs "判据在路径上"**;这里是 **"表非空" vs "表够长"**。
|
||||
|
||||
⚠️ **两个命名空间别混**(我第一版就混了,被自己刚写的判据当场抓住):
|
||||
`wired` 装的是**函数名**(`probeSelfTest`),`CAUSAL`/契约行装的是**旗标名**(`probe-selftest`)。
|
||||
拿前者比后者 ⇒ **5 条全报"幽灵名"、另 5 条全报"没有因果锚"**(一次红 2 条、方向相反)。
|
||||
⇒ 判据没错,是我把两套名字当成了一套。**正确的来源是 `SELFTESTS` 的键**(旗标名)。
|
||||
★ 教训:**同一个东西在两个地方有两套名字时,"名字相等"不是可传递的**。
|
||||
|
||||
★ 层级表(**每层都是一根轴**):写法(4) → 存在性(5,"它还在吗") → 行为(6,"它做了吗")
|
||||
→ **自述(7,"它说的话可信吗")**。
|
||||
|
||||
@ -2687,6 +2687,9 @@ let selfCheckRuns = 0;
|
||||
* (它真跑 `summary.py` 拿 rc 来比)。假体不读表 ⇒ 仍然全绿 ⇒ 被这条抓住。
|
||||
*/
|
||||
['exitcode-selftest', " 'baseline-residue': 1,", " 'baseline-residue': 99,"],
|
||||
['mutants-line-selftest',
|
||||
'/(RESULT mutants=\\d+ ran=\\d+ skipped=\\d+ on_new_criteria=\\d+)(?: diag=([\\w-]+))?/',
|
||||
'/XXXNEVERMATCHXXX/'],
|
||||
];
|
||||
for (const [name, from, to] of CAUSAL) {
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'am-causal-'));
|
||||
@ -2723,6 +2726,55 @@ let selfCheckRuns = 0;
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
/*
|
||||
* ★★★ **层 8**:守着锚的那张表,自己没人守(pi 2026-09-18 报)。
|
||||
*
|
||||
* 上一条我写"表本身也被真跑锚住了" —— 那句话**只对了一半**:
|
||||
* 被守的是"表里的锚文本在源码里唯一",而**"表里该有几条"没有任何东西在问**。实测:
|
||||
*
|
||||
* `CAUSAL = []`(整表清空) ⇒ red=10,与基线**逐字相同**、因果 RED 0 条
|
||||
* 只留 1 对(删掉另外 3 对) ⇒ red=10,那三条自检**从此失去因果锚**、无任何提示
|
||||
* `CAUSAL = []` **+ 假 ok×13** ⇒ red=10、因果/空壳 RED **0 条**
|
||||
* ⇒ **层 7 的攻击被完整地放回来了,而代价从"改函数体"变成"删掉表里一行"。**
|
||||
*
|
||||
* ⇒ 根因:**锚点不能落在"被守对象的自述"上**(层 7),
|
||||
* 而这里是 **"守着锚的那张表自己的内容"没有锚** ——
|
||||
* 上一轮是"判据自述",这一轮是"**锚表的自述**"。
|
||||
*
|
||||
* ★ 修法的要点:覆盖面必须有一个**不在表里**的来源。这里用**两个**,都是已有的:
|
||||
* ① `contractEntries` —— `CRITERIA.md` 契约行(**外部文件**,与层 5 同一个锚点,
|
||||
* 一个外部来源同时守两件事,不必再写第二张表);
|
||||
* ② `wired` —— 源码里 `SELFTESTS` 登记的自检名(**源码结构**)。
|
||||
* 两条都要满足:① 让"清空/缩短表"变红;② 让"新加了一条自检却没给它因果对"变红
|
||||
* (契约行没同步更新时,只有 ② 抓得住)。
|
||||
*
|
||||
* ⚠️ 空真的坑:只写"每个 CAUSAL 项都对应一个真自检"(反向)**挡不住 `[]`** ——
|
||||
* `[]` 恰好满足那个空真("每个"在空集上恒真)。⇒ 必须**正向**要求覆盖。
|
||||
* ★ 与本仓那句一致:**判据存在 vs 判据在路径上**;这里是 **"表非空" vs "表够长"**。
|
||||
*/
|
||||
/*
|
||||
* ⚠️ **两个命名空间别混**(我第一版就混了,被自己刚写的判据抓住):
|
||||
* `wired` 装的是**函数名**(`probeSelfTest`),`CAUSAL`/契约行装的是**旗标名**(`probe-selftest`)。
|
||||
* 第一版我拿 `wired` 比 `CAUSAL` ⇒ 5 条**全部**报成"幽灵名"、另外 5 条全部报成"没有因果锚"
|
||||
* (一次红 2 条,方向相反)⇒ 判据没错,是我把两套名字当成了一套。
|
||||
* ⇒ 正确的来源是 `SELFTESTS` 的**键**(旗标名),它和 `CAUSAL` 在同一命名空间里。
|
||||
*/
|
||||
const causalNames = new Set(CAUSAL.map(([n]) => n));
|
||||
const registered = SELFTESTS.map(([n]) => n);
|
||||
const causalGhosts = [...causalNames].filter(n => !registered.includes(n));
|
||||
if (causalGhosts.length) {
|
||||
console.log(`RED 因果表里有 ${causalGhosts.length} 个**不对应任何已登记自检**的名字:`
|
||||
+ `${causalGhosts.join('、')} ⇒ 这些"锚"锚在空气上(改了也没人管)`);
|
||||
reds.push(`(自检)因果表里有幽灵名字:${causalGhosts.join('、')}`);
|
||||
}
|
||||
const causalRequired = [...new Set([...registered, ...contractEntries.map(e => e.name)])];
|
||||
const causalMissing = causalRequired.filter(n => !causalNames.has(n));
|
||||
if (causalMissing.length) {
|
||||
console.log(`RED 这些自检**没有因果锚**(改坏它守的生产符号也没人会红):`
|
||||
+ `${causalMissing.join('、')} —— 要求 ${causalRequired.length} 条,因果表只有 ${CAUSAL.length} 对`
|
||||
+ ' ⇒ 表被清空/缩短了,或者新自检没配因果对');
|
||||
reds.push(`(自检)**因果覆盖不全**:缺 ${causalMissing.join('、')}`);
|
||||
}
|
||||
const SAMPLE_FAKE_NAMES = ['fooSelfTest'];
|
||||
const allNames = [...new Set([...selfSource.matchAll(/\b(\w+SelfTest)\b/g)].map(m => m[1]))];
|
||||
const nameUnwired = allNames.filter(n => !wired.has(n) && !SAMPLE_FAKE_NAMES.includes(n));
|
||||
|
||||
Reference in New Issue
Block a user