fix(相位/安全): 部署门禁只判产物自证;静默 break 改成出声;内核读数带时间坐标

pi 2026-09-14 的裁定与两条更正,逐条落地。

1. **相位裁定(选 c)**:`packaging`/`build-stamp` 属于**构建相位**,不属于安装相位。
   `run-all.mjs` 现在有相位:`AGENTMAIL_CRITERIA_PHASE=install`(部署门禁用)。
   每条判据登记它读的哪一侧(`ARTIFACT`/`SOURCE`),install 相位里出现 SOURCE 侧判据 → 红;
   被跳过的判据**点名打印**,不静默丢。汇总打 `RESULT phase=build|install`。
   规则入册 `test/CRITERIA.md` §11(含三个真实实例:check-shared-libs 恒红、
   packaging 一改前端就卡死、HOME 在门禁跑完之后才炸)。

   安装相位**真正能判的那一半**:`deploy/install.sh` 读**产物自证**(不重算 dist)——
   `releaseCandidate !== true` → 拒绝;产物 `gitRev` ≠ HEAD → "这个包比源码旧" → 拒绝;
   放行要显式 `--allow-dirty` / `--allow-stale`;`--check` 干跑只报结论不拦。
   实测干跑输出:`产物:gitRev=6702cc2 树=dirty releaseCandidate=false | 当前 HEAD=6702cc2`
   → 报"不是发布候选 + 正式安装会被拒绝 + 要放行请显式说清"。

2. **别解析运行器文本**(pi §5):`broken`/`red` 的判定改成按 TAP 的**名字**——
   文件级失败的测试名就是路径,断言失败的名字是判据名。变异双向验证:
   未定义标识符 → 「跑不起来的判据」;把某条判据条件改成假 → 「红的判据」。
   不再往关键字表里加补丁(那是往文本解析里加补丁,方向是错的)。

3. **静默 break 是安全相关**(pi §3):`session_update` 找不到活动会话时不再静默 break,
   改成出声日志(走 journalctl 那条通道),写清两种成因(此刻没在跑 / **接管会话**重启后无法定位)、
   方向(收紧被延迟)、以及兜底的**前提**("下次投递"要求这条会话还会收到新邮件)。
   `lib/mail-session-id.js` 模块头同步改成安全相关措辞("人以为自己收紧了权限、实际没有"),
   四桥逐字节同源,`check-shared-libs.sh` 退出码 0。

4. 内核读数补时间坐标(pi 13ea2fdf):`BUILD_INFO.txt` 里除原始 `dep`/`=>` 行外,
   现在还有 `kernelBinMtime` 与**正在运行的进程启动时间** —— 二进制会在两次读数之间被换掉,
   没有时间坐标的读数不成立。
This commit is contained in:
2026-09-14 16:54:30 +08:00
parent 6702cc2f5e
commit 33488760ce
9 changed files with 395 additions and 10 deletions

View File

@ -355,3 +355,23 @@ pi 2026-09-14 提的形状:**不是无条件 fail-closed** —— 缺字段可
| 邮件的 `permission_mode` | **不写、不改档**(不是写默认档) | 窄(fail-closed) | 本仓 2026-09-14:缺字段被 `\|\| 'workspace'` 兜成窄档,等于"一个 bug 以正常形态活着";守卫见 `plugins/*/lib/permission-mode.js` 的 `modeForStateWrite` |
| HomeAgent `plugin.json` 的 `sdk` | **内核不读**(字段只对人有效) | 无(不是语义,是文档) | 内核 `internal/plugin/manifest.go` 的结构体里没有该字段;`registry.go` 的 `loadOne` 只用 `NameZh/NameEn` |
| HomeAgent `plugin.json` 的 `capabilities` | **不受限** | 宽(fail-open) | 内核注释明写理由:17 个存量清单都没有它,空声明当最小权限会让它们**静默降级** |
## 11. 相位:门不许挂在它判不了的相位上
**规则(可机检)**:**部署门禁只允许读"待安装的产物 + 目标机状态",不许依赖"源码树是最新构建的"这个前提。**
一条门如果在某个相位**恒红或恒绿**,那不是门的问题,是**相位挂错了**。
两个相位问的是不同的问题:
| 相位 | 问的问题 | 判据 | 判法 |
| --- | --- | --- | --- |
| 构建 / 发布(`npm test`、`--release`) | 我要产出的东西,是不是从**当前源码**新鲜构建的? | `packaging`、`build-stamp`、`releaseCandidate` | **重新计算**(比对 dist/产物与源码树) |
| 安装(`deploy/install.sh`) | **别人已经产出的**这个东西,能不能装到这台机器上? | 产物自证(`gitRev`/`releaseCandidate`) | **读它自己说的**(不重算) |
实现:`run-all.mjs` 用 `AGENTMAIL_CRITERIA_PHASE=install` 切相位;每条判据在 `SIDES` 里登记它读的
哪一侧,`install` 相位里出现 `SOURCE` 侧的判据**直接红**;被跳过的判据会在汇总里**点名打印**
(不静默丢)。
三个真实实例(同一天撞到,都不是巧合):`check-shared-libs`(从 `153985e` 起在部署路径恒红)、
`packaging`(前端一改就卡死 —— 部署路径不重新打包)、`HOME: unbound variable`
(在**所有门禁跑完之后**才炸:最贵的位置)。