|
|
3be824849d
|
fix(deploy): env-defaults 的顺序错(③ 的探测依赖 ④ 的产物)+ 非 root 的 HOME 陷阱 + 兜底行可见 + 补 root 断言
pi 读了新加的 `deploy/lib/env-defaults.sh`(三个 source 点他都确认对),指出三条,逐一实测后处理:
**1. 顺序错(真错,而且正好落在它自己要消的那类假设上)**
③ 用 `command -v go` 探测,而 `command -v` **走 PATH**;④ 才修 PATH ⇒
**在 ④ 要修的那个环境里(PATH 为空),③ 的探测必然失败**,紧接着 ④ 把 PATH 装上、
后面的步骤**又能**找到 go —— **探测结论与实际可用性相反**。
实测(`env -i`):顺序翻转前 `go` 在 ③ 处找不到、④ 之后 `/usr/bin/go` 就在了。
已把 PATH 那段**挪到最前**,并在文件头写明顺序是**正确性而不是风格**。
(pi 自己也说了严重度:今天对 go 大概率无影响,因为 ① 已兜住 HOME、现代 go 会从 `$HOME/go`
自推缓存 —— 他把它当形状问题提,这个判断我认;一条探测所依赖的东西正是同文件后面要修的东西,
这正是本文件存在的理由。)
**2. `HOME=/root` 在非 root 调用者手里会把环境问题变成代码问题**
`redeploy-gateway.sh` 原先没有 EUID 断言(只有 `install.sh` 有),于是"非 root + 空 HOME"
会拿到 `HOME=/root`,接着 `go build`/`npm ci` 往 `/root/go`、`/root/.npm` 写 ⇒ **EACCES**,
而那串报错看起来是代码/工程问题 —— 正是本文件要消的东西。
已按身份分叉 + **验证可写**(判据落在"能不能写",不落在"路径长得像不像"),
兜底落到 `${TMPDIR:-/tmp}/agentmail-home-$(id -u)`;连一处可写的都找不到 ⇒ exit 2 + 人话。
★ **顺着他的思路又实测出第二个口子**:`HOME` **已给**但不可写时,上面只判"未设"就放行 ——
后果与空 HOME 完全相同,只是入参不同(`sudo -E`、从 root shell 继承、容器挂错)。
`mkdir -p` 对**已存在的不可写目录会返回成功**,所以必须单独判 `-w`。
实测 `setpriv --reuid=65534 env -i HOME=/root` ⇒ `touch $HOME/probe` 被拒。
已覆盖"已给但不可写",并**先说清再改判**(`[WARN] 调用者给的 HOME=… 不可写;改判到 …`),
不静默换目录 —— 静默换会让"东西写到哪去了"变成谜。
五种场景实测(全空 / 环境齐 / 非 root+空 / 非 root+不可写 HOME / root+可写):全部符合预期。
**3. "某条脚本忘了 source"没有信号** ⇒ 采纳
`AGENTMAIL_ENV_DEFAULTS` 只是被 export、值不进正常输出 ⇒ 谁把 `source` 删了,
输出与"环境本来就齐"**完全同形**(又是"看起来在兜、其实没兜")。
新增 `agentmail_env_report()`,三个脚本各自打一行(兜了哪些 / "(无 —— 调用者已提供全部)";
忘了 source 就没有这一行)。实测三个脚本在空环境下各自都打出来了 ——
这也把验收从"一条脚本"变成"三条各自可读"。
**4. 顺带补 `redeploy-gateway.sh` 的 root 断言**
它要往 `$PREFIX`(默认 /opt/agentmail)写,非 root 必然失败在写权限上,
而报错来自 `install`/`cp`、看起来像工程问题。用退出码 **2**(环境/权限),口径与 env-defaults 一致。
实测非 root 下:`[FAIL] 环境不足:本脚本要写 /opt/agentmail,需要 root。` 退出码 2。
验证:install.sh --check 空环境 exit 0、正常环境 exit 0;npm test exit 0;
prune 自检 22/22;drift 自检 35/0;check-shared-libs exit 0;四个脚本 bash -n 通过。
|
2026-09-14 21:01:19 +08:00 |
|
|
|
743e397916
|
fix(prune)!: 构建暂存那段清理**一直是空转的**(ls -1t 对目录打 路径: 头)+ del() 失败分支补齐覆盖
## 那个真 bug:`ls -1t <多个目录>` 不打裸名字
追 pi 的 shim 建议时撞出来的,与 shim 无关 —— 是我为了给它造样本才发现的:
ls -1t /tmp/agentmail-gateway-build-* # 这些是**目录**(mktemp -d 造的)
/tmp/…-20260101-000000:
agentmail-gateway
/tmp/…-20260105-000000:
agentmail-gateway
`ls -1t` 收到**多个目录参数**时会列出**每个目录的内容**并打 `路径:` 头 ——
于是 `mapfile` 拿到的全是 `…000000:` / `agentmail-gateway` 这类行,都不是文件名
⇒ `basename | grep -oE '[0-9]{8}-[0-9]{6}'` 取不到时间戳 ⇒ 每条都判
"文件名无时间戳,判定不了" ⇒ **一个都不删**。
也就是说本段注释里写的那个问题("每次部署留下一个 24MB")**从来没被清理过**。
修法:`ls -1dt`(`-d` 让目录自身作为条目,不打头)。
**为什么一直没人发现**:自检夹具用 `: >` 造的是**普通文件**,而生产是**有内容的目录**。
夹具形状与生产不一致 ⇒ 夹具自己认了错形状,而判据 197 行又只按**文件名**判
"窗口内的还在、窗口外的不在",于是判据也认了。这是 docs 第 13 条那一族。
→ 夹具已改成真目录 + 里面放 `agentmail-gateway`;**改完立刻变红**("干净样本:/tmp 构建暂存
只留窗口内那 1 份"失败),证明夹具现在真的有分辨力,然后加 `-d` 转绿。
顺带确认:其余三处 `ls -1t`(`agentmail-gateway.bak-*`、`pre-deploy-*.db`、`pre-prune-*.db`)
glob 到的是**文件**,不受影响;插件快照那处(第 294 行)本来就已经写了 `-1dt`。
生产现场实测:`/tmp` 下确实还躺着 1 份 24MB 暂存没被收掉。
## `del()` 的失败分支:采纳 pi 的"让 rm 自己失败"
他指出的第三条路(我原先只想到 immutable 与注入点)是对的:本机以 root 跑、权限拦不住;
`unshare -r` 被拒(`/proc/self/uid_map: Permission denied`);tmpfs 无 `chattr +i`。
**改机器的权限**不如**让 rm 失败**。
实现上走了 `RM="${RM:-rm}"` 而不是 PATH shim,理由:`in_use` 会把命令行里含该路径的进程
判成"在用",而自检必须把路径写在命令行上 —— 实测评 PATH shim 时确实被 `in_use` 挡掉、
`del()` 根本没被调用(那次"测试通过"是假的)。`$RM` 默认就是 `rm`,生产行为逐字不变。
自检新增 4 条(并通过变异确认有区分力:去掉失败判定 ⇒ 强断言变红):
退出码 2、必须打出 `[FAIL] 删不掉`、不许出现收尾汇总、那条路径必须还在。
★ 变异还暴露出一条**弱断言**:单看"退出码 = 2"在变异后**照样通过**(脚本别处也有退 2 的路径)
—— 已在注释里注明它弱、区分力来自另两条,没有让它冒充证据。
★ 顺手修掉一处自指的措辞:我原先在报错里抄了收尾汇总的原话("已删除 N 项"),
于是 `grep -c '已删除'` 命中**这句报错自己** ⇒ "有没有虚报成功"这个检查把自己的措辞
当成了证据。改写成不含该字面量的说法(与 `grep -c 用例名` 是同一族:判据锚在元文本上)。
## pi 的另两点
· `diffSummary` 带 `ctx` 时**会读文件**(判 `scripts.test` 要读两侧原文),不带是纯内存比较
—— 已写进函数头,免得以后有人当纯函数用而在大树上意外吃到 I/O。
· "自检样本不独立"的三种形态(位置选择器 / 共享夹具状态泄漏 / 探针无分辨力)
**合成 docs 第 13 条**(修法同一个:显式命名 + 显式复位),并把上面"夹具形状必须与生产
一致"作为配套一条写进同一条 —— 今天的真 bug 正是它。
验证:prune 自检 22/22、干跑 exit 0;npm test exit 0;drift 自检 35/0;check-shared-libs exit 0。
|
2026-09-14 20:53:54 +08:00 |
|
|
|
7eec311756
|
fix(deploy): C 扩到全文件(133/0 闭合)+ ① 加 realpath 判据 + ②b 明说"恒等" + 环境自足收成一处
pi 这一封四个实质点,逐个实测后处理:
**C. 口径扩到全部文件**(他给的是算术,不是口味,我认):
原先按后缀取(`.conf/.service/.timer/.bak*`),我说的"零违规就不扩"是把口味当论证。
他把成本量化了:差集极小 ⇒ 多读几次文件(几十 KB),而收益是那个 `0` 从
**"有范围的 0"**(只对我划的圈成立)变成**"闭合的 0"**(对整棵 /etc/systemd 成立)。
他还补了一句我没想到的:这条判据只报**内容里含仓库路径**的文件,
所以含仓库串的 `.dpkg-old`/`~`/无后缀文件**恰恰都是真信号**(过期的旧真相),
不是噪声 —— 我先前"二进制会变成噪声"的担心本来就不成立。
**验收实测:比了 133 个文件(全部,不筛后缀)、命中 0。**
另按他要求把"零违规"这个前提写进注释,并说明"红/WARN 拆分"为什么推迟
(零违规时拆分是重构不是修 bug;出现第一个非白名单命中时再决定分档)。
**反例 1(②b 对 pi 是跑不到的分支)**:确认。pi 的依赖是全局包软链
(`-> /usr/lib/node_modules/@earendil-works/pi-coding-agent`),`cp -a` 保留软链
⇒ 两侧 realpath 到**同一个 inode**(实测 `statSync(a).ino === statSync(b).ino`)
⇒ 版本集合按构造相等 ⇒ **②b 对 pi 永远不会红**。这正是本文件自己列过的第三种形态
(断言在、区分力不在),比"没写判据"更坏因为它看起来是绿的。
已改:两侧 realpath 相同时**明说"恒等、区分力为零"**并指出它真正覆盖谁(有 vendored 树的宿主),
不再报"版本集合一致"这种让人误以为验过的措辞。自检加了这一条。
**反例 2(① 的 realpath 盲区)**:确认,形状真实且三条判据全都看不见 ——
①只 grep 内容(仓库那份 unit 文本里没有仓库字面量)、②比内容(live 就是 repo 那个 inode,
必然"一致")、④只查固定名单。已加 realpath 判据:被检文件 realpath 落在仓库里 ⇒ 红,
与内容无关。自检加**正反两面**(内容干净但指向仓库 ⇒ 红;指向仓库外 ⇒ 不许红,
否则这条判据恒红)。实测:本机 `/etc/systemd/system` 下 0 条指向仓库的软链
(即这个 0 现在才是闭合的)。
**反例 3(环境假设第四次 ⇒ 建议收成一处)**:采纳。四次的形态一模一样
(HOME ⇒ 又一次 HOME ⇒ TMPDIR ⇒ GOMODCACHE/GOPATH),每次"再加一个预检"只挡已知那一个。
新增 `deploy/lib/env-defaults.sh`:一处给全 HOME/TMPDIR/GOMODCACHE(GOPATH)/PATH,
只设**未设**的变量,注释里写明四次历史与"否则第五次一定会来";
三个部署脚本开头 source 它;**删掉**我上一轮加的那个分散 go 预检。
实测:在 `HOME`/`TMPDIR`/`GOPATH`/`GOMODCACHE` **全空**的环境里
`bash deploy/install.sh --check` **exit 0**(go vet + go test 自己站起来),
兜住的变量会在 `AGENTMAIL_ENV_DEFAULTS` 里说明。
docs 补两条纪律:13「锚点必须一一对应 —— 连'文件名'都会骗你」(E 的探针教训)、
14「退出码也有量纲」(--self-check 的退出码不是自检的结论)。
验证:npm test exit 0;check-shared-libs exit 0;drift --self-check **35/0**;
prune 干跑 exit 0;install.sh --check 空环境 exit 0。
|
2026-09-14 20:42:54 +08:00 |
|
|
|
d47035aaff
|
docs(dev-tooling): 补三条 —— 布局改动的回滚账、PIPESTATUS 与 pipefail 是判据的一部分、判据输出要自证比完全部对象
第 10 条是 pi 提的:把 lib/user-question.js 搬到 test/lib/ 的收益是'快照更干净',
但**回滚它会让快照立刻多出一处运行时漂移** —— 这类改动的账里没有'回滚'这一行。
第 11 条把管道纪律写清取法,并点明四个 deploy 脚本的 set -o pipefail 不是风格而是判据。
第 12 条来自实测:show_diff 的 cmp|head 在 set -e 下让脚本当场中止,
只报第一个对象、退出码却恰好还是 1 —— 判定对、证据被截断。
|
2026-09-14 20:35:10 +08:00 |
|
|
|
42f01c7478
|
fix: 三处"判定对、但信号假"——豁免的自检覆盖、rm 失败仍报"已删除"、show_diff 截断证据
pi 通读后逐处指出,三条都实测复现:
**1. `jsonTestOnlyChange`/`testOnlyDrift`/`runtimeOnly` 没有任何自检碰过。**
他的质疑成立:我上一封说"配了六个样本",那六个样本是**开发时的内联脚本、没进文件**
(他读了全文,找不到——我核了,确实没有)。而这段逻辑是文件里**唯一一处"把红变成绿"
的代码**,也是唯一没有判据的代码,失效方向恰好是"比恒黄更坏"那个(假绿)。
已补 **4 条走真路径的样本**(真临时树 + `diffSummary(d,{repoDir,snapDir})`):
★只差 `scripts.test` ⇒ 不算运行时漂移且必须说出豁免了哪条键;
★`scripts.start` 变了 ⇒ 必须算运行时;★解析不了 ⇒ 不许豁免;★不传 ctx ⇒ 不豁免。
★ 写样本时被一对**同义不同数**的字段绊住:断言 `runtimeDrift === 0` 却得到 1 ——
`diffSummary` 报的是**原始**检测数,而 `checkHost` 自己又减了一遍豁免。
判据自己产出两个矛盾口径,与"注释里两组矛盾的写点计数"同族 ⇒ **统一**:
`runtimeDrift` = 减掉豁免后的结论,被豁免的只出现在 `testOnly` 里;
`checkHost` 删掉自算的 `runtimeOnly`。不传 ctx 时行为与旧版逐字相同。
**2. `prune-deploy-artifacts.sh` 的 `del()`:`rm` 的结果没人看。**
脚本是 `set -uo pipefail`(**无 -e**)⇒ `rm -rf` 失败(权限/只读挂载/immutable)后
照样打"删除 …(NMB)"、收尾汇总"**已删除 N 项,释放约 X MB**"
——**一次失败之后仍产出成功措辞**(与 `redeploy-plugin.sh` 里"被拒还打 [ OK ]"同族)。
已改:判失败即报错 `exit 2`;`removed`/`freed_kb` **只在成功后累加**;并加抽验
(`rm` 报成功但路径还在 ⇒ 报错),不信单一退出码。
⚠️ **这一条的失败路径我没能实测**:本机以 root 跑,权限拦不住 `rm`;
`unshare -r` 建只读挂载被拒(`/proc/self/uid_map: Permission denied`);
tmpfs 无 `chattr +i`。要覆盖得在带 CAP_LINUX_IMMUTABLE 的环境用 immutable 文件,
或给 `del()` 加 `RM` 注入点。**我没有把"改过"说成"验过"。**
**3. `check-shared-libs.sh` 的 `show_diff` 被 `set -e` 当场中止。**
`cmp` 有差异返回 1 ⇒ `pipefail` 让函数返回 1 ⇒ 独立调用触发 `set -e` ⇒ **脚本当场死**。
实测(脚本级,fixture 树里造两处分叉:pi 与 zcode):
旧版:报告 1 处分叉、收尾汇总 0 次
新版:报告 2 处分叉、收尾汇总 0 次(收尾那句本来只在成功时打,见下)
两者退出码都是 1(判定一直是对的)—— 所以**光量退出码看不见这个 bug**,
正是 pi 说的"判定对、证据被截断"。而且旧版连 `fail=1` 都执行不到:
退出码 1 是 `set -e` 给的。加 `|| true` 后遍历跑完。
|
2026-09-14 20:35:01 +08:00 |
|
|
|
7ec45b8d45
|
fix(prune): 备份集"同生共死"在用跳过时失效 —— 原来只跳一个文件,兄弟照删
pi 评审 2026-09-14 指出(实测确认):`in_use "$f"` 命中就 `continue`,
只跳过**那一个**文件,同一 `<ts>` 的其余成员照删 ⇒ 备份集被拆开,
与这段自己的注释("拆开留没有意义")相反。
**在删除那一支(`i >= KEEP_DB_BACKUPS`)后果比 pi 说的更难看**:
正在被使用的那个 `in_use` 留了下来,而同一时间戳的 `-wal`/`-shm` 照删 ——
**"留下"的那份要配上被删掉的兄弟才是完整的一套**,所以留下的是**残的**。
(保留那一支的后果轻一些:只拆集,不丢在用文件。)
改法:先算一次整集的在用状态(`set_in_use`),整集一起跳过并报
"★跳过整集(有成员正在被使用)<ts>",`skipped` 按整集成员数计。
干跑实测照常(本机当前 0 项待删、0 项跳过);`bash -n` 通过。
|
2026-09-14 20:28:50 +08:00 |
|
|
|
56b9028579
|
fix(deploy): 三个工具统一 AGENTMAIL_PREFIX + go 环境预检(别把环境问题报成代码问题)
pi 的 G 项,两处都实测确认:
1. **`PREFIX` 三套写法**:`redeploy-gateway.sh` 与 `reset-demo.sh` 都是
`${AGENTMAIL_PREFIX:-/opt/agentmail}`,只有 `install.sh` 写死 `/opt/agentmail`
⇒ 谁设了那个变量,install 装到 A、redeploy 看 B、drift-check 看 C。
实际不止一处:`install.sh` 里另有 3 处**绕过自己的 `$PREFIX`** 硬编码
(`agent-config`、`pi-config`、`bin/` 的 install),`redeploy-gateway.sh` 里也有 2 处
(它自己有 `$PREFIX` 却绕过)。全部收回 `$PREFIX`;`install.sh` 改为尊重
`${AGENTMAIL_PREFIX:-/opt/agentmail}`。`install.sh` 里 `/opt/agentmail` 现在只剩
注释与默认值两处。
⚠️ `check-deploy-drift.mjs` 仍是硬编码——**没有改**:它是独立工具、不 source 任何脚本,
要它认前缀得先定义"配置从哪来",那是设计题不是一行改动。(已在此留档。)
2. **`--check` 相位新增的 go 门禁会产出误导信号**:我实测(沙箱里没有 `HOME`/`GOPATH`)
它报 `go: module cache not found: neither GOMODCACHE nor GOPATH is set`,
然后被我说成"go vet / go test 不过 —— **先修好再安装**" ——
**把环境问题报成代码问题**,而那正好诱导人去 sudo 建目录
(GOPATH 会落到 `${GOPATH:-$HOME/go}/pkg/mod`)。
加一句预检:`go env GOMODCACHE` 为空 ⇒ 明说"环境不足"并 **exit 2**(环境约定),
不再冒充代码缺陷。实测:沙箱里现在报"环境不足…药方:带上有 HOME 的环境重跑"、退出码 2;
带上 `HOME=/root` 后 `go vet` + `go test` **通过**(退出码 0)。
|
2026-09-14 20:27:26 +08:00 |
|
|
|
44fdfb3ed7
|
fix(deploy-drift): 判据 ① 的覆盖面写进 note(0 必须带上可证伪范围)+ 记录 C 的取舍 + docs 补 4 条纪律
pi 的 C 项(`.bak` 那次修法"把圈往外挪了一格,还是圈")我**实测后决定不改**,理由留档:
- 这一格现在**零违规**:`/etc/systemd/system` 133 个文件里**没有任何一个**含仓库路径,
包括现存的 4 个 `.bak`(`dsh-lan.service.bak-20260903-081410`、
`pi-bridge.service.bak-13010-20260814`、`pi-bridge.service.bak-20260814`、
`pi-web-sessiond.service.bak-20260814`)—— 全干净;
- pi 提到的 `zcode.service.bak-20260912-145744` **他读时已经 ENOENT**(他自己写了),
也就是说我引他那句话时依据的文件**已经不存在**;
- 扩到"每个普通文件都读"在当前只会引入噪声(二进制/dpkg 数据库类),换不到真信号。
⇒ 改为**把覆盖面写进 note**("比了 129 个 … 文件")——
`0` 只有在"它能被证伪的范围"写明之后才是结论,这正是这条判据当初缺的那句话。
等真出现一个非白名单后缀的违规再改,那时我们就有实例了。
docs/DEV-TOOLING.md 补 4 条纪律(编号 7-10,原第 7 条顺延):
7 「注入点会把该抓的 bug 藏起来」(含位置选择器 `bad[0]` 的同类);
8 「看起来在比、其实没比」要当一条自查(本轮出现三次:路径错/空目录/符号链接),
且**绿的时候也要留下覆盖范围的证据**;
9 **变异之前先提交**(我未提交就变异 + `git checkout` 还原,把自己的改动冲掉);
10 **注释里的数字无法被判据守住**(同文件三处说法三个数、式子加起来还是错的)。
自检全过;实跑 ① 报"比了 129 个文件"。
|
2026-09-14 20:25:24 +08:00 |
|
|
|
a0ba10341c
|
docs(deploy-drift): 删掉两组互相矛盾的"写点计数",改写成口径 + 理由
pi 逐处数出来的(我复核确认):同一份文件里
- `describeEnvError` 头注释写"写点有**五处**";
- `selfCheck` 头注释写"本函数共 **6 处写** = mkdtempSync×2 + writeFileSync×2 + 三处裸写"
—— **这条自己列的式子 2+2+3 加起来是 7**。
三处说法三个数,**没有一个等于实际站点数**(实际是 mkdtempSync×2 + mkdirSync×2 +
writeFileSync×6 = **10 处**;函数体后来又长了,数字只会更旧)。
**现在一个数字都不写。** 理由与本仓库既有纪律同源("不要为此引入手抄的期望用例数
常量 —— 手抄常量会过期"):注释里的数字**无法被判据守住**,改代码时没人会回来改它们,
而"两组数字互相矛盾"比"没有数字"更糟 —— 它让读者以为有人数过。
实例就在这份文件里:依赖树夹具(第 1009 行附近)后来又加了 3 处写,谁也没回头改注释。
要判"覆盖是否完整"只能靠**机制**:整段 try/catch 保证不论哪一处抛 ENOSPC 都被翻译成
人话 —— 覆盖范围不取决于入口、也不取决于处数。这一点写进注释了。
|
2026-09-14 20:24:49 +08:00 |
|
|
|
ebb1040559
|
fix(install): go vet / go test 提到 --check 相位 —— 原措辞"所有会红的门禁都跑过了"与自己下一段矛盾
pi 读出来的(实测确认):干跑末尾写"**所有会红的门禁都跑过了**(前端 typecheck/test/build、
共用模块同源、各插件测试、插件构建)",而紧接着那段就把 `go vet + go test` 列进
"下面这些步骤干跑**没有执行**" —— **那两条正是会红的门禁**。
两段话自相矛盾,而读者只会读那句加粗的结论。
采用 pi 倾向的方案①(把它提进 `--check` 相位)而不是只改措辞,理由:
这两道门**最容易在别人的机器上红**(Go 版本、模块缓存、平台),
而"第一个拿到 root 的人第一次跑门禁"正是 `--check` 要解决的场景。
它们不写系统目录(只写 go 缓存),放进这个相位没有副作用。
顺带把措辞改准:结论句改成"**除了"构建 Gateway 二进制"之外**,所有会红的门禁都跑过了"。
实测(本机,`HOME=/root GOPATH=/root/go`):`go vet ./...` 与 `go test ./...` 都是
**真退出码 0**(gateway 各包全绿)⇒ 新增的这一相位不会造成"一跑就红"。
⚠️ 我的沙箱里 `$HOME` 为空 ⇒ 直接跑 go 会报
`go: module cache not found: neither GOMODCACHE nor GOPATH is set` ——
**这是环境问题不是代码问题**(`/root/go/pkg/mod` 存在,正式走 root 不受影响)。
|
2026-09-14 20:24:09 +08:00 |
|
|
|
af42a08fbf
|
fix(deploy): 故障通知脚本的安装原来在 if 分支里(--skip-web 就不装)+ 判据 ③ 只验"在不在"
pi 读代码抓到的两处,实测确认:
1. `redeploy-gateway.sh` 的 `install … service-failure-notify.mjs` 夹在
`if [ "$SYNC_WEB" = 1 ] && [ -d …/dist/assets ]` 里(在 `[ OK ] 前端产物比源码新`
之后、`else` 之前)⇒ **`--skip-web`、或 `dist/assets` 不存在时运行时脚本根本不装**。
它跟前端产物没有任何关系,只是恰好被写进了同一支。后果:改了仓库里的通知脚本、
用 `--skip-web` 部署 ⇒ **生产还是旧的那份**。
已移出 if/else(并加 `install -d` / 安装失败的判失败 + exit 2)。
2. 判据 ③ 原先只判 `<isFile> && 有执行位`,不判**是哪一份** ⇒ 上一条的后果全绿。
现在比内容(与仓库 `deploy/service-failure-notify.mjs` 逐字节),
读不到仓库那份时**报"比不了"并判红**,不许当成"一致"。
—— 判据 ② 对 unit 本来就是比内容的,③ 该同形。
自检新增一条:★通知脚本内容与仓库不一致 ⇒ 必须红。
(探针第一版按**文件名**判两侧,而两条路径的文件名相同
(`deploy/service-failure-notify.mjs` vs `/opt/agentmail/bin/service-failure-notify.mjs`)
⇒ 两边返回同一个串、探针自己没分辨力。改成按哪一侧区分。)
现状实测:仓库与装机两份 sha256 相同(496 行),所以这是**盲区而非事故** ——
pi 的措辞准确。自检 28 条全过;实跑 ③ 报"内容与仓库一致"。
|
2026-09-14 20:23:26 +08:00 |
|
|
|
3b5c51c41e
|
fix(deploy-drift): 补上 EXCLUDE_DIRS 挖掉的依赖树洞(②b)+ 写明排除口径表
pi 逐条对过 `EXCLUDE_*` 与 `redeploy-plugin.sh`,最要紧的是 `node_modules` 那格:
部署脚本**把依赖拷进快照**("依赖必须进快照:仓库外没有 node_modules 可借"),
而本文件整份跳过它 ⇒ **仓库换过依赖、快照还是旧的,判据报"逐字节一致"**。
这正是 `EXCLUDE_DIRS` 上面那句注释自己预言的假绿。
新增判据 ②b:读锁文件里的**版本集合**做签名(一个文件、24ms、实测 165 个包)。
选它而不是逐文件比:抓的是"依赖树漂移"这个真实风险,又不会被 `node_modules`
里的缓存噪声乱报(真的逐文件比 `node_modules` 不现实)。
- 一致 ⇒ 绿并说出比了几个包;版本变了 ⇒ 红;**一侧没有 ⇒ 也红**(不许当"未比"放过)。
过程中连踩两个同形状的坑,都记在代码里:
1. 依赖是**符号链接**(`@earendil-works/pi-coding-agent -> /usr/lib/node_modules/…`),
第一版只挑 `isDirectory()` ⇒ 空手而归 ⇒ 恒报"两边都没有依赖树,未比"
—— **又是"看起来在比、其实没比"**;
2. 我把 `repoUnits`/`liveUnits`(systemd 目录)传给了找锁文件的函数,
于是永远找不到 ⇒ 同一个形状再来一次。
⇒ 教训:一条判据如果**只能靠真文件系统喂**,它就没法被自检;
`findDepLock` 因此改成只走注入面,自检样本把它真正走一遍。
另外:
- 排除口径写成**表**(对齐/未对齐各自说明),不再只是"逐条对齐"一句话;
- `dist.old` 加进 `EXCLUDE_SUFFIX`(部署脚本会 `rm -rf` 它,仓库里若有会造成**永久假红**);
- `coverage` 保留排除但写明"仓库里当前不存在,无实际影响,保留是为了不假装对齐";
- `.cache`/`*.log`/`.DS_Store` 三处**方向相反**(本文件比脚本更宽)写明为已知取舍。
自检新增三条:★依赖树一致 ⇒ 绿且说出包数、★版本变了 ⇒ 必须红、★一侧没有 ⇒ 必须红。
自检 25 条全过;实跑 ②b 报"165 个包,版本集合一致"。
|
2026-09-14 20:22:12 +08:00 |
|
|
|
5e38914cac
|
fix(deploy-drift): 判据 ② 是假绿 —— 它比的目录不存在(一个文件都没比过,却报"一致")
pi 逐行读出来的(我实测确认):默认路径写成
`new URL('../systemd', import.meta.url).pathname`,而 `import.meta.url` 在 `deploy/` 下
⇒ 解析成 `/home/program/agentmail/systemd`(**ENOENT**,真身在 `deploy/systemd/`)。
`readdir` 抛的 ENOENT 被 `catch { return; }` 静默吞掉 ⇒ `drift` 恒空
⇒ **一个文件都没比过,却报"已安装单元与 deploy/systemd/ 一致",而且它参与退出码。**
这是第四种形态的标本(**边界没说出口 ⇒ 报了个自己都不知道是假的 0**),
而且**自检接不住它**:`layoutSelfCheck()` 每个样本都显式注入 `repoUnits:'/repo/systemd'`
⇒ 真实默认值从没被任何样本走过 ⇒ 写错了自检也 100% 绿。
**注入点把该抓的 bug 藏起来了** —— 同族里这是最难看的一层。
四处一起修:
1. 路径改成 `join(HERE, 'systemd')`,并**导出** `DEFAULT_REPO_UNITS` 让自检能断言默认值本身;
2. `catch` 不再静默:目录读不到 ⇒ **判红**("比不了"不许伪装成"一致");
**空目录也判红** —— 真实目录有 22 个文件,不可能是空的(这条是被自检逼出来的:
注入的 readdir 对未知目录返回 `[]`,于是"不存在"能伪装成"空",我第一版修法又栽成恒绿);
3. **反向也判**:机器上有、仓库里没有的(原先永远不报 —— 那正是"仓库里的是旧的、
机器上的是新的"的另一半)。**口径必须收窄**:`/etc/systemd/system` 下绝大多数是
系统自带 unit 与 enable 出的软链(实测 111 个),全报等于没报 ⇒ 只报本仓库自己那套、
跳过软链 ⇒ 现在 0 个;
4. 绿的时候 note 带上覆盖范围("比了 22 个文件,全部一致(目录:…)")——
空 note 无法区分"一致"和"没比过",而那正是这条判据原先的样子。
自检新增三条判据:★默认单元目录存在、★默认单元目录不是仓库根下那个不存在的 systemd/、
★单元目录读不到 ⇒ 必须判红。另把 `bad[0]`/`badBak[0]` 这类**位置选择器**改成
按名字取(`byName`)—— 位置选择器是另一种"注入点藏 bug":插一条新检查就会改变语义。
**变异确认**(这次先提交、再变异):把默认路径改回 `../systemd` ⇒ 判据 ② **红**,
note 精确报 `仓库单元目录读不到:/home/program/agentmail/systemd(ENOENT)`。
⚠️ 上一次做这个变异时我在**未提交**状态下用 `git checkout HEAD --` 还原,
把自己的改动一起冲掉了 —— 这正是我自己写进 DEV-TOOLING 的那条纪律,我又踩了一次。
这次先 commit 再变异。
验证:`--self-check` 全过;实跑 ② 由"空 note 恒绿"变成"比了 22 个文件,全部一致"。
|
2026-09-14 20:19:30 +08:00 |
|
|
|
a1ed4ea1a5
|
docs(dev-tooling): 变异纪律补两条 —— 判据退出码不许经管道取值(tail 改写了量纲)+ 新判据上线先找相反的不变量
第 6 条是我自己刚踩的:`bash check-shared-libs.sh 2>&1 | tail -25; echo $?` 拿到的是
tail 的退出码,于是那条脚本的红(真值 1)被我报成 0 —— 只因为失败信息走 stderr 没进管道
才露出来。第 7 条是这轮最贵的一课:新的'不可达就搬走'与既有的'共用模块逐字节同源'
方向相反,我只看⻅了自己那条,差点让下次部署静默删掉 dsh 桥的生产模块。
|
2026-09-14 20:14:47 +08:00 |
|
|
|
fbdfdcc81d
|
fix(deploy): staging 拷贝失败必须当场 exit 2(这次实测打出了假的 [ OK ])+ 登记同类未堵点
一次在沙箱里跑的 `bash deploy/redeploy-plugin.sh pi`(本意是修生产缺文件)被权限拒绝,
结果暴露出脚本自己的一个假绿:
mkdir: cannot create directory '…/.20260914-201204.staging': Permission denied
cp: cannot create directory '…' : Permission denied
[ OK ] 已拷入 node_modules(生产不借用仓库的依赖) ← 一个字节都没拷
[FAIL] staging 里没有入口 src/index.mjs
**一段输出里两个互相矛盾的信号,而且 OK 在前** —— 这正是本仓库反复记录的那个病
("从失败里产出一份看着正常的报告"),这次是部署脚本自己得上了。
改动:`mkdir`/`cp` 当场判失败并按约定 exit 2(环境/权限问题,不是检查问题);
`node_modules` 那一份**拷失败就不再打 OK**(依赖进不了 staging 会在重启时才炸,
而那时旧版已经被换掉了)。实测同一路径:现在**立刻** exit 2、零个假 `[ OK ]`。
成功路径未受影响(脚本其余部分与 `[ OK ]`/`[FAIL]` 约定不变)。
`docs/DEBTS.json` 登记同类未堵点 `redeploy-script-unguarded-steps`:
`deploy/redeploy-gateway.sh:84` 的 `run "cp -r …"` 无守卫,而该脚本只有
`set -uo pipefail`(**无 `-e`**)、`run()` 内部 `eval` 的失败既不中断也不被调用点接收
⇒ 前端产物没拷进去也继续往下走。**没有当场改那个文件**:另一条会话正在改它,
改它等于把冲突塞给一条我看不见的路径(`install.sh` 已有 `set -euo pipefail`,无需处理)。
登记的 `due` 就是"下次改任一 deploy 脚本时"。
验证:`npm test` 463/463、`check-shared-libs.sh` exit 0、`--self-check` 全过、`bash -n` OK。
|
2026-09-14 20:13:57 +08:00 |
|
|
|
d616582e96
|
fix: 回滚 user-question.js 那一搬(它把 check-shared-libs 打红两处),并把 drift 的非运行时差异摘出来
pi 逐处对文件后指出:我按"本平台不可达 ⇒ 搬去 test/lib/"把 `lib/user-question.js`
搬走,打红了 `deploy/check-shared-libs.sh` 两处(实测确认,脚本真退出码 1):
共用模块缺失:plugins/pi-mail-bridge/lib/user-question.js
共用测试已分叉:test/user-question.test.mjs(opencode vs pi)
根因不是取舍而是口径:**`lib/` 上挂着两条方向相反的不变量** ——
① 共用模块四方逐字节同源(`check-shared-libs.sh`,连相对路径一起钉);
② 本平台生产可达(我新加的规则)。而 `user-question.js` **是 dsh 桥的生产代码**
(`plugins/dsh-mail-bridge/src/index.ts` 引用它)⇒ 两条必然冲突。
**`lib/` 首先是四桥共用命名空间,其次才是"本平台可达"**;可达性只能当**报告**,
不能当搬家判据。教训的形状:**一条新判据上线时,先找它可能与哪些既有不变量冲突** ——
我只看⻅了自己那条。
改动:
- `user-question.js` 与它的测试回到 `lib/`、`test/`(路径也与 dsh 侧一致),
两边逐字节相同已复验;`check-shared-libs.sh` 退出码 0。
- `reach.mjs` 增加 `sharedLibNames()`:直接从 `check-shared-libs.sh` 的 `ALL_LIBS`
读共用清单做豁免(不手抄常量),并把"进快照但本平台不可达"降级为**报告**。
- `layout-boundaries.test.mjs` 增加回归判据:共用模块必须留在 `lib/`、
测试相对路径与 dsh 一致、两侧逐字节相同。
- 删掉 `reach.mjs` / `docs/DEV-TOOLING.md` 里那句**无据的机制说明**
("user-question 走前缀动态 import"):`localRefs` 的三条正则只认引号字面量,
对模板字面量形状是**盲的** ⇒ 那句若为真,搬走的就是生产代码而两条判据都会绿。
pi 读了 `src/` 下九个文件都找不到引用,我也确认是记忆偏差;理由改用 `addressing.js`
(传递可达、`src` 直接引用数为 0)—— 它已足够证明"直接引用数不是可达性"。
顺带按 pi 的第二条建议:`deploy/check-deploy-drift.mjs` 判据 ① 把
**非运行时差异**摘出来(`jsonTestOnlyChange`,只豁免 `scripts.test` 一类字段,
只对"两边都在、仅内容不同"的文件生效)。理由:一条**永远黄、没人打算为它动手**的判据
唯一的下场是被学会忽略,那时真正的运行时漂移会被一起忽略。
⚠️ 摘的条件很窄 —— **把运行时差异误判成非运行时比恒黄更坏(那是假绿)**,
所以 `main`/`start`/`dependencies` 变了、或解析不了,一律仍算运行时;
纯函数加了六个反/正样本的判据(含三个"必须算运行时"的)。
(该文件同时有另一条会话的改动,未提交、我未触碰;本次只加了我这一段。)
验证:`npm test` 463/463;`check-shared-libs.sh` 退出码 0;`--self-check` 18 条全过。
|
2026-09-14 20:11:58 +08:00 |
|
|
|
2d936893f5
|
fix(deploy): /tmp 占满把部署自己卡死了 —— 收构建暂存 + 构建带 -trimpath + 判据 ⑤
起因是用户让「清理一下」那批带仓库路径的残留。照着清理策略走时撞上更大的事实:
本机 /tmp 是 9.8G 的 tmpfs,**已 100% 满、可用 0 字节**,我自己的 `go build` 当场
ENOSPC 失败 —— 而部署的第一步就是构建。
## 1 谁把 /tmp 占满的(agentmail 自己的那份)
`redeploy-gateway.sh` 把网关构建到 /tmp 再 install 过去(为了原子替换),**用完没人删**:
每次部署留一个 24MB,实测 7 份 / 162MB。加上电子打包的中间物(squashfs-root 283MB、
pkgcheck/deb 291MB)、go-build-agentmail 缓存 172MB、4 个孤儿 go-build 工作目录 50MB
—— agentmail 名下约 960MB。另有别的产品的 /tmp/gocache 4.6G(TrueAgent 的
rebuild-plugins.sh 里 `export GOCACHE=/tmp/gocache`),不是本项目的,没动。
- prune-deploy-artifacts.sh 新增一类「构建暂存」,窗口 KEEP_BUILD_STAGES=1
(正常路径下部署脚本自己会收,留下的只可能是失败那次,正好留现场)。
自检 +1 项、变异验证过(把删除改成永不删 → 恰好那一项红)。
- 本次实际收:删除 8 项 / 释放 164MB(另加手动清 623MB 不可再生的中间物)。
- redeploy-gateway.sh 成功分支上收掉 $STAGE;失败/回滚分支**不删**(要留现场)。
## 2 残留里还藏着两处「旧真相」
- /etc/systemd/system/zcode.service.bak-20260912-145744(+ 同一次改动的
zcode.service.d/10-dbus.conf.bak-…)里躺着 /home/program/agentmail/deploy/
service-failure-notify.mjs —— 就是我上一封报「/etc/systemd 引用仓库 = 0 个文件」时
**判据自己划掉了的那一类**(walk 里 `!name.includes('.bak')`)。已删(在线单元与
deploy/systemd/ 逐字节一致,sha256 核对过),另外 4 个是别的产品的,没动。
- 判据 ① 因此放宽到含 .bak,并补了坏样本(.bak 里引用仓库路径必须判红)。
上一封那句「0 个文件」的边界现在写进判据里了 —— 边界不说出口,就等于报了个假的 0。
## 3 -trimpath:标准目录部署只做了一半
Go 默认把源文件绝对路径编进二进制。对照实验(同一份源码、同一个 go,只差标志):
带 -trimpath 0 处,不带 57 处 —— 而 19:05 那次部署产出的
/opt/agentmail/agentmail-gateway 里就有 57 处 /home/program/agentmail/…。
依赖确实没了,但**源仓库位置还印在产物上**。两个构建点都加上 -trimpath,
并新增判据 ⑤(已安装二进制不得含源码路径,两侧样本都验)。
判据 ⑤ 现在**是红的**,这是存量产物的实情:磁盘上那份要等下一次
redeploy-gateway.sh 才会被换掉。我没替它单独重启网关 —— 会掐断正在跑的会话。
(工作区是多会话共用的,本次只 add 了上面这 4 个 deploy/ 文件。)
|
2026-09-14 20:06:19 +08:00 |
|
|
|
6b1cca0fe4
|
docs(dev-tooling): 加一条提交纪律 —— 多 agent 共用工作区,提交前先看 git status 里有没有别人的文件
这个工作区是多条会话共用的:2026-09-14 20:02~20:04 另一条会话正在改
deploy/install.sh、deploy/prune-deploy-artifacts.sh、deploy/redeploy-gateway.sh
(-trimpath 那组加固),而我同时在同一个仓库里连续提交。
`git add -A` 会把别人没写完、没审过的改动一起做进我的提交,且从 git log 看不出归属。
规矩:显式列出要提交的路径;提交信息里不把别人的改动算成自己的。
(本次三次 pi-bridge 提交都是显式路径,事后用 git show --stat 核对过没有混入。)
|
2026-09-14 20:05:15 +08:00 |
|
|
|
e49f0a8f6a
|
docs(dev-tooling): 写点三处**不同的入口假设**、过宽过窄、因果无关判据、锚点、lib/ 边界 + 一条被自己纠正的观察
按 pi 的两条建议补:
1. "漏过三次"改成**列出三次的位置与各自不同的入口假设**(他指出的问题:原写法
会让读者以为"同一处错了三次"):① `writeSession` 只包 `writeFileSync`;
② `mk()` 的 `mkdtempSync` 在 try 之外;③ 兜底层放在 `main()` 而 `selfCheck()` 是导出的。
三次都概括成一句:**"我以为的入口/哪一行"决定了覆盖范围**。
2. "判据只能发现已知成因"作为实例加进同节,并配对写出**因果无关**的运行期判据
(`test/lib/run-suite.mjs` 数结果行 / 跨文件重名实测不被拦)。
新补四条(本轮新踩出来的):
- **判据过宽和过窄都是坏的**(那条 import 判据自己同时踩过两边);
- **判据的锚点必须与结论一一对应**(重名检查锚结果行 vs 锚"名字出现过",
噪声与效应恰好同阶 ⇒ 4 看起来还能解释);
- **`lib/` 与 `test/lib/` 的边界**(部署脚本是 `cp -a` + `rm -rf test`,
没有 `EXCLUDE_DIRS` 这种变量;★ 别写成"被 src/ 直接 import"—— 直接引用数不是可达性);
- **附一节"被自己的结论半路纠正的观察"**:那条 `result IS NULL` 的挂起请求看着像
授权链缺口,实际是网关按设计处理的(`PermissionWaitWindow = 10min` + 推导出的
"失效时刻")。教训写成"先查完'是否已由另一层按设计处理掉'再下结论"——
这类误判会以"我发现了新问题"的语气传播,比沉默更贵。
|
2026-09-14 20:00:52 +08:00 |
|
|
|
fb85a8728d
|
refactor(pi-bridge): 定下 lib/ 与 test/lib/ 的边界 —— 三个测试侧模块原来会随部署进 /opt
pi 复核后指出:`lib/` 会被 `cp -a "$SRC/." "$STAGING/"` **整份打进生产快照**
(排除清单只有 `test/`、`.git`、`node_modules/.cache`),而我们那三个测试侧模块
(`tmp-space.mjs`、`env-error.mjs`、`session-fixtures.mjs`)都住在 `lib/` 里。
后果不是几 KB,而是"漂移 N 处"这个数字**虚高**、哈希清单变长 ——
而"手抄哈希清单"正是我们刚定性为会过期的东西。
## 规则写成**可判定的**,不写成约定
lib/ = 从生产入口可达的模块(会进快照)
test/lib/ = 只被测试引用的模块(test/ 不部署、也不被注册进套件)
`test/lib/reach.mjs` 真去走一遍 import 闭包(种子 = `src/index.mjs` +
源码里 `new URL('./x.mjs', import.meta.url)` 这类**按路径 fork 的子进程入口**)。
★ 顺带纠正 pi 的规则表述:他写的是"被 `src/` import",但实测 22 个 `lib/` 模块里
有 4 个 `src` **直接**引用数是 0 —— `addressing.js`(被 `lib/inbox-format.js` 引)、
`user-question.js`(走前缀动态 import)、`mail-session-id.js`、`crash-notify.mjs`。
**直接引用数不是可达性**,所以判据真走图而不是 grep。
★ 也纠正他的排除清单名字:脚本里没有 `EXCLUDE_DIRS` 这个变量,就是一条 `rm -rf`。
## 本规则多抓到一个 pi 没发现的
`lib/user-question.js` 也是**只被测试引用**(只有 `test/user-question.test.mjs` 用它)
⇒ 同样会进快照。已一并移到 `test/lib/`。剩下 `mail-session-id.js` 与
`crash-notify.mjs` 是**谁都不用**(生产与测试都不可达)—— 那是遗留物,
不动它们(不属本次范围),但记录在此。
## 新增:因果**无关**的运行期判据
`test/lib/run-suite.mjs`:跑套件并从**同一次运行的 TAP**里数结果行,任何用例名
出现两次就红。为什么需要:静态那条(测试文件不许互相 import)只能发现**已知成因**。
实测跨文件重名**不会被 runner 拦**:两个文件各写一个同名用例 ⇒
`# tests 2 / # pass 2 / # fail 0`,两句 `ok`,零警告。
判据锚在 `^(ok|not ok) <n> - <名字>`(**结果行**),不是"名字出现过"——
pi 先前那条 `grep -c '<名字>'` 给 4 是因为 TAP 里名字既出现在 `# Subtest:` 头、
又出现在结果行,**2 倍效应 + 2 倍噪声恰好同值**,若行种类是 3 就会把两次读成三次。
本脚本自带 `--self-check`(干净样本放行 / 重复样本点名 / 只出现在头里的不算重复 /
名字含 `#` 不被截断)。
`package.json` 的 `test` 改为:
node test/lib/env-preflight.mjs && node test/lib/run-suite.mjs
## 判据全进套件
`test/layout-boundaries.test.mjs`(新):生产可达性不碰 `test/`、`test/lib/` 里不许藏
运行时模块、测试文件不许互相 import、`npm test` 必须接上 run-suite 那一层。
原来放在 `env-guard.test.mjs` 里那条"夹具不在测试文件里"已移到这里(集中边界判据)。
## 变异自检(两条都实测红了才留下)
- 造一个与巨行用例**同名**的探针文件 ⇒ `npm test` exit 1 并点名
`2× ★巨大的 message 行不进内存也不影响解析`;
- 往 `src/gateway.mjs` 加一行指向 `test/lib/run-suite.mjs` 的真 import ⇒
边界判据红并指出 `生产可达了测试代码:test/lib/run-suite.mjs`。
两条探针均已删除、`src/gateway.mjs` 用 `git checkout` 还原并 `cmp` 校验一致。
顺带修一处路径:`env-guard.test.mjs` 里 `PREFLIGHT` 仍指向旧的 `test/env-preflight.mjs`
(前置脚本已移入 `test/lib/`)。
验证:`npm test` **462/462**、结果行重复检查 0 个重名、set 全绿。
|
2026-09-14 20:00:16 +08:00 |
|
|
|
d735e674e1
|
docs(dev-tooling): 判据纪律那节 —— 三种"看起来验过了"的失效形态 + 变异纪律 + 两条实现纪律
pi 复核后提出的收尾:跨文件的教训现在没有容器(实证散在三个文件的头注释里)。
先 `ls docs/` 看过:`docs/DEV-TOOLING.md` 已经有"三条判据,以及它们各自防的那个错"
那一节,主题完全相同 —— 所以**并进去而不是新开一篇**(这是 pi 明确要求的形状)。
写的是**可迁移**的那部分,实证不复述、只给索引:
- 三种失效形态:**钉装饰**(断言落在注释上)、**分支退化**(绑在会变的环境上)、
**跑不到的分支**(断言在、区分力不在 —— 短路是无声的,比退化更狠);
- 变异纪律 5 条:先证明能还原再注入、只对已干净提交的文件做、还原路径不得依赖被测资源、
判据不得用被测物证明自己、别只看过滤后的输出(也**不要**为此引入手抄的期望数常量);
- 两条实现纪律:测试文件之间不许互相 import(会二次注册整套用例,实测 475→459);
写点覆盖范围不得取决于入口(这条在这套代码里被漏过三次);
以及"判据过宽和过窄都是坏的"—— 那条"测试文件互相引用"判据自己同时踩过两边。
- 索引指出:`check-deploy-drift.mjs` 的运行输出**就是**部署状态的判据来源,
**不要**抄成哈希清单往外发(发给 jianf 的那份在两次提交后就作废了)。
|
2026-09-14 19:52:41 +08:00 |
|
|
|
f1059c5a6b
|
fix(pi-bridge): 夹具移出测试文件(import 它会二次注册整套用例)+ 测试侧写点全部兜住 + 判据不再往 /tmp 留垃圾
pi 复核了探针形状(论证闭合),又报了三条,全部实测成立。
## 一、从测试文件 import 助手 ⇒ 那套用例被**再注册一遍**(最实质)
`env-guard.test.mjs` 曾从 `session-scan.test.mjs` 取 `writeSession`。`node --test`
默认每个文件一个子进程,模块导入是进程内的 ⇒ 那个文件的 16 条用例在 env-guard
的进程里**又注册了一遍**。
实测确认:TAP 里巨行用例(单条往临时目录写 ~12 MiB)出现**两次**
(`ok 87` / `ok 353`),测试总数 475。**判据自己在加倍压 /tmp** —— 而 /tmp 正是
这次事件的主角。修完:459 条,巨行用例 1 次。
修法就是 pi 指的形状,也正是 `translateEnvError` 那次的同一手法:
夹具移到**非测试模块** `lib/session-fixtures.mjs`(可被引用,不被注册进套件)。
## 二、测试侧写点还是裸的
`makeRoot()` 的 `mkdtempSync`、以及 `writeSession` 里在 try **之外**的 `mkdirSync`
(ENOSPC 也可能从这里出来)⇒ 绕过前置脚本时抛的仍是原始英文堆栈,
而"绕过前置也要说人话"正是这套兜底存在的理由。现在整段包一层,与 `selfCheck()`
同一形状:**覆盖范围不取决于"我以为的哪一行"**。
## 三、判据往共享 /tmp 里留垃圾
`writeSession(tmpdir(), '--probe--', …)` / `'--probe2--'` 每跑一次就留两个目录、
且永不清理。现在改用 `os.tmpdir()`(纯字符串,不 statfs)当根:那两条的创建都被
假写打断 ⇒ 目录根本不会建出来 ⇒ 既不读也不写真实临时目录。
## 四、一条新判据替代原来的文本接线检查
守**机制**:解析测试文件里的模块引用(静态 `from` / 动态 `import()` / `require()`),
任何指向另一个 `.test.mjs` 的引用都算违规 —— 注释里提到文件名不算(注释不会注册用例)。
这条判据自己踩了两次,都留在注释里:
第 1 版 只匹配静态 from ⇒ 漏掉动态导入;
第 2 版 "文件里出现别的测试文件名" ⇒ 把**注释里的散文引用**也算成违规
(本仓库有 3 处这样的注释,逼人删掉有用的注释),
而且**它被自己注释里的示例字面量扫到**。
**过宽和过窄都是坏的** —— 这正是这一串评审反复出现的同一族错误。
验证:`npm test` **459/459**(少了 16 条重复注册);巨行用例出现 1 次;
`--self-check` 18 条全过;`TMPDIR=/tmp node deploy/check-deploy-drift.mjs --self-check` ⇒ exit 2 + 人话。
|
2026-09-14 19:51:44 +08:00 |
|
|
|
6b7c12d9da
|
fix(pi-bridge): "开关被认"那条判据自己也有假绿 —— 我按真实测量分叉,于是永远走短路分支
pi 评审第二轮指出:上一版"开关真的被认"只在"真实测量不足"那个分支里断言,
机器一恢复健康(/tmp 被清空)这条就退化成"只验 --measure"的弱检查,
而它守的恰恰是"开关别静默失效"。
认下之后我做变异(把开关整个忽略掉、永远用真实测量)验证,**发现比这更糟**:
那条新写的判据**在变异下照样绿**。
原因是我写成了 `realAvail < MIN_FREE_BYTES ? (不足分支,只看退出码) : (充足分支)`,
而本机真实可用**就是 0** ⇒ 永远走不足分支;开关被整个忽略时,回退测量同样给
exit 2 ⇒ 断言通过。**"断言在,区分力不在"** —— 与 pi 点的是同一类病,
只是它藏在一个**跑不到的分支**里(嵌套三元短路),比"分支退化"更难看出来。
修法:不跟真实测量比,**让两个探针自己互为反面**,并断言**输出里的判定词**
(不只看退出码 —— 退出码可能与真实状态巧合相同):
探针 A:注入 1 字节 ⇒ exit 2 + 必须打印「< 需要」
探针 B:注入 128 MiB(>阈值)⇒ exit 0 + 必须打印「≥ 需要」
开关被忽略 ⇒ 两次都按真实测量给同一个答案 ⇒ 至少一条红。这个论证不依赖真实测量
是多少。变异自检实测:注入"忽略开关"的变异后,第 23、24、25 三条一起红。
顺带修一处**HEAD 里就带着的坏行**:第 162 行的 `test(..., () => {` 后面被塞进了
`// 覆盖…` 注释(上一次编辑吃掉了那个换行),整行不合法。这次一并拆回两行。
过程中我两次改坏文件(一次把手写 `replace` 的锚点算错、把"非法参数"那条整条删掉),
两次都靠 `git checkout HEAD -- <file>` 拉回重做 —— 这正是上一轮写进
`lib/env-error.mjs` 的那条纪律(变异/改写只对已提交文件做、还原只走 git)当场生效。
验证:`npm test` **475/475**;env-guard 单跑 30 条全过(含 24 号在两个探针下的双断言)。
|
2026-09-14 19:46:07 +08:00 |
|
|
|
87359588eb
|
fix(pi-bridge): 评审第二轮 —— 判据在健康机器上会退化、"一处覆盖"取决于入口、笔误参数静默放行
pi 读了 `5bc579f` 之后报了两条新的 + 三条小的,全部认下并落地。
## 一、"开关真的被认"那条判据在 /tmp 被清空后失去分辨力
上一版只在"真实测量不足"那个分支里断言(注入大数必须放行)。问题是:
**"不足"正是机器恢复健康后会消失的条件** —— 那天这条判据就退化成"只验
`--measure` 可用"的弱检查,而它守的恰恰是"开关别静默失效"。
两个方向是对偶的、各守一个机器状态,所以改成**按实测分叉、在两个分支里断言相反的方向**:
真实不足 ⇒ 注入大数必须放行 (开关被忽略则回退测量 ⇒ 2 ≠ 0 ⇒ 红)
真实充足 ⇒ 注入 0 必须 exit 2(开关被忽略则回退测量 ⇒ 0 ≠ 2 ⇒ 红)
量不到就 `assert.fail` 并说明"无法分叉"—— 不静默跳过(跳过会把"失去分辨力"
伪装成"验过了")。另把"端到端"那条的两个方向拆明白:只验"不足⇒2"时,
一个恒报不足的坏守卫也能绿。
## 二、"一处覆盖全部写点"成立的前提是"从 main() 进来"
`selfCheck()` 是**导出**的(用途就是被直接调),而兜住那三处裸写的 catch 在
`main()` 里 ⇒ 任何绕过 `main()` 的调用者撞上 ENOSPC 拿到的仍是原始英文堆栈。
**"覆盖范围取决于我以为的入口"正是这一串 bug 的共同病根**,所以把整段包一层
(`body()` + 统一 catch):与入口无关,`main()` 那个退化为冗余的第二道。
实测:`TMPDIR=/tmp node -e 'import("./deploy/check-deploy-drift.mjs").then(m=>m.selfCheck())'`
现在拿到的是「环境不足…这是环境问题,不是检查器的问题」。
## 三、`--inject-avail=abc` 静默放行(笔误 = 跳过守卫)
`Number('abc')` = NaN ⇒ 判据当"没测到" ⇒ 放行。现在按仓库约定处理:
**非法值 exit 2,未知参数也 exit 2**(`--measure` 少写 `=` 同样炸)。
`null` 仍是合法值("没测到 ⇒ 放行"是有意的),加了判据把这两个方向都钉住。
## 四、三条小的
- 两份实现(`lib/env-error.mjs` 的 `translateEnvError` 与 `deploy/` 的
`describeEnvError`)**不去重**,但两边各写一句"为什么不复用":
`deploy/` 的独立性比去重值钱(那份文件头整段在讲"服务不该依赖仓库是否存在")。
并写明**第三份拷贝出现时再考虑共用**。
- 写点计数口径写进注释:本函数 **6 处写** = `mkdtempSync`×2 + `mk()` 内 ×2
+ 三处裸写。免得与别处"五处"的说法对不上(上一封信里两个实测数字就是这么被误读的)。
- 变异自检的纪律补进 `lib/env-error.mjs` 头注释:**先证明能撤回来再注入变异,
且还原路径不能依赖被测对象**(那次把备份写进 `/tmp` —— 正是当时被占满的资源,
备份没写成而变异已覆盖源文件)。现在只对"已在 HEAD 干净提交"的文件做变异,
还原一律 `git checkout HEAD -- <file>`。
验证:`npm test` **475/475**;`--self-check` 18 条全过;
`TMPDIR=/tmp node deploy/check-deploy-drift.mjs --self-check` ⇒ exit 2 + 人话。
|
2026-09-14 19:42:58 +08:00 |
|
|
|
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 |
|
|
|
0b548b8fcf
|
test(pi-bridge): 临时目录满时不再伪装成内存缺陷 —— 前置自检 + ENOSPC 兜底
现场(2026-09-14 实测):`/tmp` 是 tmpfs,被别人占满,`statfsSync` 实读
`bavail*bsize` 只剩 **0.70 MiB**。此时 `npm test` 红一条
not ok 323 - ★巨大的 message 行不进内存也不影响解析
error: 'ENOSPC: no space left on device, write'
那条红的**形状指向内存**(用例名里就写着"不进内存",而它恰好是往临时目录写文件的
用例)⇒ 下一个踩到的人会去 `session-scan.mjs` 找一个**不存在**的内存缺陷。
改:
- `lib/tmp-space.mjs`:测量与判据分开,判据是纯函数 `judgeSpace`,喂字节数即可验;
读不到可用空间(null/NaN)⇒ **不判红**(不知道 ≠ 不对,否则会造出"总在亮"的红灯)。
**但 0 字节不是"不知道"** —— 第一版把 `<=0` 一并当"没测到",于是 `bavail` 只剩
712 字节时前置自检放行、紧接着 17 条用例 ENOSPC 全红:前置自检装了等于没装。
阈值 32 MiB = 实测单条用例最大写入量(`session-scan` 那条写 3×3 MiB 行 ≈ 12 MiB)
×2 + 8 MiB 机动,不是总容量的百分比(百分比在这套测试上没有依据)。
- `test/env-preflight.mjs`(名字不带 `.test.`,不被 glob 收进用例):
`package.json` 的 test 改成先跑它;不足时打印实测/阈值/目录并 **exit 2**
—— 与 `deploy/redeploy-plugin.sh` 的 `2=环境问题` 同一套约定,看到 2 才知道
去查机器而不是查代码。文案里明写「这是环境不足,不是断言失败」。
- `session-scan.test.mjs`:兜底翻译 ENOSPC(`node --test 'test/*.test.mjs'` 会绕过
前置脚本,这一句不管套件怎么被调起来都生效)—— 这正是治那条误导的关键。
- `test/env-guard.test.mjs`:8 条自证 —— 纯函数两头 + 边界(≥阈值算够、<阈值不够)
+ 0 字节必须红 + 读不到不判红 + 阈值有据 + 端到端 exit 2 且文案对得上。
端到端那条**不假设本机 /tmp 仍然满**:先自己量一次,够用就跳过并说明原因,
免得它退化成一条"总在亮"或"总在绿"的假判据。
顺带修 `deploy/check-deploy-drift.mjs` 两处同源问题:
- `selfCheck()` 要在临时目录造两棵小树,`/tmp` 满时抛 ENOSPC —— 而它是**未捕获异常**,
堆栈指向本文件,看起来像检查器坏了。翻译成说得清的错并让 main() 报 2。
- 新增判据 ⑥「工作区干净」—— **只提示,不参与 exit code**。判据 ① 比的是
「仓库工作区→快照」这一跳,覆盖不到「HEAD→工作区」那一跳(实证:一行未提交的
死代码被 17:20 的快照带进生产,而 ① 报的是"逐字节一致")。做成红灯就是一条
总在亮的判据(本文件头自己骂过的病),所以只说、不判。
验证:`npm test` 453/453(新增 8 条);`npm test` 在 /tmp 满时 exit 2 且不再跑用例;
`node deploy/check-deploy-drift.mjs --self-check` 17 条全过(含 ⑥ 的三条正反面)。
|
2026-09-14 19:29:44 +08:00 |
|
|
|
5c22daf51b
|
fix(deploy-drift): 结论区那行命令跑不起来(node → bash)+ 写明判据 ① 的量纲
两处,都在 `deploy/check-deploy-drift.mjs`:
1. 结论区打印的修复命令是 `node deploy/redeploy-plugin.sh <host>`,
而它是 **bash** 脚本 —— 照抄的人会拿到
`SyntaxError: Invalid or unexpected token`(脚本第 2 行 `#` 被当 JS 解析)。
这个坑的源头就是这一行:pi 在 2026-09-14 那轮部署请求里抄的就是它打印的输出,
抄错不是阅读问题,是判据自己印错了。改成 `bash …`。
2. 判据 ① 的注释写明它比的是**仓库工作区当前内容**,不是 git HEAD。
实证(同一天):`plugins/pi-mail-bridge/src/pool.mjs` 里一行未提交的
`let missingSessionCount = 0;`(模块顶层、无人读)被 17:20 那次
**从脏工作区做的**快照原样带进了生产,而判据 ① 当时报的是「逐字节一致」——
工作区与快照确实一致,只是两者都不等于 HEAD。
判据没错,是量纲只覆盖「仓库→快照」这一跳,覆盖不到「HEAD→工作区」那一跳;
想连那一跳一起判得单独比 `git diff --stat`。
(那条死代码本身已由 pi 的补丁 A 从工作区删掉,pool.mjs 工作区已回到 HEAD 内容;
下次部署后 `current/src/pool.mjs` 里的它才消失 —— 在此之前判据 ① 会因此报一处
pool.mjs 漂移,那是"待部署",不是新问题。)
|
2026-09-14 19:24:44 +08:00 |
|
|
|
f0884d03be
|
fix(pi-bridge): 决策到了没唤醒等待者 —— 整条授权链断掉(用户报「授权机制有问题」)
现场(用户:「我发现授权机制有问题,你看看 webui4frpc 的那个 session」):
同一条会话一天被问 6 次「是否允许执行 bash?」,**每次人都在 8 秒内点了同意**,
而每一轮都恰好烧满 10 分钟(TURN_TIMEOUT_MS),回信只有一句 59 字的开场白。
该 agent 自己的会话转录里写着:**"The bash tool keeps returning 'No result provided'"**。
根因:`worker.mjs` 的 `permission_decision` 分支取出了等待者、删了表项、存了备注,
**却没有调用 `resolve`**:
const resolve = pending.get(msg.relayKey);
if (!resolve) return;
pending.delete(msg.relayKey);
decidedExtra.set(msg.relayKey, { ... });
return; // ← 等的人永远醒不过来
于是一条命令走完下面这一整圈:
① 工具调用挂着不动 → 一轮跑到 10 分钟 TURN_TIMEOUT_MS 才结束;
② 桥把模型那半句开场白当「本轮总结」发回(59 字);
③ 会话里留下**没有 toolResult 的 toolCall** → 下一轮 pi SDK 给它补一条
`isError: true` 的「No result provided」→ 模型重试 bash → 人又被问一遍。
为什么之前全绿:`permission-note.test.mjs` 的 WIRING 钉的是
`decidedExtra.set(msg.relayKey)`(**备注=装饰**)与 `renderDecisionReason`,
**没有一条钉"唤醒"**。2026-09-13 那次修备注时把唤醒弄丢,判据照样全绿
—— 钉装饰不钉机制。
改:
- `resolve(msg.decision)` 补回,放在 `decidedExtra.set` **之后**(hook 醒来要读备注渲染
拒绝理由,顺序反了会复现 2026-09-13 的「备注丢失 → 模型重复追问」)
- 判据:WIRING 补一条「必须唤醒」;另加 `checkDecisionBranch` **按标记切出决策分支正文**
判"存在 + 归属 + 顺序"(不用"相距 N 字符"的窗口断言 —— 第一版就是那样假红的)
- 三种反面写法做变异自检:删掉 resolve / resolve 早于备注 / resolve 挪出分支,都必须红
- 全套 445 条通过
跨端核对:dsh 桥的 `pendingApprovals` 有 `pending.resolve(outcome)`(没这个问题);
opencode 走原生 permission 回复、zcode 走事件钩子 —— 这条路径只有 pi 桥有。
|
2026-09-14 19:16:22 +08:00 |
|
|
|
5544edabaf
|
fix(webui): 窄屏「我的」页补上圆角(与日历那次同一个根因,另一条分支)
用户(2026-09-14):「webui的『我的』页面,还是没有圆角」。
根因不在 AccountPage —— 在 App.tsx 的**分支**:没有列表栏的页面(我的/管理/写信)
走 `<div className="flex-1 min-h-0 flex">{main}</div>`,而 `.narrow-shell > *` 那条
圆角正落在这一层上。它是透明的、且 overflow: visible —— 有底色的是它里面的页面
根节点(`… bg-white`),内层直角从透明外壳里原样戳出来。
这与日历那次(`.narrow-stack` / `.cal-panes`)是同一条教训的**第三处**:
圆角必须给到**有底色的那一层**。
实测(390x844,真浏览器读渲染像素):
改前 面板 radius=0px,四角像素 255,255,255(纯白=方角)
改后 面板 radius=14px,四角像素 = 页面底色 249,250,251(被切掉)
宽屏 1280 一直是好的(对照:与收件箱详情栏的四角剖面逐像素一致)
改法:那一层挂 `narrow-solo`,圆角写在 `.narrow-solo > *`(有底色的那层),
**不写 overflow:hidden**(这层下面的子元素自己就是滚动容器,与 app-shell 那段同一个坑)。
横屏紧凑的两栏(`narrow-duo`)照 `cal-panes` 只给**外沿**:首元素左两角、末元素右两角。
判据 narrow-layout 新增 3 条(规则与 JSX 按类名接得上 / 不写 overflow:hidden /
两栏只给外沿);变异自检:把 `narrow-solo` 类名去掉,判据立刻红。
已部署(d3f6f3b·0914-1904);.deb 与 dist 同批重打。
|
2026-09-14 19:07:28 +08:00 |
|
|
|
d3f6f3baf6
|
跨端: 鸿蒙日历写侧(新建/编辑/删除/暂停恢复)+ 表单不再是一面滚轮墙
用户:「是的去吧」(批准上一封列出的下一步)。
- CalendarApi:createEvent / updateEvent / deleteEvent(POST /calendar/events、
PUT/DELETE /calendar/events/{id});新建与编辑发**同一份** CalendarEventInput
—— 服务端两个端点同形(CreateCalendarEvent 里那个 Status 字段的注释就写着这句),
拆两份会在严格解码下 400
- CalendarPage:写侧表单(新建与编辑共用一份)、二次确认删除、暂停/恢复走 update
(调度器只触发 active;没有暂停就只能删掉重加,而那会丢 event_id 与历史)
- 服务端 400 文案原样显示(收件地址无法解析:…)—— 建事件时就校验地址,
吞成保存失败等于让人猜,而猜的代价是以为设好了、实际永远发不出去
- 空标题/空收件人在本地就挡住,不发注定 400 的请求
- 提交的是**时间戳**(isoTimestampOfLocal),不是日期键;编辑回填走 localDateOf
- ★ 表单形状:日期/时间选择器合起来 ~1400px,展开着放会把标题与收件人挤出屏幕 ——
改成一行摘要 + 点开展开(默认收起)。这不是美观问题:展开态下保存在屏幕外,
要跨过一整面滚轮才够得着
- 判据 +7(端点/载荷/前置校验/文案/二次确认/时间戳往返/变异自检),23 条全绿
- 真机实测(hvigorw + hdc + uitest 逐字段输入 + 逐键点击,每一步都回库核对):
建 → 库里 event_time 是 2026-09-28T01:00Z(= 本地 09:00 整,默认值正确)、
暂停 → status=paused、删除第一下只变确认删除(库里还在)、第二下才真删(库里 0 条)
|
2026-09-14 18:57:53 +08:00 |
|
|
|
94ba4b9c58
|
跨端: 鸿蒙端功能同步第一步——日历(只读月视图)上架,入口进底部导航
用户:「要给鸿蒙端做功能同步」。按 API 面盘点(WebUI 62 个 API 函数 vs 鸿蒙 38 个),
最大的用户面缺口是**日历**:纯逻辑(model/Calendar.ts)与判据早就在,一直没页面。
新增:
- api/CalendarApi.ets:GET /calendar/events?from=&to=(与 WebUI 同参;区间按**网格**取,
不是月首月末 —— 首尾格子会显示邻月,只查当月会让那些格子永远空着)
- pages/CalendarPage.ets:月网格(翻月/回今天)、点某天看当天日程、事件点、今天/选中两态、
加载失败说出来。**没做**:写侧(增删改)、农历重复、.ics、滑动翻页 —— 逐条写在文件头
- model/Calendar.ts:补 localIsoOf / hhmmAtOffset / deviceOffsetMinutes(偏移是入参 ⇒ 三时区可真跑)
- model/Models.ets:CalendarEvent / CalendarListResponse(字段对齐服务端 JSON)
- NavItems:加「日历」,底部成为 通信/日历/联系人 三项(与 WebUI 同序)
- MainPage:日历是**常驻 pane**(visibility 控制),首次可见才拉数据;today 走
@Prop @Watch(visible) 在 pane 变可见时重算 ⇒ 结算欠账 calendar-today-recompute
(DEBTS 15 笔 → 14 笔,余额里不再计这一笔)
判据:harmony-calendar 新增 6 条(网格/表头同源、事件归日走 localIsoOf、三时区钟点、
today 重算路径、翻月走 addMonths、变异自检);harmony-nav ② 分派与 ④ 让位跟着改成结构性判据
(④ 原来那个 400 字符窗口一加 pane 就红 —— 窗口式判据的又一次现身);harmony-logic 两处
「只剩两个平级页签」跟着改成三项。
真机实测(harmony-emu + hvigorw assembleHap + hdc install + uitest click + dumpLayout):
9 月网格星期对齐(周一起始,2026-09-01 落在「二」列)、事件点恰好在有日程的那 6 天
(11/17/18/24/25/30)、点 09-17 列出当天两条日程且钟点是本地时间(DB 里 02:20Z/08:30Z
→ 界面 10:20/16:30)。
|
2026-09-14 18:38:51 +08:00 |
|
|
|
79e591aa8b
|
跨端: 底部导航选中态——图标也变色(鸿蒙侧补齐),选中态只换颜色
用户:「ui更新同步到鸿蒙端」+「选中对应的文字和图标变色即可」。
WebUI(上一提交):删掉背景块与顶部指示条,只留颜色。
鸿蒙侧:本来就没有背景/指示条(选中态从没用形状表达过),缺的是**图标那一半** ——
MainPage.NavItem 里只有 label 上了 fontColor,图标一直保持默认色,看着像选中了一半。
- MainPage.NavItem:图标补 fontColor,与 label 过同一个三元式
- 判据 harmony-nav ⑤ + 真变异自检(删掉图标那行 fontColor,⑤ 立刻红)
- 自检本身也修了一处窗口式判据:原来用 [\s\S]{0,80} 找 fontColor,
把下一行 label 的也圈了进来,自检自己假绿
- 模拟器实测(harmony-emu + hvigorw + hdc + uitest):同一字形在两项之间颜色随选中**对调**
(核心色 81,121,187 选中 vs 135,168,217 未选中)
- 提交归属:本提交同时动 harmony 与 electron,按仓库判据自报家门(跨端:)
|
2026-09-14 18:38:33 +08:00 |
|
|
|
12294835da
|
fix(余额): 圆角差异条目补两侧数值 + 策略现在就定(设备只做验证、不做决策)
pi 2026-09-14 两条。
1. **别让设备当"以谁为准"的决策者**:这个工作区起不了模拟器,把决策挂在设备上
就是一笔**永远不还**的账。观测能告的只是"并排看是否感知不一致",而策略现在就能写 ——
已写进条目(提案,待人追认):**语义配对成立 ⇒ 数值按两端各自成立(WebUI 的 rem 基准
vs 鸿蒙 vp/系统档),默认以 WebUI 为准**;只有鸿蒙平台规范明确要求系统档时才反向。
设备**只做验证,不做决策**。`kind` 从 env 改回 **scope**:这里等的是"有人拍一下",
不是"环境到位";并写明**实现者不能自己拍**(同 `unknown-preset-approval`)。
2. **补两个数**(没有数就既不能裁定也不能验证):
· WebUI:`0.875rem`=14px / `0.5rem`=8px;
· 鸿蒙:**本工作区读不到数值** —— `toolchains/id_defined.json` 该条目只有 name/type,
SDK 的 `ets-loader/sysResource.js` 只有**资源 ID 125829709 / 125829702**,
实体值在**系统资源包**里(编译进 resources.index / 随设备)。
这个"只能读到 ID、读不到值"的事实写进条目,免得下一个人以为去查一下就有。
|
2026-09-14 17:52:15 +08:00 |
|
|
|
59b2575838
|
fix(webui): 底部导航选中态只换颜色(去掉背景块与顶部指示条)
用户:「同时底部导航栏选中对应的文字和图标变色即可」。
- NarrowNav:删掉 bg-blue-400 顶部指示条
- index.css:.narrow-nav .nav-item[data-active='true'] 背景置 transparent——
只作用在底部导航;宽屏侧栏是 48px 竖条、没有文字标签,那块底色是它唯一的选中线索
- 图标本就是 stroke=currentColor,所以跟着文字色走(判据钉住这一点)
- 判据 nav-merge ④ + 变异自检:把 bg-blue-400 加回去,④ 立刻红
|
2026-09-14 17:50:34 +08:00 |
|
|
|
9965ee2204
|
chore(deploy): 清理补两类残留(数据库/附件备份集、会话清理前备份)+ 在线库硬闸门 + 两侧自检
- 备份集:同一 <ts> 的库+附件包一起进出窗口;按文件名时间戳排序(mtime 被访问改过)
- 在线库永不入删除清单:del() 里 readlink -f 比对,命中即 FATAL 退出
- 每个插件只剩 current 时报「无回滚目标」(只报告,不造快照)
- --self-check 16 项:干净样本该删的删/该留的留/在线库不动;坏样本(备份指向在线库)必须拒跑;
外加「自检没有碰生产根」的前后指纹比对
- 事故记录写进 DEV-TOOLING.md:自检传了 ROOT 而脚本读 AGENTMAIL_ROOT ⇒ --apply 打到生产根;
半对不对更难认(PRUNE_TMP_DIR 那一路是对的),是 bash -x 露的马脚
|
2026-09-14 17:50:29 +08:00 |
|
|
|
8390890646
|
跨端: feat(对齐): 参照物版本登记(CalendarView.tsx @d78f19f,变了即红)+ 圆角按语义配对(数值来源不同另行登记)
pi 2026-09-14 骨架开工前两件。
1. **对齐参照物要有版本号**:WebUI `CalendarView.tsx` 可能同时在动(gui-lab 有未合入改动),
照工作副本画完之后参照物一变,这版就成了"照一份没人认领的草案对齐的",而**没人能判它对不对**。
新增 `docs/ALIGN-REFS.json`(blob 哈希 + 登记于哪个 commit + "以哪次为准")
+ 判据 `test/align-refs.test.mjs`:哈希变了即红,报错按 §14 写明
「正确修法 = 读差异→判断骨架要不要改→再更新登记」与
「最常见的错误修法 = 把新哈希抄进去(那是把闸门降级成状态记录)」。变异确认会红。
当前登记:`d78f19f`「日历页面补上圆角」,工作副本干净 —— 若另有未合入的,合入后再对一次。
2. **圆角必须走令牌,且跨端按语义配对**:鸿蒙侧已有 `Theme.ets:105/107` 的
`radiusCard`/`radiusControl`(**系统**资源),WebUI 是 `--radius-card: 0.875rem` /
`--radius-control: 0.5rem`。⇒ 端**语义对齐、数值来源不同**,所以登记写成"按语义配对",
并且判据要求每条都写明"按语义还是按数值"(否则下一个人会直接去比数字)。
数值差异本身按形态差异进余额:`radius-card-numeric-divergence`(等设备并排看再决定以谁为准)。
鸿蒙侧不许把 14 / 0.875 抄成裸数字 —— 与当初 14 处 Material 调色板清零同一形态,量纲换成长度。
|
2026-09-14 17:46:20 +08:00 |
|
|
|
6fc8099b33
|
跨端: feat(日历): today 不许跨调用残留(常驻 pane 跨零点);扫描口径去注释+去字符串现在就定
pi 2026-09-14 骨架前最后两条。
1. **`today` 必须"显示时求值"**:顶层是「通信 / 日历 / 联系人」三个平级 pane,日历**常驻**,
而 `today` 是唯一随时间变的输入。建页取一次 ⇒ 跨午夜后标记停在昨天,**判据全绿**
(模型没错,是喂进去的值过期了)—— 与 `Calendar.ts` 防的"上次渲染残留"同一件事,
只是残留在**调用侧**。
· 纯逻辑侧判据(新增第 10 条):连续两次不同输入各自独立(不许跨调用残留)、
推进一天后值必须变、**不传就不许有今天**(不许内部兜底取系统时间 —— 那正是"取一次"的写法);
· 调用侧义务(pane 变可见时重算,不许只在 aboutToAppear)进余额:
`calendar-today-recompute`(kind=scope),到期前提写明"骨架写完后立刻"。
2. **`code()` 去注释、不去字符串**:任何只是**提到**被禁写法的字符串会被自己判红。
两条路("报错文案不引被禁模式" vs "只在表达式位置匹配")现在就定,选后者:
先**整段去掉字符串字面量**再匹配 —— "提到"不再误红,"真的用它取日期键"照样红。
副作用写在旁边(正则近似、非解析器;转义/跨行模板处理不干净 ⇒ 是采样不是完备性证明),
并写明:**出现同族误红时不许"再改一次扫描口径"把判据往宽里挪**。
|
2026-09-14 17:44:52 +08:00 |
|
|
|
13e8671d03
|
跨端: feat(P6): 表头与网格共用同一个 startOfWeek;"今天"的调用侧入可跑判据;禁用 toISOString 取日期键
pi 2026-09-14 两条,都赶在页面骨架之前定下来。
1. **唯一分叉点必须同时喂两处**:整月网格有两个地方依赖"周从哪天开始" ——
空格数(`leadingBlanks`)**与表头第一格**。表头若在页面里硬编码,就是**第二个分叉**:
格子全对、**表头整体错一列**,而原有 6 条判据一条都不会红(它们只看网格)。
新增 `weekdayLabels(startOfWeek)`(顺序只从这一个参数出)+ **交叉核对判据**:
把"1 号落在第几列"与"那一列的表头字"对上(6 个月份 × 2 种起始)。
2. **"今天"的调用侧**:`today` 入参化让纯逻辑侧干净了,代价是**唯一还能错的地方搬到了调用侧**
—— 而它正好是纯逻辑判据够不着的。`toISOString()` 是 UTC 口径:UTC+8 的清晨会给**昨天**,
"今天"就标到上一格,且在本机跑 UTC 的环境里**永远测不出来**。
新增 `isoOfLocal(now)`(本地年月日手工补零)+ `isoAtOffset(now, 分钟)`(与前者同源但不依赖进程时区,
好让三种偏移**可以真跑**)。判据钉住:UTC+8 / UTC-7 / UTC 三种偏移的日期、
**两种取法在 UTC+8 清晨必须不同**(把陷阱本身钉死)、以及本机两条取法自洽。
3. 附带一条**未来时**的判据:鸿蒙树里不许出现 `toISOString().slice(0,10)` 取日期键
(登记值 0 ⇒ 页面骨架写错时立刻红,报错写"正确修法 = isoOfLocal"与"最常见的错误修法 = 改期望值")。
它第一次跑就抓到了 `model/Calendar.ts` 里**解释这个陷阱的注释** —— 所以改扫 `code()`
(去注释后的代码):规则管代码,注释是文档。
|
2026-09-14 17:43:09 +08:00 |
|
|
|
e94e4dcd39
|
跨端: feat(P6): 日历纯逻辑 model/Calendar.ts + 6 条可跑判据(第 1 步的逻辑那一半,不需要设备)
|
2026-09-14 17:41:43 +08:00 |
|
|
|
9fd89308ab
|
fix(判据): 自指豁免改成"按次数"登记;标签缩回"已登记词表"并把它自己的盲区登记成欠账
pi 2026-09-14 两点,本质是同一件事:**判据自己的盲区,又成了新的"只写在某处"**。
1. **自指豁免从"整文件放行"改成"登记一个次数"**:整文件放行的后果很具体 ——
`debt-visibility.test.mjs` 成了唯一一个"可以在里面声明边界而不被抓"的文件,
而它恰好是定义词表的那个(将来有人在这里写一句真实的「这里没判/已知缺口」,① 不会红)。
现在把 N=9 当普通登记值:N 处是**词表定义与报错文案**,第 N+1 处就是新边界声明 ⇒ 红。
变异确认:在本文件里再加一处 ⇒ 红;撤回 ⇒ 绿。
pi 那句话我照抄进注释:"用自己定义的词表数自己无意义"推出的是"**这 N 处**无意义",
不是"**这个文件**无意义"。
2. **标签缩回断言范围**(§17 的收法一):判据名从"有边界声明就必须登记"改成
"用**已登记词表**声明边界的文件,必须在余额里有对应的一笔(词表外的说法不在范围内)"。
并且**词表键控这个盲区本身按我自己的新规则进了余额**:
`boundary-vocabulary-incomplete`(已知未覆盖:`TODO`/「这里没判」/「跳过」等同义说法抓不到),
到期前提是"发现词表外说法时扩词表并登记那一处"。
—— 这条是被**本判据自己的分支 ③** 逼出来的:它先报"debt-visibility.test.mjs 有边界声明,
但没有任何一笔余额的 where 指向它"。判据咬到了判据自己身上,正合其意。
余额:`debts=13 (static-only=5==登记 ✓ / other=8 by go-test)`。
|
2026-09-14 17:39:47 +08:00 |
|
|
|
d34342b49b
|
feat(判据): §16 本身变成判据 —— 判据目录里的"未覆盖/未验/已知缺口"声明必须在余额里有对应的一笔
pi 2026-09-14:§16 一直是"靠记得问"的纪律,按 §15 它该有判据;这是同一形状的**第五次**,
这次落在规则自己身上。
新判据 `test/debt-visibility.test.mjs`(三支,均已变异验证):
1. **出现次数必须与登记一致**:某文件里新多出一处边界声明 ⇒ 红,
报错按 §14 写「正确修法(先补一笔余额,再改次数)」与
「最常见的错误修法(只把数字调大/把声明删掉)」。变异:给 harmony-logic 加一处 ⇒ 红。
2. **每一笔余额必须带到期前提与位置** ⇒ 变异:清空 static-criteria 的 due ⇒ 红
(「那不是欠账,是"我们知道"」)。
3. **有边界声明的文件必须被某笔余额的 where 引用**(两处挂钩,不是各写各的)。
**自指豁免**(按文件 + 理由):本文件**定义**了那套措辞,用自己定义的词表数自己是无意义的
—— 而且它**第一次跑就把自己抓出来了**(11 处),证明计数是活的,不是走过场。
顺带把 `static-criteria` / `observability-output` 的 `where` 与具体文件挂钩(5 个静态判据文件)。
|
2026-09-14 17:38:33 +08:00 |
|
|
|
6eef1fd91c
|
fix(余额): 两条"已知未覆盖的回滚路径"进余额(绿判据的名字在默认路径上不打印)
pi 2026-09-14:这不是新规则,是我自己刚立的 §16 往上递归一层。
- 判据**绿**的时候名字不打印(默认路径只出 `RESULT pass=N fail=0`),
所以「另两条路未覆盖」这句话写在标签/注释里 = **默认路径上看不见** ——
正是 §16 要挡的那一类;与 overlay 那条同族:**希望别人知道的边界只存在于源码里**。
- 登记两笔,`where` 明确写「这是**已知未覆盖的回滚路径**,不是未验的运行时性质」
(两件事不同,不混一格):
· `nav-dark-route-b-unguarded`:引入深色主题 / 第一次给导航组件加 `dark:` 变体时堵;
堵法按**文件窄豁免**,不许写成「导航目录不许出现 dark:」。
· `nav-blur-route-c-unguarded`:第一次给导航元素加工具类模糊时堵。
余额:`debts=12 (static-only=5==登记 ✓ / other=7 by go-test)`。
|
2026-09-14 17:36:54 +08:00 |
|
|
|
22715e8af0
|
docs(判据): §17 变异只证明样本被抓;标签必须等于断言范围(两种收法,选一个)
|
2026-09-14 17:36:02 +08:00 |
|
|
|
1fcb754b61
|
fix(判据): 两条反向断言"标签比断言宽"——缩回真实范围并记下三条逃逸路(各注变异确认);overlay 运行期边界进余额
pi 2026-09-14:变异证明的是"我注入的那两条会被抓",不是"回滚会被抓" —— 对。
1. **标签缩回断言的真实范围**(选 pi 的 (a)):两条判据原先叫"导航不得变暗 / 不得自叠模糊",
实际各只扫了一条路。按 pi 预测的三条逃逸路各注一次变异,**三条全部逃掉**(导航变黑/变模糊,
判据照样全绿):`.dark .nav-rail{background-color}`(选择器作用域)、组件 `dark:bg-slate-900`
(Tailwind 产的不是 `.dark{--nav-}`)、组件 `backdrop-blur-lg`(不在那条选择器下)。
现在名字就是它们真正断言的东西,**未覆盖的路逐条写在注释里**(含变异表),
并写明要堵时按文件窄豁免写、别一刀切。
理由:**标签比断言宽的判据,会在它没测的那条路上被回滚时给绿,而人信的是标签。**
2. **overlay 的运行期边界进余额**(`overlay-follows-app-theme`):机制依据(`applyTheme →
setColorMode`、资源按应用配置解析)我认,但它是**运行期**性质、注释里的"未验"没人判 ——
按我自己刚立的纪律:只活在注释里的边界 = 没人判的边界。到期前提写死:上设备后翻转
colorMode,断言遮罩跟着「应用」主题变;证伪则改为从应用主题派生,**不是**回双常量。
|
2026-09-14 17:35:53 +08:00 |
|
|
|
9af4fde706
|
docs(计划): P2a/P2b 归属入册(鸿蒙侧,参照物是 WebUI 已成的形态);产物自证两条报错补"最常见的错误修法"
1. **归属弄反了**(pi 2026-09-14):联系人卡片视图 / 收件箱按会话折叠 / 删平级 tab
**全在鸿蒙侧落地**;WebUI 的 `ContactPanel.tsx`(列表/卡片两视图)、`lib/mailGroups.ts`
(按会话折叠)都是**参照物**,WebUI 根本没有平级会话入口。已写进计划文档,
并写明「收件箱只取 50 封」那件事**不与 P2a 互相阻塞**(它是 WebUI 现存缺陷,单独查)。
2. **§14 落到产物自证这一族**:`build-stamp` / `packaging` 的报错原先只有「正确修法」
(重构建 / 重打包),补上**最常见的错误修法** —— 别改 `BUILD_INFO.json` 的
`gitRev`/`srcHash`、别改断言或塞一份手抄 dist:那是把判据废掉。
|
2026-09-14 17:33:05 +08:00 |
|
|
|
e35ea479b5
|
fix(余额): 子集关系打进字符串本身(读到 9≠5 的人不该需要读信才知道为什么);映射欠账加"不许绕过映射"到期前提
pi 2026-09-14 两点。
1. `debts=9(registered) measured_static=5==登记 ✓` 让读的人先看到 9≠5,而
"本相位只有 static 有权威实测"只写在**信里**、不在那一行里 —— 那一行**没有一个字在判它可读**。
现在:`debts=9 (static-only=5==登记 ✓ / other=4 by go-test)(…)`。
理由与 §12 同源:**余额里任何需要靠注释才能读对的地方,就是下一个假绿。**
2. `blurStyleFor` 那笔欠账的到期前提再加一条:**调用点出现时**,判据要同时判
「调用点不许自己再换算一次 px→档」—— 否则调用点到位、消费侧计数转绿,
而换算被绕过(直接传算好的档位),映射那条行为判据照样全绿 —— 钉了个没人走的路。
|
2026-09-14 17:27:14 +08:00 |
|
|
|
06c1a67bb6
|
fix(判据): prose 要走顶层静态 import(我用了动态 import,被同一条判据第三次抓住)
同一天第三次栽在"读取纪律"上:先是裸 readFileSync,再是新判据里又用一次,
最后是"用了 prose 但没静态 import"。三次都是同一套判据抓的 ——
这正是它存在的意义,也说明"我记得住"确实不成立。
|
2026-09-14 17:22:19 +08:00 |
|
|
|
684baf7cc8
|
fix(判据): 新判据里又用了裸 readFileSync —— 被 criteria-hygiene 第二次抓住,改走 prose
教训补一笔:这条纪律我今天已经踩过一次(run-all.mjs),第二次犯在**刚写的那条判据**里。
说明"靠我记得"确实不成立 —— 抓住它的还是判据本身,这不是巧合,是这套机制在起作用。
|
2026-09-14 17:21:48 +08:00 |
|
|
|
b16e2d24fd
|
fix(欠账): RESULT 行同时打"登记 vs 实测"(副本漂移可见 + 不等即红);共享树 stash 纪律与两条推广入册
pi 2026-09-14 三条。
1. **可见的那个数字是副本**:`docs/DEBTS.json` 手可编辑,而它唯一的守卫是 Go 那条断言
—— 相位耦合(打印它的相位里 Go 断言未必跑过)+ 副本会漂移(我自己 §12 那句话)。
现在 RESULT 行同时给出:
`debts=9(registered) measured_static=5==登记 ✓(…;本相位只有 static 有权威实测,其余由 go test 侧判据比对)`
即:**本相位能实测的(static,权威来源就是代码)当场比对并标注 ✓/✗**;
**测不了的明说由谁比对**,不冒充已实测。并新增判据:**static 的登记值 ≠ 实测值即红**。
2. **共享树上不要用 `git stash` / `--autostash`**(pi:同一台机器,只是自动化了)——
它把**别人**未提交的改动也收走,冲突/中断时可能留在 stash 里,而 ta 只看到"我的改动不见了"。
入册 CRITERIA §8.1:优先 `git worktree add`;必须原地操作就先约定窗口;事后核对别人的文件还在原处。
3. **两条推广入册**(各只出现过一次,但形状会复发):
· §15 **判据必须自足**:不许通过共享可变状态在判据之间传递结论 —— 顺序依赖的显形方式
永远是**假绿**(我踩过:余额由 SKIP 那支写入、登记判据去读 ⇒ 同包按文件顺序跑 ⇒ 读到 0);
· §16 **"要提醒人的"输出必须走默认路径**(`TestMain` 收尾打印在常态 `go test` 里不存在),
配套动作是 pi 那句:**写完"可打印的余额"之后,去看一眼默认路径实际打出来的样子**。
|
2026-09-14 17:21:26 +08:00 |
|
|
|
512b3f8f80
|
fix(更正): 我登记的"不存在映射表"是错的(blurStyleFor 一直在,缺的是调用点);豁免按文件+次数抓出来的
pi 2026-09-14 三条接续,其中 §3 那一条**抓出了我自己的一个错误结论**。
1. **§3 交叉提醒(豁免按"文件 + 次数")→ 直接翻出我漏掉的东西**:
把 `bgBlur` 的豁免改成计数后,逐处核对出现次数时发现 `model/Appearance.ts` 里
**有一个 `blurStyleFor(bgBlur)`** —— **px → 系统材质档的映射表早就在那儿**
(文件注释还写着「判据可以直接跑它」),只是**没有任何调用点**。
而我先前把"鸿蒙没有消费点"登记成了"**不存在映射表 ⇒ 钉映射判据是假判据**",
**那是错的**,并且已经写进了两处文档(CRITERIA.md §10、计划文档 §7.12)。
⇒ 两处都**更正**了,并写明发现方式(计数机制把它翻出来的)。
正确的登记:**映射表存在且可判;缺的是调用点** —— 这两件事分开判。
2. **映射表按"它是行为"来钉**(新判据,26/26 绿):分档边界 0/8/20、
**单调性**(px 变大档次不许倒退)、**NaN 不许落到最厚那一档**(比较全 false 时掉到最后一档
是最坏方向)。`blurStyleFor` 是纯函数 ⇒ 与 Wallpaper 一样能直接用 node 跑,不需要设备。
3. **§1 痕迹的"输出那条腿"仍无人判 —— 我把它登记成欠账而不是假装钉了**:
页面层(`MainPage.ets`)有没有真的拿 `presetSubstitutedFrom` 打日志,现在**没有判据**。
我没有在本轮补上,原因是它要动 `.ets`(按环境约定,写 `.ets` 前要先按 ArkTS 纪律加载规范),
而 P6 第 1、2 步正好要动那个文件 —— 所以登记成 `docs/DEBTS.json` 的
`observability-output`(余额 1、到期前提写明"页面接上日志时同时补判据"),
**不是"未完成"含糊过去**,而是"什么时候还"写清楚了。
4. **§2 §10 那格从"无人类批准"收口成"待批准"**:仍进余额 —— `unknown-preset-approval`,
到期前提是"有人追认或驳回『未知 id 显示 aurora 而不是空白』这个方向",
并写明**我作为实现者不能自己追认自己**。
5. 顺手把 pi 那条建议入册(`CRITERIA.md` §14):**登记/清册类判据的报错要同时写
"正确修法"与"最常见的错误修法"** —— 因为读到红的人第一反应通常是改那个数字。
余额现在一处可见:`RESULT phase=install static=5 debts=9(…) probe=ok`。
|
2026-09-14 17:20:18 +08:00 |
|