Files
MailUI4Agents/plugins/pi-mail-bridge/lib/env-error.mjs
JianFeeeee 5bc579f910 fix(pi-bridge): 按评审补三处 —— ENOSPC 只盖了一个写点、旧注释自相矛盾、兜底判据钉的是文本
pi 逐字读了上一版落地的代码,报了三个"还差一格"。都不是推翻,是同一根因
("环境不足伪装成别的")在这套守卫自己身上的残留。

## 一、翻译只覆盖了 5 个写点里的 1 个(最实质)

`selfCheck()` 要在临时目录造两棵样本树,写点有**五处**;上一版只把 `mk()` 里那两处
包了 try/catch,后面三处(`README.md` / `extra.mjs` / `test/t.mjs`)裸写。它们撞上
ENOSPC 时异常冒到 `main()` 的 catch:**退出码是对的(2),但打印的是原始英文
`ENOSPC: no space left on device, write` 加一段指向本文件的堆栈** —— 也就是上一版
要治的那个信号("看起来像检查器坏了")**恰恰在最需要它的路径上还在**。

改法:抽一个 `describeEnvError(e, what)`,在 `main()` 的 catch 里**统一**换成人话。
一处覆盖全部写点,以后再加写点也不用管。`mk()` 里那段裸判断一并换成调用它。

## 二、`lib/tmp-space.mjs` 的头注释在说谎(读者已误读一次)

原文写"`availBytes` 为 `null`(读不到 / 平台不支持 / **字段为 0**)" —— 而"字段为 0"
指的其实是 `statfs.bsize === 0`(测量层确实 `if (!s.bsize) return null`),读起来
却像是在说"可用 0 字节也算不知道" —— **正是我上一版刚踩、刚补判据的那个坑**。
pi 第一遍读就误读成了后者。已把两个 case 分开写死,并注明"这条注释写错过一次"。

## 三、兜底判据钉的是文本,不是机制

`env-guard.test.mjs` 原来对 `session-scan.test.mjs` 断言 /ENOSPC/ 与 /环境/,
而那段**解释性注释里本来就有这两个词** ⇒ 删掉整段翻译逻辑、只留注释,判据照样绿。
这正是 `permission-note.test.mjs` 自己警告过的"钉装饰不钉机制"。

改法(按仓库规矩,纯函数 + 反面样本 + 接线):
- 翻译逻辑提到 `lib/env-error.mjs` 的 `translateEnvError`(纯函数);
- 判据喂构造出来的错误验**行为**:ENOSPC 必须翻译且带药方、普通错误必须**原样返回
  同一个对象**("什么都翻译"比不翻译更坏 —— 真缺陷会被套上环境的外衣);
- `writeSession` 抽出 `write` 参数(**只为测试存在**,`pool.mjs` 的 `workerPath` 同一手法),
  于是"接线还在不在"是**行为**判据:喂一个必然 ENOSPC 的假写,翻译必须发生。
  抽它的理由写在注释里 —— 是"可被反面样本喂",不是复用(只有一个调用点)。
- 变异自检:删掉写点的翻译 ⇒ 第 28、29 两条立刻红(已实测)。

## 四、顺带三处小的一致性问题

- 端到端那条判据原靠"本机 /tmp 恰好是满的"来验 —— 那是把判据绑在**会变的环境**上,
  /tmp 一清空就自动跳过、无声失效。前置脚本加两个**只为测试存在**的开关:
  `--measure=<dir>`(只量并打印 JSON)与 `--inject-avail=<n>`(绕过测量直接判定),
  于是"不足⇒exit 2"与"充足⇒放行"在任何机器上都验得了(两个方向都验,缺一即假绿)。
- 判据 ⑥ 原先只有它自己带圈号前缀,读者会去找不存在的第 ⑤ 条。改成 `checkLayout`
  的每条都带**连续 id**(1..N),`name` 是纯展示串,并加一条"id 不许跳号"的自检。
- 两个实测数(`729_088` 字节 = 0.70 MiB、`712` 字节)是**不同时刻**量的,并列摆着像抄错,
  各标了来历;`lib/tmp-space.mjs` 里那条改用"一度真是 0"的说法。

验证:`npm test` **474/474**(上一版 453);`--self-check` **18 条全过**(新增 id 连续);
`npm test` 在临时目录不足时仍 exit 2 且一条用例都不跑。
2026-09-14 19:39:07 +08:00

41 lines
2.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 「临时目录写不进去」⇒ 人话。**纯函数,可被反面样本喂**。
*
* # 为什么它必须是一个函数,而不是散在写点里的 try/catch
*
* 2026-09-14 实测:`/tmp` 是满的 tmpfs,`bavail` 一度真是 **0**。此时这条
* `npm test` 红的是
*
* not ok 323 - ★巨大的 message 行不进内存也不影响解析
* error: 'ENOSPC: no space left on device, write'
*
* 那条红的**形状指向内存**(用例名里就写着"不进内存",而它恰好是往临时目录
* 写文件的用例)⇒ 下一个踩到的人会去 `session-scan.mjs` 找一个**不存在**的
* 内存缺陷。翻译成人话("这是环境问题,不是内存缺陷")就治这个。
*
* # 为什么抽出来(只有一个调用点也值得抽)
*
* **不是为了复用,是为了可被反面样本喂**:`translateEnvError` 能被直接喂一个
* 构造出来的 ENOSPC 错误,验"该翻译的翻译了、不该翻译的原样返回"。
* 反例:第一版把这个判断留在 `session-scan.test.mjs` 里,判据只能靠**读源码文本**
* (断言文件里出现 `/ENOSPC/`)—— 而那段解释性注释里本来就有 "ENOSPC" 这个词,
* 于是**删掉整个翻译逻辑、只留注释,判据照样绿**。这正是
* `permission-note.test.mjs` 警告过的"钉装饰不钉机制"。
*
* @param {unknown} e 捕获到的错误
* @returns {{ translated: boolean, error: Error }} 翻译过的新错误,或原样返回
*/
export function translateEnvError(e) {
const message = e && typeof e.message === 'string' ? e.message : String(e ?? '');
const isEnospc = (e && e.code === 'ENOSPC') || /no space left on device/i.test(message);
if (!isEnospc) return { translated: false, error: e };
const err = new Error(
'环境不足:临时目录写不进去(ENOSPC)—— 这是环境问题,不是内存缺陷。' +
'药方:TMPDIR=<有空间的目录> npm test'
);
err.code = 'ENOSPC';
err.cause = e;
return { translated: true, error: err };
}