## 一、`debt-visibility` 那条红:**新文件不会自动跑一遍守卫**
```
这些文件里有"边界声明",但一次都没登记:
harmony-deviceprobe.test.mjs(2 处) ← e917b87/4880c31 新加的判据文件
```
**这是同一个洞在新文件上的复发**:上一轮我刚修完 `harmony-admin` / `harmony-imageprep`,
下一个**新建的**判据文件又踩了同一个坑。pi 之所以看见,只是因为他跑了整个套件 ——
**缺的不是"记得登记",是"新建判据文件"这个动作没有守卫**。这条形状与"写了判据忘了接线"同族,
只是这次忘的是**登记边界**。
处理:**按次数登记(2),不整文件放行** —— 整文件放行的话,将来在这个文件里写一句
真实的「这里没判 / 已知缺口」就**不会红**。那 2 处本身也不是"这块没验",
而是对**词表本身**的断言(`unverifiedReason(...)` 必须含「未验」)。
同时在 `docs/DEBTS.json` 补一笔 `deviceprobe-fixture-timing`(`where` 指向该文件)——
`debt-visibility` 的第二条要求"声明必须有对应的一笔",两处各写各的会让审计只找到一处。
这笔的**到期前提是"两份 fixture 变成当场采集而不是人工存文件"**。
⚠️ **Go 侧未能本机验证**:`go test ./internal/repo/` 在本机报
`module cache not found: neither GOMODCACHE nor GOPATH is set`。我读了
`TestDebtLedgerMatchesMeasurement`:它只校验"每笔都有 due/where"+"三笔必须同处登记",
**没有"所有 id 必须在 Go 侧也列出"的断言**,所以新增一笔不需要改 Go。
但这是**读代码得出的结论,不是跑出来的** —— 如实标成未验。
## 二、`blurStyleFor` 删除后:生产代码里 5 处注释在说一个**不存在的函数**
函数已按 pi 的裁定删除(别的会话的 `9a10ab2` 落的)。但删除后
`Wallpaper.ts`(4 处)与 `MainPage.ets`(1 处)还在用现在时提它:
```
Wallpaper.ts:242 "由 `model/Appearance.ts` 的 `blurStyleFor` 映射成系统材质档"
Wallpaper.ts:248 "页面拿它去问 `blurStyleFor`"
Wallpaper.ts:251 "`blurStyleFor` 也写了、就是没有任何调用点"
Wallpaper.ts:311 "`blurStyleFor` 里面也有一次 clamp,那是它自己的防线"
MainPage.ets:1711 "(`blurStyleFor` 那张表服务的是**材质档**…)"
```
**这正是本会话反复在消的"注释描述一份不存在的代码"**,而它现在比之前更危险:
下一个人读注释会去找一个**已经被有意删掉**的函数,找不到就会**重新实现它** ——
而"它为什么不该回来"恰恰是那次删除唯一值钱的东西。
已全部改成**过去时 + 它已删除**,并在 `Appearance.ts` 原处留了碑文(函数没了,理由不能没)。
`:251` 那处尤其要改:原文说"`blurStyleFor` 也写了、就是没有任何调用点"——
函数已不存在,这句会让读者以为**还差一个调用点没补**,而事实是**连函数都不该有**。
## 三、这条碑文判据我做了变异验证
`harmony-appearance.test.mjs` 里那条「碑文不许回来」的判据**确实在校验**(不是摆着好看):
把 `Wallpaper.ts` 那段碑文抹掉 ⇒ **红**;还原 ⇒ **绿**。
顺带核了它的**指向**:碑文现在的主要落点是 `Appearance.ts`(函数原来所在处),
而判据的正则锚的是 `Wallpaper.ts` —— 两处都有内容才过,我保留了 `Wallpaper.ts` 里的引用
(它说明"这里的 px 不是材质档"),所以判据成立。
## 四、未做
- 到期闸门那 **7 条**(pi 更正过我:`STATIC_ONLY` 是 7 不是 8,我上封记串了)**仍然没动**。
- `PROBE_DEVICE=none` 下**剩 5 条红**,都是**别的会话**新加判据但没更新登记数
(`narrow-layout` 88>64、`nav-merge` 9>8、`harmony-presets` 6>5、`commit-hygiene` 3>2)
加 `build-stamp`(`dist` 没重构建,与本次改动无因果)。**我没有替他们改**。
110 lines
6.7 KiB
JavaScript
110 lines
6.7 KiB
JavaScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { readdirSync } from 'node:fs';
|
||
import { dirname, join } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { prose } from './lib/read.mjs';
|
||
|
||
/*
|
||
★ 边界声明必须**同时进余额**(默认路径可见)—— 把 §16 本身变成判据(pi 2026-09-14)。
|
||
|
||
§16 原文是「"要提醒人的"输出必须走默认路径」。它一直是**靠记得问**的一条纪律 ——
|
||
而这轮我们已经确认过四次同一形状:**"我知道一个边界" → 写进注释/标签/信里 →
|
||
那个位置在默认路径上等于不存在**(余额打在 TestMain、权威源写在信里、overlay 边界写在注释里、
|
||
两条未覆盖的路写在判据标签里)。第五次它落在了**规则自己**身上:写在 CRITERIA.md 里的规则,
|
||
没有任何东西在判它被执行。
|
||
|
||
所以这条判据就是 §16 的可判形态:**凡在判据目录里声明"未覆盖 / 未验 / 已知缺口"的地方,
|
||
必须在 `docs/DEBTS.json` 里有一笔**(那里是默认路径可见的余额)。
|
||
|
||
登记方式与其它清册同形:**按文件 + 出现次数**(次数写死在这里,多一处即红),
|
||
且该文件必须被余额里的某一笔 `where` 引用到。
|
||
|
||
报错照 §14 写"正确修法 + 最常见的错误修法":正确修法是**把边界登记成一笔欠账**
|
||
(余额里可见、有到期前提),**不是**把下面这个数字 +1。
|
||
*/
|
||
|
||
/** 会被当作"边界声明"的措辞(新增措辞要连同它一起登记,别偷偷放行) */
|
||
const MARKERS = ['未覆盖', '未验', '已知缺口'];
|
||
|
||
/** 登记值:文件 → 该文件里边界声明的**出现次数上限**(超一处即红) */
|
||
const REGISTERED = new Map([
|
||
['background.test.mjs', 3], // 两条反向断言的未覆盖路(route B / route C)+ 说明
|
||
['harmony-appearance.test.mjs', 4], // bgBlur 消费侧/映射、运行期形态类边界
|
||
['harmony-logic.test.mjs', 1], // `.ets` 状态机要跑起来才算数
|
||
['debt-visibility.test.mjs', 12], // 本文件:N 处是词表定义 + 报错文案 + 上面那段解释(第 N+1 处即红)
|
||
['harmony-admin.test.mjs', 1], // 用户管理页:本机无设备 ⇒ 只能证明"代码里这么写"
|
||
['harmony-imageprep.test.mjs', 4], // 图片上传:压图/选图/服务端收下,三样本机都验不了
|
||
/*
|
||
* `harmony-deviceprobe.test.mjs` 的 2 处:**都不是"这块没验"的边界声明**,而是
|
||
* 对**词表本身**的断言 —— ① `unverifiedReason(VERDICT_OTHER)` 必须含「未验」;
|
||
* ② `mayAssertOn(…)` 那条的说明文字("失效方式是永远未验")。
|
||
* 但**按次数登记、不整文件放行**(π 2026-09-15 指出的形状):整文件放行的话,
|
||
* 将来在这个文件里写一句真实的「这里没判 / 已知缺口」就**不会红** ——
|
||
* 那正是"新文件不会自动跑一遍守卫"这个洞。2 就是上限,第 3 处即红。
|
||
*/
|
||
['harmony-deviceprobe.test.mjs', 2]
|
||
]);
|
||
|
||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||
|
||
test('★ 用**已登记词表**声明边界的文件,必须在余额里有对应的一笔(词表外的说法不在本判据范围内)', () => {
|
||
const ledger = JSON.parse(prose(join(HERE, '..', '..', '..', 'docs', 'DEBTS.json')));
|
||
const wheres = ledger.debts.map(d => `${d.where || ''}`);
|
||
|
||
/*
|
||
* 本文件**也**在受判之列 —— 而且**按次数**登记,不整文件放行(pi 2026-09-14)。
|
||
*
|
||
* 我第一版给它的是"整文件豁免",理由是"用自己定义的词表数自己无意义"。这句话是对的,
|
||
* 但它推出的是"**这 N 处**无意义",不是"**这个文件**无意义":整文件放行的后果很具体 ——
|
||
* 将来有人在这个文件里写一句真实的「这里没判 / 已知缺口」,①不会红。
|
||
* 那就是 `migrate.go` 那个形状换了个落点,这次落在判据自己身上。
|
||
*
|
||
* 所以现在把 N 当成普通登记值:N 处是**词表定义与报错文案**(它们必须提到这些词),
|
||
* 第 N+1 处就是新的边界声明 ⇒ 红。
|
||
*
|
||
* **词表是采样、不是完备**(同一个词表键控的盲区):换 `TODO` / 「这里没判」/「跳过」
|
||
* 等同义说法声明同一个边界,本判据**抓不到** —— 这条盲区已登记在 `docs/DEBTS.json`
|
||
* 的 `boundary-vocabulary-incomplete`,所以在默认路径的余额里看得见,不靠记得。
|
||
*/
|
||
const files = readdirSync(HERE).filter(f => f.endsWith('.test.mjs') || f.endsWith('.mjs'));
|
||
const findings = [];
|
||
for (const f of files) {
|
||
const src = prose(join(HERE, f));
|
||
let n = 0;
|
||
for (const m of MARKERS) n += src.split(m).length - 1;
|
||
if (n === 0) continue;
|
||
findings.push({ file: f, count: n });
|
||
}
|
||
|
||
// ① 出现次数必须与登记一致(多一处即红:那是**新**的边界声明,还没进余额)
|
||
const unregistered = [];
|
||
const overCount = [];
|
||
for (const { file, count } of findings) {
|
||
if (!REGISTERED.has(file)) { unregistered.push({ file, count }); continue; }
|
||
if (count > REGISTERED.get(file)) overCount.push({ file, count, max: REGISTERED.get(file) });
|
||
}
|
||
assert.deepEqual(unregistered, [],
|
||
`这些文件里有"边界声明",但一次都没登记:\n ${unregistered.map(x => `${x.file}(${x.count} 处)`).join('\n ')}\n` +
|
||
`**正确修法**:把该边界登记成 docs/DEBTS.json 里的一笔(这样它出现在 RESULT 行的余额里、带到期前提),` +
|
||
`再把本文件 REGISTERED 里的次数补上。\n` +
|
||
`**最常见的错误修法**:只把下面这个数字调大/把声明删掉 —— 那是把这条判据废掉(§14),` +
|
||
`边界会回到"只有读过源码的人才知道"。`);
|
||
assert.deepEqual(overCount, [],
|
||
`边界声明比登记的多(新增的还没进余额):\n ${overCount.map(x => `${x.file}:${x.count} 处 > 登记 ${x.max}`).join('\n ')}\n` +
|
||
`**正确修法**:先在 docs/DEBTS.json 里补一笔,再改这里的次数。**别只改数字**。`);
|
||
|
||
// ② 每一笔余额都必须指向一个**存在**的位置,且带到期前提
|
||
for (const d of ledger.debts) {
|
||
assert.ok((d.due || '').trim().length > 0, `欠账 ${d.id} 没写到期前提 —— 那不是欠账,是"我们知道"`);
|
||
assert.ok((d.where || '').trim().length > 0, `欠账 ${d.id} 没写判据位置`);
|
||
}
|
||
|
||
// ③ 有边界声明的文件,必须被余额里某一笔 where 引用到(两处挂钩,不是各写各的)
|
||
for (const { file } of findings) {
|
||
assert.ok(wheres.some(w => w.includes(file)),
|
||
`${file} 里有边界声明,但没有任何一笔欠账的 where 指向它 —— ` +
|
||
`两处各写各的,审计时只会找到一处(§10.1 的理由同此)`);
|
||
}
|
||
});
|