按 pi 复核改五处:判据的"值/行为"原则、真正的构建门、typecheck 进门、Go 判据零匹配、迁移归属
pi 用反例与变异逐条点了五处,全部**先跑变异再改**(结论都写在原地)。
## 1 判据③:形状正则已删(它永远差一个反例)
实测 pi 的反例 —— `const k = PREFIX + accountId; return PREFIX;`(拼了但没返回)——
对第三版判据**仍然全绿**:第三版锚的是"**函数体里存在**这样的表达式",不是"**返回的**表达式"。
三版的骗法各一个(签名里的参数 / 提了一下没用 / 拼了没返回),都在判"源码里有没有那个形状",
而缺陷是"算出来的值对不对" ⇒ **权威交给行为判据**(`test/stores/background.test.ts` 直接断言
`storageKey('a') !== storageKey('b')`、`=== 'agentmail.background.acct-a'`、不退回全局键),
正则那条删掉并把反例写在原地(否则下一个人会好心加回来)。
保留"键必须来自 storageKey()"那条:那是**来源**约束,不是值对不对,正则在这里合适。
鸿蒙侧只能静态判(`.ets` 本机没有运行时),已把这条限制写进判据说明。
推广进规范:CRITERIA.md §6.7 + run-all 自检关键词(10 → 11)。
## 2 真正的门:`scripts/release-linux.sh`
- 实测 pi 提的变异:**`touch dist/index.html` 时 stamp 判据是绿的** —— 它抓不到"构建失败但碰过 dist"。
stamp 是**探测器**(抓"src 改了产物没跟上"),门是**喂退出码**,两者不互替(§6.7.1)。
- `build:linux` 里**没有管道**(`&&` 链,退出码本来就传),但那次的哑巴失败是我在命令行手打
`npm run build 2>&1 | tail -4 && …` 造成的;同时发现它跑的是**裸 `vite build`,跳过 `gen:bg`**。
- 于是把配方收成 `scripts/release-linux.sh`:`set -euo pipefail` + 走 `npm run build` + 再打包。
- 判据是**行为**的:注入失败的构建(`AGENTMAIL_BUILD_CMD='exit 7'`)→ 断言退出码非 0
**且打包那步没跑**(标记文件不存在)。变异:脚本改成 `|| true` → 红 ✓。
## 3 typecheck 进 `npm test` 链
`npm run typecheck` 原本就有,但没人跑。先修掉它唯一的报错(我自己留下的未使用 import),现在干净;
`test` = run-all + vitest + typecheck。它恰好检查**没被任何测试 import 的文件**(vitest 只解析被测到的图)——
也就是那次 `is not exported by` 的形状。
## 4 Go 源码判据:零匹配 / 读不懂 都要红
改成三分:切不出函数体 → 红;字段在但值不是字面量 → 报「**判据读不懂**」(变异:`BgDim: defaultDim` → 红 ✓);
字段不在 → 红。静默放行是这类判据最危险的失败方式。
**措辞更正**:这条核对的是"与**这份服务端源码**的契约",不是"在跑的服务端二进制是 12/4"
(与"dist 是产物"同构);文档同步改。
## 5 迁移归属:定向做不到,就把"不可恢复"降级成"可恢复"
查实:`accountStore` 的 `activeId` 是**派生视图状态、不落盘**(`persist(accounts)` 只存数组),
所以**本机没有"上次活跃账号"标记可定向** —— 旧值的作者事后无法还原,定向迁移在原理上做不到。
升级前用 B、升级后先登录 A ⇒ A 接管 B 的外观,**一次性错档**,触发条件就这一条。
两件能做的都做了:**写了回读校验**(写不进去就不删旧键,避免净损失;变异:改成先删后写 → 2 条红 ✓)、
**删前另存** `agentmail.background.legacy.bak`(只写不读 ⇒ 不引入新的继承源,判据钉"只写不读")。
§7.12 把本地这半与显形方式写进同一行。
## 验证
`npm test` 退出码 0:14 个判据文件全绿 + vitest **265** 通过(+2 迁移行为测试)+ typecheck 干净;
`hvigorw assembleHap` BUILD SUCCESSFUL;安装包经新脚本重打(dist 与包同批)。
This commit is contained in:
@ -210,6 +210,48 @@ finish('标签'); // 打汇总 + `RESULT pass=N fail=M`,有失败则
|
||||
> 原则:**red 是那个人一定会看到的东西,文档不一定被打开** —— 错误信息是最省成本的交接面。
|
||||
> (验证方式:真删掉一条判据的 marker 行跑一遍,确认错误信息能照抄执行。)
|
||||
|
||||
## 6.7 判据的作用对象是**值/行为**时,不要退化成对源码形状的匹配
|
||||
|
||||
(pi 2026-09-14 用反例钉死这条,起因是我写的一条"取键函数必须把账号拼进键"的判据连改三版。)
|
||||
|
||||
那一族判据的形状是:*去源码里找某个写法*。它**永远差一个反例** —— 我实测过的三版:
|
||||
|
||||
| 版本 | 判什么 | 用什么骗过去 |
|
||||
| --- | --- | --- |
|
||||
| 一 | 整段(含签名)里同时出现 `accountId` 与常量 | `return STORAGE_KEY_PREFIX;` —— 参数表里的 `accountId` 就满足了 |
|
||||
| 二 | 函数体里两者**离得近** | `return accountId ? PREFIX : PREFIX;` —— 提了一下没用 |
|
||||
| 三 | 函数体里**同一个表达式**既含常量又拼接账号 | `const k = PREFIX + accountId; return PREFIX;` —— **拼了但没返回**(这一版我实测仍然全绿) |
|
||||
|
||||
三版都在判"源码里有没有那个形状",而缺陷是"**算出来的值对不对**"。闭合的形状是**行为判据**:
|
||||
|
||||
```ts
|
||||
strictEqual(storageKey('a') !== storageKey('b'), true);
|
||||
strictEqual(storageKey('a').includes('a'), true);
|
||||
```
|
||||
|
||||
这两行对"拼了没用上""拼了又丢掉""换个名字的退化"都红,且不误伤合法重构;
|
||||
正则那条的长期效果只剩"将来一次无害重构给你一个假红"。**所以:能跑的值就用跑的,
|
||||
正则降级成顺带看一眼,或者直接删。** 删的时候把"为什么删"和反例写在原地,
|
||||
否则下一个人会好心把它加回来。
|
||||
|
||||
**这条规则的适用边界(别过度推广)**:
|
||||
- 判"**代码里有没有这个调用/这个来源**"(例如"写缓存只许用 `storageKey()`,不许写死键名")
|
||||
—— 这是**来源**约束,不是值对不对,正则在这里是合适工具;
|
||||
- 对象**跑不起来**时(`.ets` 在本机没有运行时:编译要 hvigorw、运行要设备,
|
||||
而设备在这条链上不可用,见计划文档 §7.21),静态匹配是唯一可用的手段 ——
|
||||
但要把"这只证明形状、不证明值"写进判据的说明里,别让它冒充行为验证;
|
||||
- 与"自报条数 < 登记条数"同族:**静默放行**是这类判据最危险的失败方式。
|
||||
|
||||
### 6.7.1 附:**探测器**与**门**不是一回事
|
||||
|
||||
同一次讨论(pi 2026-09-14 §2)里还有一条:`dist` 比 `src` 新**只说明"src 改了而产物没跟上"**,
|
||||
不说明"产物是从当前 src 构建的"。实测:构建失败但**已经碰过** `dist/index.html` 时,
|
||||
那条判据**是绿的**(我跑过这个变异,它抓不到)。真正的门是**喂退出码**:
|
||||
`set -euo pipefail` / 不接管道 / 看 `PIPESTATUS`。两者不互替 ——
|
||||
门负责"失败就别产出",探测器负责"产出跟上了没有"。
|
||||
本仓的门是 `scripts/release-linux.sh`(构建失败即停,判据注入失败构建验过),
|
||||
探测器是 `test/build-stamp.test.mjs`。
|
||||
|
||||
## 7. 判据要钉用户真正会点的那一层
|
||||
|
||||
(移交信里交代的头号纪律)判据通过了但用户点不到,等于没做。
|
||||
|
||||
Reference in New Issue
Block a user