fix(判据): 探针三值 + 发布候选标签 —— 顺带查出探针从写下那天起一次都没跑成过
pi 的两条"真实的洞",都落了,而且第一条当场抓到实证。 1. **探针三值**(可用 / 不可用 / 拿不准→红):`RESULT static=5 probe=ok|unknown` 把"欠账余额"和"探针是否健康"拆成两个数字。 **换完第一次运行就报 probe=unknown** —— 一查:探针调的是 `execFileSync`, 而这个文件 import 的是 `spawnSync`,**名字根本没定义**。也就是说 **探针从写下的那天起一次都没跑成过**,旧的两值设计把 `ReferenceError` 和"没有设备"一起吞掉、统一报成"设备不可用":机制在、闸门从没开过, 而它看起来完全健康。这正是 pi 描述的"恒不开闸",只是比预想更彻底。 现在:命令在但跑不成 → unknown → 红;所有候选都不存在(本机没装 hdc)→ 可判的 "没有设备工具" → false,避免没装 SDK 的机器天天假红。 附 `--probe-selftest`(只跑分类器,不跑套件)+ 变异验证(把 unknown 当"不成立"→ 红)。 2. **releaseCandidate = !gitDirty**(从展示升成标签):BUILD_INFO 现在自报 `releaseCandidate`,发布脚本在脏树时会打印"这个包不是发布候选"。 判据 `build-stamp` 断言"标签与 gitDirty 必须一致"。 **实证**:本轮我打的包正是这种情况 —— `gitDirty: true`(含着 gui-lab 未提交的 NarrowStack/index.css),`releaseCandidate: false`,日志里明确说了"不是发布候选"。 3. 附带:`criteria-hygiene` 加一条"用到 `code/prose/bytes` 就必须真的 import"。 理由是同一形状我这轮在三个文件里各犯过一次(最后一次是 `execFileSync`/`spawnSync`), 而它表现为"判据红了"(ReferenceError 抛在判据自己身上),看起来像判据失败、 不像判据写错。这条至少把最常写错的那几个名字变成明确的红。
This commit is contained in:
@ -59,7 +59,7 @@ for (const must of ['配对/解析', 'allow-list', '变异验证', '剥掉注释
|
||||
|
||||
const SUITE = [
|
||||
['test/markdown-xss.test.mjs', [], 9],
|
||||
['test/narrow-layout.test.mjs', [], 62],
|
||||
['test/narrow-layout.test.mjs', [], 64],
|
||||
['test/nav-merge.test.mjs', [], 8],
|
||||
['test/theme.test.mjs', [], 30],
|
||||
['test/background.test.mjs', [], 42],
|
||||
@ -72,11 +72,11 @@ const SUITE = [
|
||||
['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 6],
|
||||
// 外观契约:默认值去 Go 源码里读(服务端 DefaultAppearance 是权威)+ 缓存键按账号
|
||||
['test/appearance-defaults.test.mjs', [], 3],
|
||||
['test/build-stamp.test.mjs', [], 6],
|
||||
['test/build-stamp.test.mjs', [], 7],
|
||||
['test/packaging.test.mjs', [], 5],
|
||||
['test/commit-hygiene.test.mjs', ['--experimental-strip-types', '--no-warnings'], 2],
|
||||
// 判据目录自身的卫生:读文本必须走 test/lib/read.mjs 的具名入口
|
||||
['test/criteria-hygiene.test.mjs', [], 2]
|
||||
['test/criteria-hygiene.test.mjs', [], 3]
|
||||
];
|
||||
|
||||
// 自检 1:清单里的文件必须真的存在(写错名字 = 那条判据永远不跑)
|
||||
@ -226,25 +226,91 @@ for (const [file, flags, expected] of SUITE) {
|
||||
* 这条是"自报 0 条 < 登记条数"的**时间版本**:那条管"判据还在不在",这条管
|
||||
* "它该升级了没有"。
|
||||
*/
|
||||
/**
|
||||
* 探针:判定静态判据的"到期前提"是否成立。
|
||||
*
|
||||
* # 三值,不是两值(pi 2026-09-14 §2)
|
||||
*
|
||||
* 原先只有"成立/不成立"两种结果,于是**"探针跑不了"和"设备不可用"被归成同一格**:
|
||||
* 设备那天真可用了,闸门也永远不会开 —— 机制在,闸门锈死,而且看起来完全健康
|
||||
* (这是"自报 0 条 < 登记 30 条"的第三种形状:探针自己坏了,没人知道)。
|
||||
*
|
||||
* 所以:
|
||||
* - `true` 可用(前提成立 → 依赖它的静态判据**到期**,必须处理);
|
||||
* - `false` 不可用(探针**确实跑成了**,结论是没有目标);
|
||||
* - `'unknown'` 拿不准(**命令在但跑不成**:超时、非零退出、抛异常)→ **按到期处理**。
|
||||
*
|
||||
* # 这条设计当场抓到了什么(写下来,因为它是"闸门锈死"的实证)
|
||||
*
|
||||
* 换三值之后第一次运行就报了 `probe=unknown` —— 一查:探针里调的是 `execFileSync`,
|
||||
* 而这个文件 import 的是 `spawnSync`(**名字根本没定义**)。也就是说
|
||||
* **探针从写下的那天起一次都没跑成过**,而旧的二值设计把 `ReferenceError`
|
||||
* 连同"没有设备"一起吞掉、统一报成"设备不可用"——**机制在、闸门从来没开过,
|
||||
* 而它看起来完全健康**。三值把它变成了一声明确的红:探针自己坏了,必须有人看一眼。
|
||||
*
|
||||
* 另外,`'unknown'` 只留给"命令在、但跑不成";**所有候选都不存在**(本机没装 hdc)
|
||||
* 是可判的事实(没有工具就不可能有设备),报 `false`,否则没装 SDK 的机器会天天假红。
|
||||
* 拿不准就红,让人看一眼 —— 这条判断比"猜一个"便宜得多。
|
||||
*
|
||||
* 测试用 `AGENTMAIL_PROBE_DEVICE=ok|none|unknown` 覆盖(判据自检要用:
|
||||
* 没有这个开关就没法验证"unknown 会不会红")。
|
||||
*/
|
||||
const PROBES = {
|
||||
device: {
|
||||
desc: '有可用的设备/模拟器(hdc 看得到目标)',
|
||||
run() {
|
||||
const override = process.env.AGENTMAIL_PROBE_DEVICE;
|
||||
if (override === 'ok') return true;
|
||||
if (override === 'none') return false;
|
||||
if (override === 'unknown') return 'unknown';
|
||||
const sdkHdc = '/opt/huawei/command-line-tools/sdk/default/openharmony/toolchains/hdc';
|
||||
const candidates = [sdkHdc, 'hdc'];
|
||||
let ranOnce = false; // 至少有一条命令**跑成过**(哪怕是"没有目标")
|
||||
let sawEmpty = false;
|
||||
let anomaly = ''; // 命令在,但跑不成(超时/非零退出/…)—— 这类是"拿不准"
|
||||
let allMissing = true; // 所有候选都不存在 = 本机没装设备工具(这是**可判**的:不可能有设备)
|
||||
for (const bin of candidates) {
|
||||
try {
|
||||
const out = execFileSync(bin, ['list', 'targets'], { encoding: 'utf8', timeout: 15000 });
|
||||
const t = out.trim();
|
||||
if (t && !/\[Empty\]/.test(t)) return true;
|
||||
} catch { /* 没有 hdc 或超时:前提不成立 */ }
|
||||
const r = spawnSync(bin, ['list', 'targets'], { encoding: 'utf8', timeout: 15000 });
|
||||
if (r.error && r.error.code === 'ENOENT') continue; // 这个候选不存在,看下一个
|
||||
allMissing = false;
|
||||
if (r.error) { anomaly = r.error.code || String(r.error.message || r.error); continue; }
|
||||
if (r.status !== 0) { anomaly = `exit ${r.status}`; continue; }
|
||||
ranOnce = true;
|
||||
const t = (r.stdout || '').trim();
|
||||
if (t && !/\[Empty\]/.test(t)) return true; // 明确可用
|
||||
sawEmpty = true; // 明确"没有目标"
|
||||
}
|
||||
return false;
|
||||
if (ranOnce && sawEmpty) return false; // 跑成了,且确实没有目标
|
||||
if (anomaly) return 'unknown'; // 命令在、跑不成 → 拿不准(要红)
|
||||
if (allMissing) return false; // 本机根本没有 hdc → 不可能有设备
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 探针结论 → 是否等于"前提成立(到期)" */
|
||||
const probeIsDue = v => v === true || v === 'unknown';
|
||||
|
||||
/** 只能验形态的判据:文件 + 为什么只能静态 + 到期前提 */
|
||||
/*
|
||||
* 探针自检(`--probe-selftest`):它判的是**判据自己的分辨力** ——
|
||||
* "unknown 到底会不会红"。没有这条,`probeIsDue` 哪天被改成 `v === true`
|
||||
* 也没人会发现,而那正是"闸门锈死"的写法。
|
||||
*/
|
||||
if (process.argv.includes('--probe-selftest')) {
|
||||
const cases = [
|
||||
['ok 可用 → 到期', true, true],
|
||||
['none 不可用 → 不到期', false, false],
|
||||
['unknown 拿不准 → 到期(必须红)', 'unknown', true],
|
||||
];
|
||||
let bad = 0;
|
||||
for (const [what, value, want] of cases) {
|
||||
const got = probeIsDue(value);
|
||||
console.log(`${got === want ? 'ok ' : 'RED '} ${what}(probeIsDue(${JSON.stringify(value)}) = ${got})`);
|
||||
if (got !== want) bad++;
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
}
|
||||
const STATIC_ONLY = [
|
||||
['test/harmony-nav.test.mjs', '底栏结构/命中区常量/挂载关系:`.ets` 要 hvigorw 才能编译、要设备才能点', 'device'],
|
||||
['test/harmony-appearance.test.mjs', '壁纸/令牌/遮罩渲染:观感与运行期换肤要设备', 'device'],
|
||||
@ -263,9 +329,18 @@ for (const [file, , probe] of STATIC_ONLY) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
const dueStatic = STATIC_ONLY.filter(([, , probe]) => PROBES[probe].run());
|
||||
const probeResults = {};
|
||||
for (const [, , probe] of STATIC_ONLY) {
|
||||
if (probeResults[probe] === undefined) probeResults[probe] = PROBES[probe].run();
|
||||
}
|
||||
|
||||
const dueStatic = STATIC_ONLY.filter(([, , probe]) => probeIsDue(probeResults[probe]));
|
||||
const unknownProbes = Object.entries(probeResults).filter(([, v]) => v === 'unknown').map(([k]) => k);
|
||||
if (dueStatic.length > 0) {
|
||||
console.error(`\n✗ 静态判据**到期**了:${PROBES[dueStatic[0][2]].desc} 现在是成立的 ——`);
|
||||
const firstUnknown = unknownProbes.includes(dueStatic[0][2]);
|
||||
console.error(firstUnknown
|
||||
? `\n✗ 探针**跑不了**(${PROBES[dueStatic[0][2]].desc})—— 拿不准就按到期处理,别让闸门锈死:`
|
||||
: `\n✗ 静态判据**到期**了:${PROBES[dueStatic[0][2]].desc} 现在是成立的 ——`);
|
||||
for (const [file, why, probe] of dueStatic) {
|
||||
console.error(` - ${file}(到期前提:${PROBES[probe].desc};当初只能静态的原因:${why})`);
|
||||
}
|
||||
@ -277,7 +352,15 @@ if (dueStatic.length > 0) {
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`RESULT static=${STATIC_ONLY.length}(只能验形态的判据:到期前提成立就自动变红)`);
|
||||
/*
|
||||
* 汇总里"欠账余额"和"探针是否健康"是两个不同的数字(pi §2):
|
||||
* static=5 —— 还欠着 5 条只能验形态的判据;
|
||||
* probe=ok —— 探针自己是好的(unknown 说明闸门可能锈死了,得人看一眼)。
|
||||
*/
|
||||
const probeSummary = unknownProbes.length ? 'unknown' : 'ok';
|
||||
console.log(`RESULT static=${STATIC_ONLY.length} probe=${probeSummary}` +
|
||||
(unknownProbes.length ? `(探针跑不了:${unknownProbes.join('、')} —— 已按到期处理)`
|
||||
: '(只能验形态的判据:到期前提成立就自动变红)'));
|
||||
|
||||
console.log(`\n========== 判据汇总 ==========`);
|
||||
if (reds.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user