diff --git a/client/electron/test/CRITERIA.md b/client/electron/test/CRITERIA.md index 5412f56..b7f1a1c 100644 --- a/client/electron/test/CRITERIA.md +++ b/client/electron/test/CRITERIA.md @@ -295,6 +295,53 @@ CAUSAL = [] + 假 ok×13 ⇒ red=10、因果/空壳 RED **0 条** ⇒ 判据没错,是我把两套名字当成了一套。**正确的来源是 `SELFTESTS` 的键**(旗标名)。 ★ 教训:**同一个东西在两个地方有两套名字时,"名字相等"不是可传递的**。 +### 6.1.3 **判断据的那个人,也可信吗**(pi 2026-09-18,层 9) + +§6.1.1 我为把因果判据的成本从 4.3s 压到 52~696ms 加了快入口 `--only-selftest`, +而它当时自带**第二张分发表 `T`**(`{'probe-selftest': probeSelfTest, …}`)。 +我在上一封把它如实标成"**副作用、不是防线**" —— **层 9 就长在那个副作用上**。实测: + +``` +T 全换成 () => 1 桩 ⇒ red=9,与基线**逐字相同**、因果 RED **0 条** +T 全桩 + 假 ok×13(层 7) ⇒ red=9、**红清单 diff 0 行**(层 7 的攻击完整放回) +``` + +为什么因果判据看不见:它观察孩子**只经 `T` 的分发**,而它比的"孩子退出码" +正是 `T` 提供的 ⇒ **判据的观测通道被它自己要观测的对象控制着。** +⇒ 层 7 问"**被测者**的话可信吗",层 9 问"**测量者**的话可信吗"。 +★ 也是本仓老形状的又一次:`T` 与 `SELFTESTS` 是**同一事实两份实现** +(`stripStrings` 兄弟副本、`blurStyleFor`、`parseSkips` 抄副本…… 这次是**分发映射表**)。 + +**修法一(消除"两份实现"本身):`T` 不该单独存在。** +`SELFTESTS` 上移成**唯一的一份登记**,快入口从它派生 ⇒ "桩化 `T`"这个动作**没有落点**。 +⚠️ 五个自检都是 `function` 声明(**提升**)⇒ 引用它们没有 TDZ 问题; +但**调用**它们仍必须排在 `DIAG` 等 `const` 表之后(那个 `if` 留在原位)。 + +**修法二(更硬,堵形状而不堵那一个洞):给因果判据加对照 —— 未改坏时必须 rc=0。** + +只问"改坏 ⇒ 必须 rc=1"的话,**任何恒 rc=1 的通道**(表、wrapper、环境变量…)都能让它绿。 +⇒ 加一问:**没改坏的时候它必须 rc=0**。通道坏了 ⇒ 这一问就红。实测: +把快入口改成恒 `exit 1` ⇒ **5 条 RED**(`观测通道本身有问题`); +而改坏生产符号那一半仍照常红。 +★ 这是"锚点必须落在被守对象管不着的地方"的**行为**版本: +**我不需要知道通道长什么样,只要求它在两种输入下给出两种答案。** +⚠️ 代价:每对自检跑**两次**(默认跑 5.1s → **6.8s**,可接受)。 + +★★ **一般化(我认为这是这条线上最值得记的一句)**: + +> **每加一条防线,要同时回答两问:** +> ① 它守什么?(设计意图) +> ② **它自己新增了哪些"必须被守"的东西?**(副作用) +> +> 层 9 就是第②问没问的产物 —— `--only-selftest` 是**性能上的净收益**, +> 却往被守集合里加了一个**没人守**的成员。 + +⇒ 这与"每根轴的底看起来都像整体的底"是同一现象的两种说法: +**防线是会长出新的被守对象的**,所以你永远能看到一个新的"底"。 + +★ 层级表:写法(4) → 存在性(5,它还在吗) → 行为(6,它做了吗) → 自述(7,被测者可信吗) +→ 锚表(8,锚还在吗) → **观测(9,判断据的那个人可信吗)**。 + ★ 层级表(**每层都是一根轴**):写法(4) → 存在性(5,"它还在吗") → 行为(6,"它做了吗") → **自述(7,"它说的话可信吗")**。 diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 0ca51e8..603e4e1 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -2296,23 +2296,45 @@ if (process.argv.includes('--mutants-line-selftest')) process.exit(mutantsLineSe * ⇒ 任何"调用自检"的代码都必须排在**所有** `const` 表与自检函数定义之后。 * (所以这里紧贴生产区段入口:`let mutantsLine` 之前。) */ +/* + * ★★★ **唯一的一份登记**(pi 2026-09-18 报的层 9)。 + * + * 层 7 我为了把因果判据的成本从 4.3s 压到 52~696ms,加了快入口 `--only-selftest`, + * 而它当时自带**第二张分发表 `T`**(`{'probe-selftest': probeSelfTest, …}`)。 + * 我在上一封自己把那个入口标成"副作用、不是防线"—— **层 9 就长在那个副作用上**: + * + * · `T` 全换成 `() => 1` 桩 ⇒ `red=9`,与基线**逐字相同**、因果 RED **0 条**; + * · `T` 全桩 **+ 假 ok×13**(层 7 的攻击)⇒ `red=9`、**红清单 diff 0 行**。 + * ⇒ **层 7 的攻击被完整放回,而代价只是"把 `T` 里 5 个值换成 `() => 1`"。** + * + * 为什么因果判据看不见:它观察孩子**只经 `T` 的分发**,而它比的"孩子退出码" + * 正是 `T` 提供的 ⇒ **判据的观测通道被它自己要观测的对象控制着。** + * ⇒ 层 7 是"**被测者**的话可信吗",层 9 是"**测量者**的话可信吗"。 + * + * ⇒ 修法(pi ①,我采用):**让 `T` 不存在** —— 一处定义(本表)、一处派生。 + * 那么"桩化 `T`"这个动作**没有落点**(改本表会被层 5/8 的判据抓住)。 + * ⚠️ 本表因此**上移到 `--only-selftest` 之前**(原来是块内的 `const`,在它之后)。 + * 五个自检都是 `function` 声明(**提升**),所以引用它们没有 TDZ 问题; + * 而**调用**它们仍必须排在 `DIAG` 等 `const` 表之后 —— 下面那个 `if` 就在原位, + * 这正是它位置的理由(见上面那段"const 不提升"的注释)。 + * ★ 一般化(pi 建议写进 `CRITERIA.md`): + * **每加一条防线,要同时回答两问:① 它守什么?② 它新增了哪些"必须被守"的东西?** + * 层 9 就是第②问没问的产物。 + */ +const SELFTESTS = [ + ['probe-selftest', probeSelfTest], + ['exitcode-selftest', exitcodeSelfTest], + ['skip-selftest', skipSelfTest], + ['verdict-selftest', verdictSelfTest], + ['mutants-line-selftest', mutantsLineSelfTest], +]; if (ONLY_SELFTEST) { - const T = { 'probe-selftest': probeSelfTest, 'exitcode-selftest': exitcodeSelfTest, - 'skip-selftest': skipSelfTest, 'verdict-selftest': verdictSelfTest, - 'mutants-line-selftest': mutantsLineSelfTest }; - const fn = T[ONLY_SELFTEST]; - if (!fn) { console.error(`✗ 未知自检 ${ONLY_SELFTEST}`); process.exit(2); } - process.exit(fn() > 0 ? 1 : 0); + const entry = SELFTESTS.find(([n]) => n === ONLY_SELFTEST); + if (!entry) { console.error(`✗ 未知自检 ${ONLY_SELFTEST}`); process.exit(2); } + process.exit(entry[1]() > 0 ? 1 : 0); } let selfCheckRuns = 0; { - const SELFTESTS = [ - ['probe-selftest', probeSelfTest], - ['exitcode-selftest', exitcodeSelfTest], - ['skip-selftest', skipSelfTest], - ['verdict-selftest', verdictSelfTest], - ['mutants-line-selftest', mutantsLineSelfTest], - ]; /* * 自检的输出**不是**这个阶段的正常输出(各打几十行 `ok …`)⇒ 吞掉 stdout, * 只在红时把它自己的 RED 行带进报告。 @@ -2710,6 +2732,26 @@ let selfCheckRuns = 0; reds.push(`(自检)因果锚文本不唯一:${name}`); continue; } + /* + * ★★★ **对照:未改坏时必须 rc=0**(层 9 的另一半,也是更硬的那一半)。 + * + * pi 报的层 9 是"**观测通道与被测样本住在同一张可编辑的表里**"(我那张 `T`)。 + * 我把 `T` 消掉了(唯一登记),但那只堵住了**那一个形状**: + * 只要判据**只有"改坏 ⇒ 必须 rc=1"这一问**,任何"恒 rc=1 的通道" + * (不管是表、是 wrapper、还是环境变量)都能让它绿。 + * ⇒ 加一问:**没改坏的时候,它必须 rc=0。** 通道坏了 ⇒ 这一问就红。 + * ★ 这就是"锚点必须落在被守对象管不着的地方"的**行为**版本: + * 我不需要知道通道长什么样,只要求它在**两种输入下给出两种答案**。 + * ⚠️ 代价:每对一个自检跑**两次**(实测量级 52~696ms/次 ⇒ 可接受)。 + */ + const base = spawnSync(process.execPath, [target, `--only-selftest=${name}`], + { encoding: 'utf8', cwd: ROOT }); + if (base.status !== 0) { + console.log(`RED 因果判据的**对照**不过:\`${name}\` 在**没改坏**时退出码是 ${base.status}` + + '(应为 0)⇒ **观测通道本身有问题**(观测到的"红"不能归因给改坏)'); + reds.push(`(自检)因果对照失败:\`${name}\` 未改坏时 rc=${base.status}(通道坏了)`); + continue; + } writeFileSync(target, src.replace(from, to)); const got = spawnSync(process.execPath, [target, `--only-selftest=${name}`], { encoding: 'utf8', cwd: ROOT });