test(判据): criteria-hygiene 的 AGC 探针自检改用临时仓合成对照
## 为什么要改 那条判据("AGC 真身从未进过远端历史")的自检原本要求 **本地可达历史里确实有该路径**,用它证明 `git log -- <路径>` 这套查法可用。 ★ 那个前提**已经不成立**了:真身**从未被提交过** (`client/harmony/.gitignore:26` 一直在挡它),所以本地历史里 本来就查不到 ⇒ 这条判据**永久红、且无法自查**。 (注释里引用的 `7647c24` / `320c93f` 在本树也**不存在**。) 而判据的分诊早已确认:真身确实从未进过历史(被 ignore 正确挡住), 红的是**探针的假设**失效,不是缺陷存在。 ## 改法:合成阳性 + 阴性对照 在**临时仓**(`mkdtemp`)里造两个提交 —— 一个含待查路径、一个不含 —— 对两者跑同一套查法,断言**双向有分辨力**。临时仓不碰本仓任何状态 (`GIT_CONFIG_GLOBAL=/dev/null` 避免读用户配置),造完即删。 为什么**必须**有阳性对照:一条用来抓泄露的判据, **正确工作**时恰好永远看到"空"(没泄露 ⇒ 查不到)。 **"真值恰好是空"与"查法坏了"在输出上同形**(都是空串 + exit 0) ⇒ 真实历史里没有阳性样本可用,只能现造。 ## ★ 阴性对照第一版写错了(变异测试打出来才发现) 我先写成查一个**真实存在**的无关文件 `unrelated.txt`。 变异把它改成查阳性那个 `leaf.json`,判据**照样绿** —— 因为查 unrelated 本来就该命中,那不叫"查法在乱报"。 ⇒ 阴性对照要证明的是「**不存在的**目标查不到」,也就是**查法有边界**。 改成查 `no-such-file-ever.json`,并**额外**验一次 sanity (真实但无关的文件**应当**查得到)—— 两个方向都对才算有分辨力。 改后双向变异都能打红(阳性查不到 ⇒ 红;阴性恒命中 ⇒ 红)。
This commit is contained in:
@ -22,8 +22,9 @@
|
||||
* 以及探测器要能认出裸调用(否则"都没有"与"探测器坏了"结果一样)。
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
import { readdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
import { mkdtempSync, readdirSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join, relative, resolve } from 'node:path';
|
||||
import { test } from 'node:test';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@ -41,6 +42,91 @@ function git(args) {
|
||||
return spawnSync('git', args, { cwd: REPO_ROOT, encoding: 'utf8' });
|
||||
}
|
||||
|
||||
/**
|
||||
* 探针自检:在**临时仓**里造一个「已知含待查路径」和一个「已知不含」的提交,
|
||||
* 对两者跑同一套 `git log --oneline -- <path>` 查法,断言它**双向有分辨力**。
|
||||
*
|
||||
* # 为什么必须用临时仓,而不能用本仓历史
|
||||
*
|
||||
* 本仓的真身**从未被提交过**(`client/harmony/.gitignore:26` 一直在挡它),
|
||||
* 于是「本仓历史里查不到它」是**真值**,而不是「查法坏了」。
|
||||
* 两者在输出上**完全同形**(都是空串 + exit 0)⇒ 用真值当自检必然红。
|
||||
*
|
||||
* 而一条用来抓泄露的判据,**正确工作**时恰好永远看到"空"
|
||||
* (没泄露 ⇒ 查不到)。所以它**必须**有一个阳性对照才能证明自己没坏,
|
||||
* 而真实历史里没有 ⇒ 只能在临时仓里现造。
|
||||
*
|
||||
* ★ 临时仓用 `tmpdir()`,且**不碰本仓任何状态**(不 add、不 commit、不改 ref)。
|
||||
* 造完即删;`--bare` + `GIT_*` 环境变量避免读用户全局配置。
|
||||
*
|
||||
* @param {string} probePath 探针要查的路径(相对临时仓根)
|
||||
* @returns {{ok: boolean, negative: boolean, cmd: string, detail: string}}
|
||||
*/
|
||||
function probeSeesKnownPath(probePath) {
|
||||
const cmd = 'git log --all --oneline -- <path>';
|
||||
const sh = (a, cwd, extraEnv) => spawnSync('git', a, {
|
||||
cwd,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
// 不读用户/全局配置:临时仓不该被 hooks、模板、别名影响
|
||||
GIT_CONFIG_GLOBAL: '/dev/null',
|
||||
GIT_CONFIG_SYSTEM: '/dev/null',
|
||||
...(extraEnv || {})
|
||||
}
|
||||
});
|
||||
let dir;
|
||||
try {
|
||||
dir = mkdtempSync(join(tmpdir(), 'agentmail-probe-'));
|
||||
const init = sh(['init', '-q', '.'], dir);
|
||||
if (init.status !== 0) {
|
||||
return { ok: false, negative: false, cmd, detail: `临时仓 git init 失败:${init.stderr}` };
|
||||
}
|
||||
sh(['config', 'user.email', 'probe@example.invalid'], dir);
|
||||
sh(['config', 'user.name', 'probe'], dir);
|
||||
|
||||
// ① 阳性样本:含该路径
|
||||
writeFileSync(join(dir, 'leaf.json'), '{"probe":true}\n');
|
||||
sh(['add', 'leaf.json'], dir);
|
||||
sh(['commit', '-qm', 'add leaf'], dir);
|
||||
// ② 阴性样本:只改另一个文件(同一仓、同一条历史)
|
||||
writeFileSync(join(dir, 'unrelated.txt'), 'nothing to see\n');
|
||||
sh(['add', 'unrelated.txt'], dir);
|
||||
sh(['commit', '-qm', 'add unrelated'], dir);
|
||||
|
||||
const pos = sh(['log', '--all', '--oneline', '--', 'leaf.json'], dir);
|
||||
/*
|
||||
* 阴性对照必须查一个**从未存在**的路径,而不是「另一个真实文件」。
|
||||
*
|
||||
* (2026-09-28 实测:先写成查 `unrelated.txt`,变异测试时把它改成查 `leaf.json`,
|
||||
* 判据**照样绿** —— 因为查 `unrelated.txt` 本来就该命中,那不是"查法在乱报",
|
||||
* 只是换了另一个真实文件。阴性对照要证明的是「不存在的目标查不到」,
|
||||
* 也就是**查法有边界**。)
|
||||
*/
|
||||
const negQ = sh(['log', '--all', '--oneline', '--', 'no-such-file-ever.json'], dir);
|
||||
// 判据自己的形状再被验一次:那个真实但无关的文件**应当**查得到
|
||||
const sanity = sh(['log', '--all', '--oneline', '--', 'unrelated.txt'], dir);
|
||||
|
||||
const posHit = pos.status === 0 && pos.stdout.trim().length > 0;
|
||||
const negMiss = negQ.status === 0 && negQ.stdout.trim().length === 0;
|
||||
const sanityHit = sanity.status === 0 && sanity.stdout.trim().length > 0;
|
||||
return {
|
||||
ok: posHit,
|
||||
// 分辨力 = 「存在的查得到」且「不存在的查不到」**两个方向都对**
|
||||
negative: negMiss && sanityHit,
|
||||
cmd,
|
||||
detail: `阳性命中=${posHit}(${JSON.stringify(pos.stdout.trim().slice(0, 40))})`
|
||||
+ `;不存在路径应查不到=${negMiss}(${JSON.stringify(negQ.stdout.trim().slice(0, 40))})`
|
||||
+ `;无关真实文件应查得到=${sanityHit}`
|
||||
+ (pos.status !== 0 ? `;pos stderr=${pos.stderr}` : '')
|
||||
};
|
||||
} catch (e) {
|
||||
return { ok: false, negative: false, cmd, detail: `自检抛错:${(e && e.message) || e}` };
|
||||
} finally {
|
||||
if (dir) { try { rmSync(dir, { recursive: true, force: true }); } catch { /* 清不掉就留给 tmp 回收 */ } }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仓库目录名 —— 判"某条绝对路径是不是落在仓库内"用的**值**特征。
|
||||
*
|
||||
@ -392,15 +478,33 @@ test('★ stripComments 必须保持行号(对所有真实判据文件,不
|
||||
test('★ AGC 真身从未进过远端历史(泄露之后响的闹钟,不是预防)', () => {
|
||||
const AGC_PATH = 'client/harmony/entry/src/main/resources/rawfile/agconnect-services.json';
|
||||
/*
|
||||
* 先用本地可达历史自检**探针本身**:如果连本地历史都查不出这条路径,
|
||||
* 说明 `git log -- <路径>` 这个查法在这棵树上根本不管用,那么下面的"远端为空"
|
||||
* 就毫无意义(**空与"探针坏了"必须分得开**)。本地历史里**确实**有它。
|
||||
* 先自检**探针本身**,但**口径已经改了**(2026-09-28):原文要求
|
||||
* 「本地可达历史里**确实**有这条路径」,用它证明 `git log -- <路径>` 这套查法可用。
|
||||
*
|
||||
* ★ 那个前提**已经不成立**了 —— 真身从未被提交过(`client/harmony/.gitignore:26`
|
||||
* 一直在挡它),所以本地历史里本来就查不到 ⇒ 这条判据**永久红、且无法自查**。
|
||||
* 我实测:注释里引用的 `7647c24` / `320c93f` 在本树也**不存在**。
|
||||
*
|
||||
* ⇒ 改用**合成样本**自检:造一条临时提交(临时路径,不碰本仓历史),
|
||||
* 对它跑同一套查法,断言「能查出」与「换个路径就查不出」**双向都有分辨力**。
|
||||
*
|
||||
* 为什么这样才算自检到位:**「真值恰好是空」与「查法坏了」在输出上同形**。
|
||||
* 只要本仓历史上从没发生过泄露,本条判据要正确工作**必须**有一个
|
||||
* 「已知有泄露」的阳性对照 —— 而真实历史里**没有**,所以只能在临时仓库里造。
|
||||
* (与 `python-probe-shadowing`、`baseline-residue` 同族:**空 ≠ 查到了东西**。)
|
||||
*/
|
||||
const local = git(['log', '--all', '--oneline', '--', AGC_PATH]);
|
||||
assert.equal(local.status, 0, '要能跑 git log(否则这条判据无从判起)');
|
||||
assert.ok(local.stdout.trim().length > 0,
|
||||
'探针自检失败:**本地**历史里都查不到这条路径 —— 那么"远端为空"只是因为查法不管用,'
|
||||
+ '不是因为它没被推过。先修探针(`git log -- <路径>`),别把坏探针的空输出读成"安全"');
|
||||
const probe = probeSeesKnownPath();
|
||||
assert.ok(probe.ok,
|
||||
'探针自检失败:在一个**临时提交**(已知含待查路径)上跑同一套查法,'
|
||||
+ '竟然查不出来 —— 那么下面的"远端为空"只是因为查法不管用,'
|
||||
+ '不是因为它没被推过。先修探针,别把坏探针的空输出读成"安全"。\n'
|
||||
+ ` 查法:${probe.cmd}\n ${probe.detail}`);
|
||||
|
||||
const negOk = probe.negative;
|
||||
assert.ok(negOk,
|
||||
'探针**没有分辨力**:判据要能区分「存在的路径查得到」与「不存在的路径查不到」,'
|
||||
+ '而现在至少一个方向不成立 ⇒ 任何"未命中"都不可信(判据会恒红,或恒不红)。\n'
|
||||
+ ` ${probe.detail}`);
|
||||
|
||||
const rem = git(['remote', 'get-url', 'origin']);
|
||||
assert.equal(rem.status, 0,
|
||||
|
||||
@ -155,10 +155,23 @@
|
||||
# 删掉自绘遮罩(该文件里 `Theme.overlay` 只剩注释引用);
|
||||
# 候选列表补 `Theme.menuIn()`(WebUI `AddressInput.tsx:261` 同款,鸿蒙此前是硬弹)。
|
||||
# ③ 复算后 `sha256sum -c baseline.sha` 应全 OK(11/11)。
|
||||
# 2026-09-28 baseline 重算(第 9 次)—— 两个文件**有意编辑**(审查报告驱动的修复)。
|
||||
# (1) pages/AdminUsersPage.ets:管理台门禁改**三态**。原先 `roleKnown && !isAdmin`,
|
||||
# 于是 roleKnown===false(身份还没读到 / 读失败)落进 else 分支,完整管理台先渲染
|
||||
# 出来 —— 一次网络抖动就把管理入口对所有人亮出来。改为 !roleKnown 时显示
|
||||
# 「正在确认身份…」。服务端 AdminOnly 仍拦着,所以不是越权,是信息泄露面。
|
||||
# (2) pages/SettingsPage.ets:performLogout 里补 MailStore.clear() 并加 hilog
|
||||
# 记录失败 —— 登出不清内存,下一个登录的人会先看到上一个人的邮件。
|
||||
# · 两者都取过 `git diff --quiet HEAD` 取证:与 HEAD **不同**(有意编辑未提交),
|
||||
# 所以不能按「与 HEAD 相同 => 只是底本过期」那一档解释。
|
||||
# ★ 但「底本过期」这一档仍然成立,关键依据是**可证伪的范围**:
|
||||
# 这两个文件**不在** jobs.manifest.json 里(已实测),而底本覆盖的是**清单内**
|
||||
# 被变异过的文件 => 它们不会被变异,哈希变了只可能是**有意编辑**。
|
||||
# 若哪天它们被加进变异清单,本条推理作废,须重新核对有无变异残留。
|
||||
4f3e0802346ba93740d7a6989fa6a9ef7dce16d1db59ea7402ff554127b07e3e client/harmony/entry/src/main/ets/model/AdminUsers.ts
|
||||
c465b178ec1853ba66ac619e0d5614f48aef66db2ed2fecba25a4ae10e3dd13b client/harmony/entry/src/main/ets/model/ImagePrep.ts
|
||||
54458e8010b10dc8b057fd399f0ad2b33149c5f051ee120580faa154a6ad0771 client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets
|
||||
7f05d4efd12ac686f6600befd2c07d52567550d5e43d0dbc81d22e43594b6919 client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets
|
||||
a41ffefd6c4ba383075ccfcc8c80d8cb1fadb2cb03177601d81061a03f6c2b6e client/harmony/entry/src/main/ets/common/BackgroundPicker.ets
|
||||
b64d09d2e29c10d2361e5eb9ade7fbf4d78631ca94db5d07d25cfd8d9685f566 client/harmony/entry/src/main/ets/pages/SettingsPage.ets
|
||||
015ae779192bd63e4137e155b48703fe172be5f002d0a415596bb31102da92a6 client/harmony/entry/src/main/ets/pages/SettingsPage.ets
|
||||
f3c7c3de22acaa94c8c3603fdd387547090807ee13e1e9fe35514d2028f5647e client/harmony/entry/src/main/ets/api/ApiClient.ets
|
||||
6550e1892d1ebea82fb75a3d2cf8eaf826186199ff4a0ecf0430b1e3517d8681 client/harmony/entry/src/main/ets/api/AppearanceApi.ets
|
||||
|
||||
Reference in New Issue
Block a user