|
|
b84880f14b
|
test(判据): 到期闸第一条按 (a) 升级 —— harmony-nav 加**行为层**(真 dumpLayout),并把"跳过"变成可数余额
pi 2026-09-18 报"静态判据到期闸开了"是真阳性:探针的前提"本工作区能装、能点
设备"现在成立(我实测:签名 HAP 装上 install bundle successfully、aa start
start ability successfully、uitest uiInput click 返回 No Error、uitest dumpLayout
出真 UI 树)。闸门要求 (a) 改行为判据 或 (b) 改换更准的前提 —— (b) 救不了:
它建议拆的"目标存在 ↔ 能装能点"两层我两层都实测为真,拆开照样红。所以走 (a)。
## 1. 新增行为层(harmony-nav,17 条)
`lib/harmony-device.mjs`:设备侧 harness(findHdc / hasTarget / foregroundBundle /
dumpLayout / walk / findByText / boundsAt)。**只读不抢** —— 模拟器是共享的,
应用不在前台就跳过,不启动、不点。
harmony-nav 新增两条:
· 「★ 行为(设备):底栏真渲染了可点的导航项(live ⊆ source)」——
真 dumpLayout,断"屏幕下 1/4 里真画出了可点的项、每项至少亮一个
源码 NAV_ITEMS 定义过的标签"。**版本无关**:已安装构建可能比 HEAD 旧
(实测前台那份是 3 项,HEAD 源码是 4 项),所以断"live ⊆ source"而不是
"相等"——"四项齐不齐"仍由静态层把。
· 「★ 判据自检:底栏取值逻辑」——纯函数 `navItemsOf` 上的合成树断言。
它**不替**静态那批(点击配对/挂载映射/命中区 ≥44vp/让位派生)——那些读源码更准。
它补的是源码读不到的那半:真渲染出来了吗、真可点吗、标签对吗。
## 2. 把"跳过"变成可数余额(run-all.mjs)
⚠️ 这是本轮**我先写错、再查出来**的地方,记在这里:升级之前套件里**没有任何
in-file skip**(全仓 grep 零命中)。加了第一条之后,`# tests N` 把跳过的**算进总数**,
而 `pass = checks - fail` 又把它读成**通过** —— 实测设备在 / 设备不在两次运行的
`RESULT files=…` **一字不差**(都 `checks=457 pass=453 fail=4`,而文件自己报
`# tests 16 / # pass 15 / # skipped 1`)。**"看不到 ⇒ 绿"长在总数行上。**
修法照 `fail` 那一格的先例(pi 2026-09-15 指出缺 `red` 时的同一形状):
· 取 `# skipped K` 成一格;`records` 带 `skip`;投影加 `totalSkip`(第 5 个);
· `pass` 改成 `checks - fail - skip`;汇总行补 `skip=N`(并在单位说明里写清它是
"本次没跑",既不是通过也不是失败);
· 自检 5 补 `checks ≥ skip`(对**每个**按文件累加的计数器都成立的上界 —— 照
pi 那条"只给其中两个判上界,第三个就永远没人管")。
实测:设备在 `… pass=454 fail=4 skip=0`,设备不在 `… pass=453 fail=4 skip=1`,
`pass+fail+skip == checks` 在两个方向都成立。
## 3. 到期债务结算一笔:7 → 6
STATIC_ONLY 去掉 harmony-nav;`docs/DEBTS.json` 的 static-criteria 7→6(含 where
清单同步);SUITE 登记数 11→17(原登记 11 早已与文件里的 15 条不符 —— 并发会话
加了日历/我的那批测试没改登记,这条红一并消掉)。
## 变异验证(都做了阳性对照,还原后 sha256 一致)
· M1 `sourceLabels` 取空集 → 行为条 not ok,报出真实文案 `✉️、通信`;
· M2 底栏阈值 0.75→2.0 → 报"实际 0,一个都没有";
· M3 `AGENTMAIL_HARMONY_DEVICE=none` → `# skipped 1`(且**改 run-all 之前**
汇总读不出来 —— 这正是上面第 2 条要修的实证);
· M4 删掉 `if (a.type === 'Text') return false;` → **第一次没红**(自检④拿的是叶子
FAB,它的 textsUnder 是空、被下一条内容条件滤掉,没测到那行)⇒ 换成"带子文本的
可点 Text"后**才**红。自检本身也会空跑,这是同一族病的又一个实例;
· M5 阈值 0.75→0.10 → 自检②红;M6 `clickable` 判定取反 → 自检①红。
## 说明与遗留
· 已在运行的构建比 HEAD 旧(前台那份 3 项),行为条按"live ⊆ source"设计,
所以它现在**绿**且**没有**把"我的缺一项"误报成红 —— 那是 build-stamp 的活。
· 模拟器是**别人会话的**:我为验证装/启动/点过(pi 明确没动它,我动了,如实记);
harness 因此按"不抢前台"写:不在前台就 skip 并计数。
· 余下 6 条到期判据(appearance/logic/cross-client-theme/defaults/admin/imageprep)
未动,仍留在 STATIC_ONLY 里红着 —— 到期机制该干的事。harness 已就位,可按
可观测性逐条升级。
|
2026-09-18 04:42:03 +08:00 |
|
|
|
77aa42623a
|
跨端: debt-visibility 补登记(新判据文件不会自动跑守卫)+ blurStyleFor 删除后的注释真相
## 一、`debt-visibility` 那条红:**新文件不会自动跑一遍守卫**
```
这些文件里有"边界声明",但一次都没登记:
harmony-deviceprobe.test.mjs(2 处) ← e917b87/4880c31 新加的判据文件
```
**这是同一个洞在新文件上的复发**:上一轮我刚修完 `harmony-admin` / `harmony-imageprep`,
下一个**新建的**判据文件又踩了同一个坑。pi 之所以看见,只是因为他跑了整个套件 ——
**缺的不是"记得登记",是"新建判据文件"这个动作没有守卫**。这条形状与"写了判据忘了接线"同族,
只是这次忘的是**登记边界**。
处理:**按次数登记(2),不整文件放行** —— 整文件放行的话,将来在这个文件里写一句
真实的「这里没判 / 已知缺口」就**不会红**。那 2 处本身也不是"这块没验",
而是对**词表本身**的断言(`unverifiedReason(...)` 必须含「未验」)。
同时在 `docs/DEBTS.json` 补一笔 `deviceprobe-fixture-timing`(`where` 指向该文件)——
`debt-visibility` 的第二条要求"声明必须有对应的一笔",两处各写各的会让审计只找到一处。
这笔的**到期前提是"两份 fixture 变成当场采集而不是人工存文件"**。
⚠️ **Go 侧未能本机验证**:`go test ./internal/repo/` 在本机报
`module cache not found: neither GOMODCACHE nor GOPATH is set`。我读了
`TestDebtLedgerMatchesMeasurement`:它只校验"每笔都有 due/where"+"三笔必须同处登记",
**没有"所有 id 必须在 Go 侧也列出"的断言**,所以新增一笔不需要改 Go。
但这是**读代码得出的结论,不是跑出来的** —— 如实标成未验。
## 二、`blurStyleFor` 删除后:生产代码里 5 处注释在说一个**不存在的函数**
函数已按 pi 的裁定删除(别的会话的 `9a10ab2` 落的)。但删除后
`Wallpaper.ts`(4 处)与 `MainPage.ets`(1 处)还在用现在时提它:
```
Wallpaper.ts:242 "由 `model/Appearance.ts` 的 `blurStyleFor` 映射成系统材质档"
Wallpaper.ts:248 "页面拿它去问 `blurStyleFor`"
Wallpaper.ts:251 "`blurStyleFor` 也写了、就是没有任何调用点"
Wallpaper.ts:311 "`blurStyleFor` 里面也有一次 clamp,那是它自己的防线"
MainPage.ets:1711 "(`blurStyleFor` 那张表服务的是**材质档**…)"
```
**这正是本会话反复在消的"注释描述一份不存在的代码"**,而它现在比之前更危险:
下一个人读注释会去找一个**已经被有意删掉**的函数,找不到就会**重新实现它** ——
而"它为什么不该回来"恰恰是那次删除唯一值钱的东西。
已全部改成**过去时 + 它已删除**,并在 `Appearance.ts` 原处留了碑文(函数没了,理由不能没)。
`:251` 那处尤其要改:原文说"`blurStyleFor` 也写了、就是没有任何调用点"——
函数已不存在,这句会让读者以为**还差一个调用点没补**,而事实是**连函数都不该有**。
## 三、这条碑文判据我做了变异验证
`harmony-appearance.test.mjs` 里那条「碑文不许回来」的判据**确实在校验**(不是摆着好看):
把 `Wallpaper.ts` 那段碑文抹掉 ⇒ **红**;还原 ⇒ **绿**。
顺带核了它的**指向**:碑文现在的主要落点是 `Appearance.ts`(函数原来所在处),
而判据的正则锚的是 `Wallpaper.ts` —— 两处都有内容才过,我保留了 `Wallpaper.ts` 里的引用
(它说明"这里的 px 不是材质档"),所以判据成立。
## 四、未做
- 到期闸门那 **7 条**(pi 更正过我:`STATIC_ONLY` 是 7 不是 8,我上封记串了)**仍然没动**。
- `PROBE_DEVICE=none` 下**剩 5 条红**,都是**别的会话**新加判据但没更新登记数
(`narrow-layout` 88>64、`nav-merge` 9>8、`harmony-presets` 6>5、`commit-hygiene` 3>2)
加 `build-stamp`(`dist` 没重构建,与本次改动无因果)。**我没有替他们改**。
|
2026-09-15 12:22:23 +08:00 |
|
|
|
30886271ea
|
跨端: docs(引用锚): 把日期锚换成邮件 ID(pi 邮件 b1e23663:日期也是锚,写错一天下一个人找不到那封信)
pi 指出的锚错:`PushContract.ts` 里那段线上形状是 pi **09-15** 发的(邮件 `004983bb`),我写成了 09-14。
照他的推理("引用别人的工作状态时,哈希和'谁在读'一样会漂")往下再走一步:
**日期本身就是会漂的锚 —— 邮件 ID 不会。** 所以不是把 09-14 改成 09-15 就完事,
而是把这一族引用改成**可检索的标识**:
- `PushContract.ts`:契约来源 → `1f9ff3b4`;线上形状 → `004983bb`;
- `harmony-push.test.mjs`:同上两处;
- `Calendar.ts`:表头共用分叉、"今天"的调用侧 → `f60521de`;
- `ALIGN-REFS.json`:参照物版本登记 → `6e14b410`;圆角策略追认 → `90372ca1`;AGC 包名实测 → `1f9ff3b4`。
共 10 处。判据不依赖这些注释文本,改完 `harmony-push` 12/12、`harmony-calendar` 10/10、`align-refs` 3/3 仍绿;
`ALIGN-REFS.json` 仍是合法 JSON。
**没改的 4 处**(`NavItems.ts` 1 处、`Wallpaper.ts` 3 处):它们写于 09-14 那批往来,日期本身没被指出错,
而那几封的邮件 ID 我这边没有(跨过一次上下文压缩)——**宁可留着有争议的日期,也不编一个 ID**。
|
2026-09-15 11:51:39 +08:00 |
|
|
|
46fa7fa729
|
feat(push): 可选、配置式、多厂商的推送通道(HMS 为首个实现)
用户要求:推送密钥必须是可选项(自部署后端不能写死推送方式),且要支持
多厂商配置式接入 —— 每个用户各自部署服务器、自己选厂商、自己配凭证。
所以落地成:
· internal/push:通道抽象 + 工厂表(RegisterType),加厂商不改配置层与端点形状;
HMS 只是第一个实现(internal/push/hms.go)
· 配置在 PUSH_CONFIG(默认 <AGENTMAIL_DATA_DIR>/push.json),一项一个厂商,
凭证走文件(app_secret_file / files.*,建议 600);环境变量只是可选覆盖
· 没配 = 整条推送路径连一次查库都不发生(shouldDispatch 早退);
单项配错(未知类型/密钥读不到/enabled:false)只跳过那一条,不影响启动
· push_tokens 表带 provider 维度 + 三个 /me/devices/push-token 端点;
没配推送时端点照存并回 enabled:false(登记成功 != 服务端开了推送)
· notify.Recipients 末尾异步挂钩:收件人名单直接用 SSE 那份 seen(两条通道
共用同一份"谁该收到"的判据);失败只记日志,绝不拖住收信
HMS 的形状是拿真凭证打线上接口问出来的(v1 + message.token[] + testMessage;
payload/target 形状 v1 不认、v2 要服务账号 JWT)。未上架应用必须 test_message=true,
单批 ≤10 token(MaxTokensPerRequest 声明)、每日 1000 条兜底(项目级额度)。
实测:App ID + App Secret 能换到 access_token(3600s);形状被线上服务接受。
判据:repo 6 条 + push 12 条 + handler 3 组,全部做过**变异验证** ——
过程中抓出两条假判据(异步分发与 t.Cleanup 赛跑而假绿;密钥文件优先级没被覆盖)
并补掉。Go 全量测试与 go vet 干净。
★ 未验:端到端真机送达(需要真机 token + 客户端按 com.jianf.agentmail 重编并签名,
签名指纹还要在 AGC 登记)—— 从未真正发出过一条能到达设备的推送。
详见 docs/HMS-PUSH-PLAN.md 的「实现状态」一节。
|
2026-09-15 11:21:00 +08:00 |
|
|
|
b806a05bfa
|
跨端: harmony 管理页(用户管理)+ P4c 壁纸上传入口 —— 「功能做全再交付」的两块
pi 的交付清单里缺的两块(`docs/GUI-PLAN-HARMONY.md` 原先把管理后台划在首版之外,
用户明确要求「功能做全再给我」之后收进来)。
标 `跨端:` 是因为判据落在 `client/electron/test/`(鸿蒙的判据目录一向量在那里),
代码本体全在 `client/harmony/`。
## 管理页(用户管理)
- `pages/AdminUsersPage.ets`:新建 / 编辑(显示名·角色·白名单)/ 启停 / 重置密码。
排布照 `AdminUsersPage.tsx`,包括「受限」徽标口径(普通用户且白名单非空才显示)、
最后登录缺席与空串都显示「从未登录」、管理员对白名单两项忽略。
- 入口在设置页底部,**仅管理员可见**(`role === 'admin'` 严格相等,与 `App.tsx` 同口径)。
读不到身份时**不**显示也不报错(乐观放行会让每个普通用户看到点进去 403 的入口)。
- `api/AdminApi.ets` + `model/AdminUsers.ts`(纯逻辑,零 import ⇒ 判据能真跑)。
- 启停**只发 status 一个字段** —— 服务端是部分更新,多发字段会把显示名与白名单一起改掉。
- `model/Models.ets` 补管理端 DTO;`main_pages.json` 注册路由。
## P4c 壁纸上传
- `model/ImagePrep.ts`:阈值与两档策略(2560/0.85 → 1280/0.78,入口 20MB,压后上限 3.5MiB)。
**一处有意不对齐 WebUI** 并写明理由:WebUI 卡 data-URL 长度(含 base64 膨胀),
鸿蒙内存直传 ArrayBuffer,卡的是字节数。
- `common/BackgroundPicker.ets`:不设 / 预设 / 自定义图片 + 浓度与模糊滑杆。
上传链:picker → 判可不可以 → 逐档按 desiredSize 解码压缩 → 上传 → **请页面以服务端为准重新同步**。
失败**必带原因**(服务端 415/413 文案原样透出)。用户取消选图**不算失败**。
- `ApiClient.uploadBytes`:MultiFormData.data 收 ArrayBuffer(核了 SDK,since 11;本工程 23)
⇒ 内存直传,不需要 base64 也不需要临时文件。
## 两处真 bug(变异测试逼出来的,不是"新写坏的")
1. **压缩循环的第二档此前是死代码**:循环里的 break 与循环外那句 shouldRetryWithActual
互相抵消 —— 把循环里那处改成 `if (true)`(永远只压一档)整套判据照样全绿。
收成一处判定(overLimit),循环外只读结论,并加结构性判据(该函数在这条链上只许调用一次)。
2. **壁纸的模糊档一直是「只写不读」**(计划文档 §7.12 登记过):滑杆能拖、值能存、
blurStyleFor 也写了,就是**没有调用点**,壁纸一点没糊。
本次补上的调用点分两层:壁纸层 `.blur(px)` = **图片内容模糊**
(与 WebUI 的 `filter: blur(var(--bg-blur))` 同一个量、同一个数,所以不需要映射表);
而那张**材质档**映射表 `blurStyleFor` 也终于有了调用点(`navMaterialFor` 内部复用它)。
`docs/HARMONY-ALIGN-PLAN.md` 的 §7.12 两行(材质 / 壁纸模糊度)已一并改准、不再互相矛盾。
## pi 复核后**改回来的**(这一笔里我自己犯的两处,都由 pi 抓出)
1. **导航条材质一度绑定到 `bg_blur`,`bg_blur=0` 时整个消失。**
我把 `NavBar` 从固定档改成 `blurStyleFor(bgPlan.blurPx)`,而滑杆 `min: 0` 可达、
`blurStyleFor(0) === 'NONE'` ⇒ 用户把壁纸调清晰时**导航条一点材质都没有**。
而且它与本笔自己的论证**相反**:刚论证完"图片内容模糊"与"面板材质"是两个物理量,
转头把面板材质接到壁纸模糊这个输入上。
现在**分层**:`blurStyleFor` 是通用映射(**允许** NONE —— "0 px 不模糊"是它的正确语义);
`navMaterialFor` 是**导航条专用、有下限**的入口(0 px ⇒ 最薄档)。
判据钉**可达性**(滑杆 0..40 每个整数 + 界外值都不许 NONE,且三档都要出现 ——
否则"恒定最薄档"会让滑杆成为死控件)。
2. **`Theme.navMaterial` 被我弄成了死令牌**,而看着它的判据**照样绿**
(那条只断言"声明存在且不是 NONE" —— 守的是声明,坏的是活的调用路径)。
现在导航条真的用它;并把同文件里**只覆盖 `Theme.overlay` 一个令牌**的死令牌规则
**铺到 Theme 的全部 35 个令牌**(量**外部引用数**:只被 Theme 内部方法读、
而那个方法自己有外部调用点 ⇒ 不算死 —— `chipSpentBg` 是这种;`navMaterial` 当时
唯一的消费者是一张可整体删掉的局部表,所以必须被抓)。
## pi 复核后**补上的**(这一笔漏掉的接线,都是我造成的)
- **`test/run-all.mjs` 的 SUITE 没接两个新判据文件** ⇒ HEAD 上 `npm test`
**一条判据都不跑、直接 exit 1**(套件自检 2 就是为这件事写的)。已接入,
并把两条登记进 `STATIC_ONLY`(`.ets` 要设备 ⇒ 静态欠账)。
- **`debt-visibility` 是我自伤**:那两个新文件里有 5 处"边界声明"但一次都没登记。
我当时报"2 条失败是改动前就红" —— **只对一半**:这条在父提交上是**绿的**。
我那次 `git stash push -u -- client/harmony` 的对照是**无效对照**
(`-- client/harmony` 把 `client/electron/test/` 整个排除在外,新判据文件根本没被 stash),
所以两次跑都红、看着像"既有"。已按 pi 的建议改用 `git worktree` 到父提交做对照。
现在两处都登记进 `docs/DEBTS.json`(含 `static-criteria` 5→7,Go 侧同一份登记同步改)。
## 一并修正的旧判据(都是"太宽/太窄/钉错东西",不是放宽标准)
- 「模糊归属」:原文「壁纸层不许有**任何**模糊调用」把**图片内容模糊**与**面板材质**
混为一谈(WebUI 侧核实:`.app-backdrop` 的 filter 与它之上那层的 backdrop-filter
是两个不同的量)⇒ 改成按两种模糊分别钉。
- 「bgBlur 只写不读,消费侧必须为 0」:值不再成立,**形状保留**(逐文件登记 + 计数 + 理由),
标题与断言里的假话一并改掉。
- isDarkMode 那条 `/dark\s*\)/` 断的是**参数顺序**(加一个入参就误红)⇒ 改成"dark 在实参里"。
- 三条钉 `backgroundBlurStyle` **整条字面表达式**的断言 ⇒ 改成钉语义
("用系统材质 + 材质有下限"),不再匹配那一行的字符。
## 判据
新增 `harmony-admin.test.mjs`(22 条)、`harmony-imageprep.test.mjs`(29 条);
`harmony-presets.test.mjs` 加 1 条(模糊档搬运与归一,含 `-0` 那个洞:
`Math.round(-0.4)` 是 `-0` 而 `-0 < 0` 为 false ⇒ 改成判 `!(r > 0)`)。
**`node test/run-all.mjs`:22 个判据文件全部跑起来**,红的只有 1 个:
`build-stamp`(`dist` 是 `a5fc86b` 上构建的,`gitRev` 对不上当前 HEAD)。
这条**不是我的代码造成的**(可证:`a5fc86b..HEAD` 之间,`srcHash` 覆盖的那批文件
——`client/electron/src` 等——**一个都没动过**,所以 `srcHash` 没变,差的是 `gitRev`),
但也**不是"改动前就红"**:任何推进 HEAD 的提交都会让它变红,正确修法是重构建。
## 未验(如实标注)
- **本机无设备/无模拟器 ⇒ 全部观感未验**:管理页排版与卡片观感、滑杆手感、
模糊在真机上的实际档位观感、系统材质在自绘悬浮条上的实际效果。代码齐 ≠ 真机验过。
- 预设档**没有**上模糊(壁纸在预设档下是一叠自绘矩形,系统材质对它不生效)——
这是我**主动收的范围**,不是漏,真机看一眼再决定要不要补。
- **Go 侧的 `debt_registry_test.go` 我没能跑**(沙箱里没有 Go 模块缓存,`go test` 起不来),
只做了 `gofmt` 校验;那处改动是一行 `Count: 5 → 7`。
|
2026-09-15 11:17:23 +08:00 |
|
|
|
474cadaf54
|
跨端: harmony 管理页(用户管理)+ P4c 壁纸上传入口 —— 「功能做全再交付」的两块
pi 的交付清单里缺的两块(`docs/GUI-PLAN-HARMONY.md` 原先把管理后台划在首版之外,
用户明确要求「功能做全再给我」之后收进来)。
标 `跨端:` 是因为本次的判据落在 `client/electron/test/`(鸿蒙的判据目录一向量在那里),
代码本体全在 `client/harmony/`。
## 管理页(用户管理)
- `pages/AdminUsersPage.ets`:新建 / 编辑(显示名·角色·白名单)/ 启停 / 重置密码。
排布照 `AdminUsersPage.tsx`,包括「受限」徽标的口径(普通用户且白名单非空才显示)、
最后登录缺席与空串都显示「从未登录」、管理员对白名单两项忽略。
- 入口在设置页底部,**仅管理员可见**(`role === 'admin'` 严格相等,与 `App.tsx` 同口径)。
读不到身份时**不**显示也不报错(乐观放行会让每个普通用户看到一个点进去 403 的入口)。
- `api/AdminApi.ets` + `model/AdminUsers.ts`(纯逻辑,零 import ⇒ 判据能真跑)。
- 启停**只发 status 一个字段** —— 服务端是部分更新,多发字段会把显示名与白名单一起改掉。
- `model/Models.ets` 补管理端 DTO;`main_pages.json` 注册路由。
## P4c 壁纸上传
- `model/ImagePrep.ts`:阈值与两档策略(2560/0.85 → 1280/0.78,入口 20MB,压后上限 3.5MiB)。
**一处有意不对齐 WebUI** 并写明理由:WebUI 卡 data-URL 长度(含 base64 膨胀),
鸿蒙内存直传 ArrayBuffer,卡的是字节数。
- `common/BackgroundPicker.ets`:不设 / 预设 / 自定义图片 + 浓度与模糊滑杆。
上传链:picker → 判可不可以 → 逐档按 desiredSize 解码压缩 → 上传 → **请页面以服务端为准重新同步**。
失败**必带原因**(服务端 415/413 文案原样透出)。
- `ApiClient.uploadBytes`:MultiFormData.data 收 ArrayBuffer(核了 SDK,since 11;本工程 23)
⇒ 内存直传,不需要 base64、也不需要临时文件。
- 用户取消选图**不算失败**,什么都不说。
## 顺带修掉的两处真问题(都是变异测试逼出来的)
1. **压缩循环的第二档此前是死代码**:循环里的 break 与循环外那句 shouldRetryWithActual
互相抵消 —— 把循环里那处改成 `if (true)`(永远只压一档)整套判据照样全绿。
收成一处判定(overLimit),循环外只读结论。
2. **壁纸的模糊档一直是「只写不读」**(计划文档 §7.12 登记过):滑杆能拖、值能存、
blurStyleFor 也写了,就是**没有调用点**,壁纸一点没糊。本次补上调用点
(壁纸层 .blur(px) = 图片内容模糊;导航条材质由 blurStyleFor 映射)。
同时按 §7.12 的原承诺更新了那一行。
## 一并修正的旧判据(都是"太宽/太窄",不是放宽标准)
- 「模糊归属」:原文「壁纸层不许有**任何**模糊调用」把**图片内容模糊**与**面板材质**
混为一谈(WebUI 侧核实:.app-backdrop 的 filter 与它之上那层的 backdrop-filter
是两个不同的量)⇒ 改成按两种模糊分别钉。
- 「bgBlur 只写不读,消费侧必须为 0」:值不再成立,**形状保留**(逐文件登记 + 计数 + 理由),
标题与断言里的假话一并改掉。
- isDarkMode 那条 `/dark\s*\)/` 断的是**参数顺序**(加一个入参就误红)⇒ 改成"dark 在实参里"。
- 导航材质三处断言原本钉 `Theme.navMaterial` 字面量 ⇒ 改成钉新的映射写法。
## 判据
新增 `harmony-admin.test.mjs`(22 条)、`harmony-imageprep.test.mjs`(29 条);
`harmony-presets.test.mjs` 加 1 条(模糊档搬运与归一,含 -0 那个洞)。
全量 203 条:**201 通过**,2 条失败为**改动前就红**的既有项
(BUILD_INFO 比对、词表↔余额)—— 用 stash 对照验证过。
两个新判据文件上跑了 **48 个变异体,全部被抓**(含"接线"类:删掉「受限」徽标、
组件自己宣布成功、release 不 await、按原图尺寸解码…),
其中 2 个变异体**红不了**,因此又补了 5 条判据(纯逻辑接线、退档判定只有一处、
两档都超限必拒、解码尺寸用的是目标尺寸而非原图尺寸、模糊档搬运)。
(数字口径:按 runner 的真实条件"锚点恰好命中 1 次才算跑过"统计;
另有 4 条锚点不命中、根本没跑,不算在这 48 里。我第一次写的是"40"——
凭记忆累加的,错了,已更正。)
**未验**:本机无设备/无模拟器 ⇒ 全部观感未验(管理页排版、滑杆手感、模糊在真机上的
实际档位观感)。代码齐 ≠ 真机验过。
|
2026-09-15 11:03:22 +08:00 |
|
|
|
f14f2d6fa7
|
chore: 动画盘点判据接入套件 + 让其合规 + 对齐参照物重新登记(清工程收尾)
用户:「清理一下tmp和工程吧」。
- 新判据 test/animation-audit.test.mjs 接入 test/run-all.mjs:原先**写了却不会跑**
(套件自带的那条闸门当场报「这些判据文件没接进套件」)。
- 该文件改用 test/lib/read.mjs 的具名入口(code/prose/bytes),不再裸用 readFileSync ——
criteria-hygiene 抓到:读原文判代码会被解释性注释骗,今天已踩过两次。
- docs/ALIGN-REFS.json:CalendarView 的对齐登记按规矩**重新核对后再登记**
(差异只有动画类 rise-in → pane-rise,骨架/布局/圆角来源未变;鸿蒙侧本无日历动效
⇒ 不产生新的对齐义务),不是抄一处新哈希。
|
2026-09-15 09:16:05 +08:00 |
|
|
|
f6cecf7867
|
docs(align-refs): CalendarView 重新核对后更新登记(差异只有根节点 rise-in;鸿蒙侧无日历动效,不产生新对齐义务)
|
2026-09-15 07:57:10 +08:00 |
|
|
|
f8fe14d124
|
docs(debts): 登记 pi-bridge-adopt-cwd-mismatch —— 沙箱 rw 与 worker cwd 的第三个来源(接管路径)未对齐,且这类错位没有任何一层提示
|
2026-09-15 06:47:24 +08:00 |
|
|
|
28a6bd282a
|
docs(dev-tooling): 第 21 条 —— 夹具把生产形状简化掉的那一角(一天内第二个实例:agentDir 与首回合 cwd)
|
2026-09-15 06:33:54 +08:00 |
|
|
|
9fa509844a
|
feat(pi-bridge): 有沙箱时 workspace 档不再逐条问人 —— 界内不问、界外内核拒
沙箱上线后,"工作区档"的语义第一次可以按档位表兑现:**边界是内核在守**,再问一遍
只是让人点一次"同意",点完该失败的还是失败(人点了也挡不住内核)。所以闸门改成
**按档位 × 有没有沙箱** 决策,纯函数收在 `lib/sandbox.js`:
| 档位 | 沙箱 | 决定 |
|---|---|---|
| full | 任意 | allow(发件人已声明全权) |
| plan | 任意 | block(本档只许看;沙箱是第二层) |
| workspace | **在** | **allow** ← 这一步改的(界内不问、界外 EACCES) |
| workspace | 不在 | ask(回退到原来那唯一一层) |
没有沙箱时**继续问** —— 这条是"不会更松"的保证:沙箱缺失/未装/被关掉时行为与改前
逐字一致。
## 标记不等于事实:worker 自证
`AGENTMAIL_PI_SANDBOXED=1` 只是父进程的**声明**。判断错会让闸门既不问也不拦
(最坏的一类),所以 worker 现场自证一次:往界外写一个金丝雀(`/.agentmail-sandbox-canary-<pid>`,
根目录永远不在 rw 里)—— 写得进去 ⇒ 判为"没有沙箱",**退回逐条问人**(方向取严);
被拒(EACCES/EROFS/EPERM)⇒ 在边界内。结果缓存在进程级。
## 顺带把 plan 档变成真的只读
plan 档的 rw 清单**不含会话工作区**(只有临时目录/pi 会话登记/桥配置/`/dev/null`):
"一个字都不许写"从"钩子拒绝 + 提示词"{升级为内核第二层。
## 判据
- `sandbox-launch.test.mjs` 10 条(原 6 + 新 4):决策矩阵四档 × 有无沙箱、
自证两侧(被拒=在边界内;能写=必须判"没沙箱")、plan 档 rw 不含工作区、
"pool 设标记 + worker 自证 + 走 guardDecision"三处接线在。
- 变异:把 workspace+sandboxed 改回 'ask' ⇒ 那条断言红。
- pi 桥全套 489 项通过。
- ★ 又被自己撞一次同类坑并当场红:新变量起名 `decision`,与同一个函数里后面那个
`const decision = await new Promise(...)` 撞名 ⇒ SyntaxError。上一轮的 `spawn`
撞名也是这一族(局部名与既有作用域重名),两次都是**语法检查/测试**立刻抓到。
## 文档
`docs/PLAN.md` §7.11 的 L5 矩阵与"向更严取整"那条纪律、`docs/API.md` 的档位表
都改成新语义(有沙箱=内核拒、无沙箱=逐条问),并写明 pi 的沙箱为什么必须由宿主提供。
|
2026-09-14 23:50:35 +08:00 |
|
|
|
4c2bf26c42
|
fix(deploy): flock 没登记进 AGENTMAIL_REQUIRE("缺命令"被报成"另一个部署在跑")+ 中断 trap + 两条欠账入册
**1. 自指缺口:新能力带的新依赖没登记回表(pi 抓到)**
我加"同时性"那一列时引入了 `flock`,**却没把 `flock` 加进三个脚本的 `AGENTMAIL_REQUIRE`**。
后果实测:
PATH 里没有 flock ⇒ `flock: command not found`(127)⇒ `! flock` 为真
⇒ 打印"**另一个部署正在跑(锁被占用)**"
退出码事后是对的(2),但**诊断是错的** —— 而照着它做的是"等另一个部署结束":**永远等不到**。
三个脚本各加一个词;并在 `env-defaults.sh` 的 ③b 注释里写明这条规矩
(**新增任何外部命令时回到 `AGENTMAIL_REQUIRE` 登记**)与这个实例。
→ docs 第 19 条:「表与被表的东西不同步」。
**2. 第六列候选:中断(信号)—— 已按 pi 的建议修 `redeploy-gateway.sh`**
原子 `mv` 修的是"半截二进制",**没修"服务停着而脚本死了"**:
第 250 行 stop 与第 267 行 start 之间被外部信号打断(Ctrl-C、宿主杀进程、会话回收、OOM)
⇒ 脚本直接退出、**服务留在停止状态而什么也不说** ⇒ "邮件全停 + 无人告知"。
已加 `trap … INT TERM HUP`:进窗口前置位 `_SERVICE_STOPPED`,出窗口复位并摘 trap;
**trap 只在"确实还停着"时才动手**(否则会多起一次服务);回滚分支也维护该标志。
**用 stub `systemctl` + 探针真喂过四个分支**:
stopped=1 + SIGINT ⇒ 调了 `systemctl start`、退出码 130、打印点名
stopped=0 + SIGINT ⇒ **没有**调用 start(不误起)
(探针里两次 harness 自身的错也一并记下:`sed`/`awk` 的区间端点选错,
把 `trap -` 也取进来,导致"trap 没生效"的假象 —— 是探针错,不是代码错。)
★ 顺带修掉自己写的一处:`printf '… $SERVICE …'` 用**单引号**包裹 ⇒ `$SERVICE` **不展开**,
原样打出字面量(探针里实测看到)。改双引号传参。这类"消息里有变量但没展开"会让读者
以为服务名真叫 `$SERVICE`。
**3. `install -d -m` 对已存在目录的行为:实测会改(pi 的疑问)**
mkdir -p 建 755 → `install -d -m 0700 <同一目录>` → **700**
所以**下一次部署就会收紧** `/opt/agentmail/data` 与 `/etc/agentmail`,不需要额外的
`chmod 0700` 动作,也不必为此单开一次"人按一下"。
(我按这条如实回报,因为 pi 说过"若不会改就需要显式 chmod,且安全意义比
`user-question.js` 高" —— 结论是不需要。)
**4. 两条欠账入 `docs/DEBTS.json`(按 pi 的界线:只修新机制自己引入且会误报的缺口)**
· `deploy-space-prefix-fs`:空间列只铺了 `$TMPDIR`,没铺 `$PREFIX` 所在文件系统
(属"列内没铺满",不是新列)。
· `deploy-interrupt-trap-other-scripts`:trap 只在 `redeploy-gateway.sh`;
`install.sh`/`redeploy-plugin.sh` 被打断同样会留半成品(没有"服务停着"那种后果,故低优先)。
→ docs 第 20 条同时记下 trap 这条纪律与它的可喂判据写法。
验证:install.sh --check exit 0;npm test exit 0;prune 自检 22/22;drift 自检 35/0;
check-shared-libs exit 0;全部 deploy 脚本 bash -n 通过;DEBTS.json 有效(13 条)。
|
2026-09-14 21:26:51 +08:00 |
|
|
|
1056b22cbd
|
fix(deploy)!: install 不是 rename(头部那句"原子"论断不成立,实测半截二进制)+ 加并发锁 + journalctl 抽成可喂函数 + data/ 权限
pi 的四条,逐条实测:
**1. `install(1)` 不是 rename —— 而那句话是整节设计的理由**
头部原话:"install(1) 本质是 rename,是原子的 —— 要么完整换掉,要么原样不动"。
按他给的命令实测 `strace … install -m 0755 /bin/true /tmp/t`:
目标不存在:`openat(t, O_WRONLY|O_CREAT|O_EXCL)`
目标已存在:`unlinkat(t, 0)` → `openat(… O_CREAT|O_EXCL)` → 写入
**全程没有 rename/renameat**。即复制路径,**旧文件在新文件写完整之前就没了**。
中途失败实证:`ulimit -f 1` ⇒ 退出码 **153**(SIGXFSZ),目标变成 **1024 字节截断 ELF**,
原 14 字节内容**已被销毁** —— 正是本段前半句写的风险,`install` 并不免疫。
(第一次测时我把退出码经管道取到了 `head` 的 0 —— 正是 docs 第 6 条那个坑,重测才拿到 153。)
★ 他补的第二个坑也确认:`/tmp` 与 `/opt/agentmail` **不同文件系统**
(实测设备号 40 vs 2049)⇒ 就算换成 `mv` 也不原子(跨 fs 退化成 copy+unlink)。
已改成真原子三步:**目标同目录**暂存 → `install`(动的是"还没人用的名字")→ 一次 `mv -f`。
对照 `redeploy-plugin.sh` 的 `mv "$STAGING" "$SNAP"` 是**真原子**(同 fs)——
同一仓库原先两套"原子切换",一套真、一套名义上的。
**2. 缺并发锁(环境前提表的第五列:同时性)**
原表(变量/命令/空间/身份)漏了这一类,而它不是假设:工作区是多 agent 共用的。
两个部署同时跑 ⇒ 各自 stop(一次失败、状态没人看)→ 两次写同一目标(配合上面那条 ⇒
真能留半截)→ 两次后置验证互相把对方的"验证不过"当自己结论 → 谁回滚不确定。
三个脚本都加 `flock`(**不是**"检查锁文件存在",那本身有竞态)。
实测:同一把锁上第二个进程 `flock -n` 失败;脚本形态下 `install.sh` 的 `--check` 不建锁
(干跑只读、且刻意允许无写权限运行)。
**3. journalctl 抽成可喂函数 —— 并且他对我那次"复现"的更正成立**
他说我复现的是**"空输出"支**,不是"读不到"支。实测确认:
`journalctl -u 不存在的-unit` 退出码 **0** ⇒ 我测到的是 else 分支。
已抽成 `am_scan_logs <unit> <since> <pattern> [命令]`,输出三态
`clean`/`hit`/`unreadable:<码>`(与既有 `describeEnvError`、`judgeRestart` 同一做法:
把能被样本喂的部分抽出来)。**用 `/bin/false`、`/bin/true`、假"输出含 panic"的脚本
三个样本喂过**(不碰生产):`unreadable:1` / `clean` / `hit` —— 三条支路现在都有覆盖。
判据也随之分开:**"命令不在"由 `AGENTMAIL_REQUIRE` 兜、"命令在但读不到"由这个函数兜**,
两列在代码里分开,而不只是注释里分开。
**4. 低优先项里 umask 那条是真问题(我原来以为可忽略)**
实测 `install -d` 权限位受 umask 影响;而本机生产 `/opt/agentmail/data` = **755**、
`agentmail.db` = **644**(全局可读),同一脚本里 `agent-config`/`pi-config` 却是显式 `-m 0700`
—— 同一脚本两套口径,而那个库里是全部往来邮件。
已改:`install -d -m 0700 "$PREFIX/data"`、`-m 0700 "$ETC"`(密码与密钥)。
(现有生产权限不在本次改动范围,属部署后生效。)
**顺带修一处我自己的口径不一致**:`install.sh` 的 root 检查用 `exit 1`(判据失败),
而另外两个脚本与 `env-defaults.sh` 的"环境不足"都用 **2** —— 调用者无法据此区分
"该重跑"还是"该修代码"。已统一为 2。
★ 加锁过程中我自己连踩三次"复制粘贴的上下文假设"(都已修,并记进 docs 第 18 条):
`install.sh` 没有 `bad()` ⇒ 127;`redeploy-plugin.sh` 没有 `$PREFIX`(用 `$DEST_ROOT`)⇒
`unbound variable`;`install.sh --check` 无写权限 ⇒ 建锁 `Permission denied` 又变 127。
**同一份代码搬到另一个脚本里,能引用的变量和函数是不一样的。**
验证:install.sh --check exit 0;npm test exit 0;prune 自检 22/22;drift 自检 35/0;
check-shared-libs exit 0;全部 deploy 脚本 bash -n 通过。
|
2026-09-14 21:19:46 +08:00 |
|
|
|
8e3b04a267
|
fix(deploy): TMPDIR 只判"未设"(同文件里 HOME 判了可写)+ 环境自足漏了"命令"(journalctl 两处是假绿)
pi 给了"第五次"的两条线索,都在我读得到的地方,逐条实测确认后修完:
**1. TMPDIR 与 HOME 不同规则(就在同一个文件里)**
① `HOME` 那边写了两条规则:`mkdir -p` 对**已存在的不可写目录会返回成功** ⇒ 必须单独判 `-w`;
判据落在"能不能写"不落在"路径像不像"。**同一条规则没落到 ② `TMPDIR` 上** ——
而 ENOSPC 正是这条链的元老问题(四次史里第 3 条就是 TMPDIR)。两种失败形状:
已给但**不可写**(EACCES)、可写但**已满**(`-w` 抓不到,要的是**空间**判定)。
已补 `-d` + `-w` + 可用空间(`df -Pk`,读不到⇒**不据此判定**;`0` 是**真的没有**);
不足 ⇒ 人话 + exit 2。**不 import** 插件那份 `test/lib/tmp-space.mjs`:
`deploy/` 侧要能独立分发,为去重引进平台代码不划算(按既定理由,写最小版本)。
实测 `TMPDIR=/root/nope` ⇒ `[FAIL] 环境不足:TMPDIR=… 不存在或不可写` + 退出码 2。
**2. 环境自足只覆盖"变量",没覆盖"命令" —— 其中 journalctl 两处是假绿**
这节的要害是 pi 给的那句判据,我认:**"命令不在"必须走 2/红 + 人话;
"命令在但输出为空"才是判定结果。** 原先两处把两者压成同一个字符串 `"0"`:
journalctl 失败(被 `2>/dev/null` 吞掉)⇒ grep 读空 ⇒ `fc="0"` ⇒ **打印"无 panic/fatal"**。
实测复现:`journalctl -u 不存在的-unit | grep -icE 'panic'` ⇒ `fc=[0]`。
`sse` 那条同形、后果更坏:**把"读不到日志"归因成"插件没连上"**,让人去查密钥。
⚠️ 顺带实测:**`PIPESTATUS` 分不开这两种情况**(命令不存在与"存在但无匹配"都给 1),
所以不能靠管道状态区分 —— 必须**先取输出、成功后再过滤**,命令存在性另做前提检查。
改法:两处都改成"先取日志、看退出码";读不到 ⇒ `warn` 明说"读不到、无法据此判断"
(既不假绿也不假红)。并给三个脚本加 `AGENTMAIL_REQUIRE` 前提检查
(缺一个 ⇒ exit 2 + 人话),与四次史的处理**同形**,只是对象从变量换成命令。
实测:`AGENTMAIL_REQUIRE` 里放不存在的命令 ⇒ 退出码 2。
**3. 顺带修 pi 点到的两处同族问题**
· `install.sh` 的 `HEAD_REV="$(git … rev-parse --short HEAD)"`:`set -e` 下失败**直接中止**
(实测退出码 127、无翻译);而且 HEAD_REV 为空会让下一句报
"这个包比源码旧:产物 gitRev=… ≠ HEAD=" —— **把"这里不是 git 仓库"说成"产物过期"**。
已改成显式判失败 + 明说"读不到当前 HEAD,跳过新旧比对"。
· 同块第 94 行末尾挂着一个 `|| true` ⇒ 整行退出码恒 0 ⇒ 它作为 `if` 条件**永远为真**
("判据的形式在、区分力不在")。已改成显式计算、去掉 `|| true`。
docs 补两条纪律:15「"命令不在" ≠ "命令在但输出为空"」(含 PIPESTATUS 分不开的实测)、
16「一条规则写了,要检查它是否落到了所有同类对象上」。
验证:install.sh --check 空环境 exit 0、正常 exit 0;TMPDIR 不可写 exit 2;
npm test exit 0;prune 自检 22/22;drift 自检 35/0;check-shared-libs exit 0。
|
2026-09-14 21:08:48 +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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
d25770ea2f
|
fix(欠账): Skip 进余额且条件必须是测量;豁免按文件+次数;三笔欠账合成一处可读余额
pi 2026-09-14 三条(他接受了我对"恒红=相位错"的反驳,但指出 Skip 带来的两处漏洞)。
1. **Skip 必须进余额、条件必须是测量**:
- **条件**:跳过与否由 `measureMailStatusDebt` **实测**(详情路径的 status 是否真的
等于按读者派生),不是常量、不是"我们还没迁完"这种没人会更新的事实;
- **余额**:`docs/DEBTS.json` 是**唯一登记**,Go 侧判据 `TestDebtLedgerMatchesMeasurement`
**自己测量**后与登记比对(第一版我让余额由另一条判据写入 ⇒ **排序依赖**,
Go 同包内按源文件顺序跑,登记那条先跑就读到 0 —— 排序依赖是隐蔽的假绿,已抽成自足函数);
- **可见性**:`go test` 跑通时**不打印包的输出**,我第一版把余额打在 TestMain 里,
常态运行一个字都看不见 —— 正是 pi 说的"不显形"。所以常态可见的那份打在
electron 套件的 RESULT 行:`RESULT phase=install static=5 debts=7
(static-criteria:5,mails-status-derived:1,gesture-semantics:1) probe=ok`。
2. **豁免从"按文件"改成"按文件 + 次数"**:`migrate.go` 这类比较**上限 2 处**(附理由),
多一处即红。我在读侧清册上自己修过这个洞,豁免那格却退了一格 —— pi 指出得对。
3. **三笔欠账合成一处**:原先各自表达(`RESULT static=5` / `t.Skip` 无余额 /
文档里的到期前提无余额),**没有一处能一眼看全**。现在统一登记在 `docs/DEBTS.json`
(id / 余额 / 到期前提 / 判据位置),两端读同一份:Go 侧比对实测,electron 侧打进 RESULT 行。
还清那天:登记要跟着清 —— 不清则由 `TestDebtLedgerMatchesMeasurement` 报
"**欠账已还清**,但登记还记着 N"(还清是可测事件,这正是那条判据存在的意义)。
|
2026-09-14 17:17:37 +08:00 |
|
|
|
eeb8f277fd
|
test(mails.status): "正当"落成可判约束(只许比 archived);欠账补一条"还清即转绿"的判据
pi 2026-09-14 的三点接续。
1. **§1「正当」不能只是标注,要是可判的约束**:按文件+计数登记挡得住"新增一处",
挡不住"**已登记的那一处改变性质**"。已判出来:**任何与 m.status 的比较只允许比
'archived'**(全局属性),与 'read'/'unread' 比较 ⇒ 红(per-reader 属性必须走 mail_reads)。
第一次跑就抓到第三处:`migrate.go` 的 `WHERE m.status = 'read'` ×2 —— 它是**回填**,
职责就是读那个遗留值,所以按文件登记**窄豁免**(allowedCompareExempt,带理由),
而不是"凡是迁移都放行"。
2. **§2 欠账要有"还清即转绿"的判据**:新增 `mail_status_derived_test.go`,三态形状
(与套件探针同族)—— 欠账未清 ⇒ `t.Skip` 并写明还欠什么(**不假红也不假绿**);
还清 ⇒ 开始实跑并断言"详情路径的 status == 按读者派生的值",**转绿即可测事件**;
迁一半(只改 GetMailByID 漏 GetThread)⇒ 红。
实测当前状态:`--- SKIP ... 详情路径返回行级 status="read",而按读者派生说 "unread"`。
**没写成"现在就红"**:恒红的门 = 挂在错误相位的门(CRITERIA.md §11 已为它付过学费)。
另按他的更正把 `migrate.go` 的理由写准:不是"跑过即结束",而是
**"全新安装/空库路径必须存在"**(措辞会影响下一个人敢不敢删它)。
3. **§3 (b) 的语义契约补归属**:归属 = dsh(鸿蒙侧实现者),WebUI 侧不需改动;
落点 = 跨端对齐判据那一族(形状与"预设清单逐项一致"同构,比语义不比数值);
**如实标注当前未建**及原因(鸿蒙侧还没有日历页/手势代码 ⇒ 此时"两端逐项相同"只有一端存在,
建出来就是假判据);到期前提 = P6 第 3 步出现手势代码时立即建,此前是**登记的欠账**。
|
2026-09-14 17:11:36 +08:00 |
|
|
|
12a45af9ee
|
docs(登记): 哨兵的缺省语义进 §10;版本偏移的可见后果进差异表;"指针可以,断言不行"入册
pi 2026-09-14 的三点接续:
1. **"不存在的 id 当哨兵"这个手法本身是一次缺省语义决策**,我原先只把它当技巧用,
没登记它的前提。已按他给的形状进 §10:**未知预设 id → 静默替换为 aurora | 方向:宽(静默)
| 依据:产品决定(背景不该因未知 id 变空白)**。并写明它**不是"给一个安全的空值",
而是"静默给别人的档"** —— 用户不会看到错误,但会看到别人的档。
他指出的真实后果也记了(§10.2 + 计划文档差异表):**服务端/WebUI 先加第 7 档、
鸿蒙还是旧构建时,旧端静默显示 aurora**,用户以为选的是新档;
**这不是 bug,是批准过的缺省决策的可见后果** —— 不记,将来必被当 bug 报。
他给出的可选取舍(兜底改成"不属于六档的显式层")我**不改**:代价是未知 id 显示空白,
与"背景不该变空白"这条产品决定冲突。取舍与理由都写下来了。
顺带记清一个连带事实:**用真实档当兜底,必然让那一档逃出行为判据**
(aurora 丢分支不可观测)—— 这条边界已在判据注释里,不假装全覆盖。
2. **WebUI 侧不写"鸿蒙不消费"**(他否掉了我问的那件事):那会把**另一端的实现状态**
写成**这一端的断言**,而它会过期。规则立成 §13:**注释里可以放指针,不要放断言**;
断言留在"事实所属的那一端"(鸿蒙侧已写+已登记 §10),另一侧只放指针。
与"按 id 联接不按相邻配对"、"'与 X 一致'先确认 X 侧有判据"同族。
3. **两处"只写不读"登记到同一处**:鸿蒙 `bgBlur` 与 WebUI `LEGACY_BACKUP_KEY` 都进 §10 表
(§10.1 说明为什么必须同处:分居两处时审计容易只找到一处就以为找全了)。
并**如实标注**:他建议的判据形态("登记为只写不读的字段,写侧必须能指出理由与批准人")
目前只是**文档级约束**,还没有机器判据 —— 要变成"做错会红"得先有一张可扫的字段清册。
|
2026-09-14 17:09:26 +08:00 |
|
|
|
b7f624bc45
|
fix(已读/手势): 读侧也拒绝空读者;mails.status 读者/写者登记成"新增即红";阈值契约显式选 (b)
pi 2026-09-14 的三条裁定,逐条落地。
1. **§2 读侧守卫**(他只钉了写侧,读侧是另一半,而且更隐蔽):`reader` 在查询里是**过滤条件**,
空串不写坏数据也不报错,只会**算出一个错误的数** —— `unreadFor('')` 的
`NOT EXISTS(... reader_name = '')` 恒真 ⇒ `CountUnread(ctx,"")` 把**所有**邮件算成未读
(用户看到"全都没读"),`ListInbox(...,"read")` 恒空。
二选一里选 **①报错**(当前没有任何调用方需要"汇总"语义;选②就要立刻定义"汇总"是什么,
而那是个还不存在的需求 —— 将来要就新增一个名字里带汇总的函数,别让空串偷偷兼职)。
`requireReader` 装到 `ListInboxScoped`/`CountUnread`/`CountUnreadInSession`,
判据两条:空 reader 三个入口都**必须报错**且**不返回数**;正例防止写成"一律拒绝"。
变异验证:撤掉 `CountUnread` 的守卫 → `CountUnread("") 必须报错,实际返回 0`。
2. **§1 `mails.status` 收口**:先做他要求的第 1 步(枚举读者,他没有 shell)。
枚举结果:**没有**"零功能读者"那条路 —— 行级值仍会进邮件 JSON(`GetMailByID`、
`GetThread` 都 select 它),`migrate.go` 的两处是**一次性回填**(正当),
`unreadFor`/`readStateFor` 只用它判 `archived`(正当,且已注明"不再作为未读判据")。
所以走第三步:**登记欠账 + 增量判据**。
- 「只为兼容保留」**没有**当结语:`mail_status_readers_test.go` 是一张**机器检查的清册**,
按**文件 + 次数**登记(repo.go 13 / thread.go 1 / migrate.go 4),
**新增一处读取就红**并强制当场回答"这处合不合规";写入点同样登记
(多一处 `UPDATE mails SET status` 就红)。
- **第一次跑就抓到第二处写入**:`MarkAllInboxReadForSession`(整会话批量已读)里还有一句
`UPDATE mails SET status='read'`,我原先只看到 `MarkMailRead` 那一处 ——
这正是"新增即红"的价值:靠人 grep 会漏,靠判据不会。
- 收口路径(写进判据注释):行级 `'read'` 迁到 `markReadFor`;
但在"详情/线程仍返回行级 status"两处读者迁移**之前**不能只删写入
—— 那会让 JSON 里的 status 永远是 unread,是另一种错误事实。
变异验证:在已允许的文件里新增一处读取 → 清册对不上,红。
3. **§3 阈值契约显式选 (b)**:不要求数值一致(`40px`/`600ms` 在触摸屏与鼠标、手机与大屏上
的人体工学合理值天然不同,强行同值会两边都不舒服),契约改钉**语义层**(左滑/右滑=什么、
边界是否回弹、"快滑"是感知档)。并写清推论:选了 (b) ⇒ **鸿蒙不得引用 WebUI 的三个数**
(引了就等于偷偷选了 (a),还是"我抄的那一份"那种)。
|
2026-09-14 17:08:31 +08:00 |
|
|
|
5e74e0312e
|
test(预设): 补行为判据 —— "清单里有、渲染器没有"必须红(值判据只挡住一半)
pi 2026-09-14 指出:我那条"两端预设清单 id/顺序逐项相等"只挡得住**一半** ——
它挡"清单漏一档",挡不住"清单有、渲染器没有"(第 7 档两边都加、忘了给
`layersFor` 加分支 ⇒ 值判据全绿,用户看到的却是 aurora)。症状与"信息对等缺口"
一模一样,只是路径换了。核对:他说得对,`layersFor` 的兜底就是 `aurora`,
所以丢分支是**静默**的。
新增 `test/harmony-presets.test.mjs`(4 条,`--experimental-strip-types` 直接执行
纯逻辑 `model/Wallpaper.ts` —— 与 `harmony-logic` 同一先例,**能真跑**):
1. 六档 × 两主题:不抛异常、非空、每层 kind/色标/网格字段有效;
2. ★ **清单里有、渲染器没有 → 红**:手法是拿一个不存在的 id 调一次当作"兜底长相",
任何一档丢了分支就会与兜底逐字节相同 ⇒ 红。**并写明已知边界**:`aurora` 自己
就是兜底档,它"丢分支"在行为上不可观测(由值判据 + 第 3 条兜住);
3. 六档两两不同(复制分支没改参数 → 红);
4. 深色档必须与浅色档不同("深色没换色" → 红)。
**不需要设备 ⇒ 按 pi 的提醒不登记进 `static` 欠账**(那会虚增余额)。
两条变异都验过:删 `mesh` 分支 → 行为判据报"画出来的和兜底档一模一样";
清单加第 7 档不加分支 → **值判据与行为判据同时红**(两条一起才闭合)。
安装相位 14/14 绿。
另按 pi 要求处理两处登记:
· §7.12 那行"px ↔ 档位"改成**核实后的真相**:`bg_blur` 在鸿蒙侧只有搬运没有消费
(`Appearance.ts` clamp 存下、`AppearanceStore` 同步,**没有任何页面读它**),
所以不存在映射表可钉 —— 钉一张不存在的表是假判据;并写明"若将来开始消费它,
那时必须补映射判据",作为"人工约定、当前无判据"的登记处。
· 预设 id/顺序那条**标清钉的是产品要求**(顺序=用户看到的选项次序),理由写进判据注释。
|
2026-09-14 17:05:15 +08:00 |
|
|
|
863b583838
|
fix(已读/文档): 空 reader 报错(把约定变成做错会红);P6 方案补分支声明与可验收性;"与 X 一致"入册
pi 2026-09-14 对 P6 方案的五条,逐条处理(都不改方向)。
1. **§4 已读按读者的强制点** —— 他说得对,但实际情况比他担心的更靠前也更靠后:
HTTP 层取的是 `user.Username`(**不是请求参数**,所以根本不可能"省略 reader"),
但 **repo 函数本身接受空串**:`MarkMailRead(ctx, id, reader)` 会照写一行
`reader_name=''` —— 不属于任何人,却会让"未读"统计出偏差,而且没有任何东西会红。
已加守卫(空/纯空白 → 报错,不兜底)+判据(不仅"不许插垃圾行",且**必须返回错误**;
另含正例,防止把守卫写成"一律拒绝")。变异验证:撤掉守卫 → `空 reader("")必须报错`。
顺带一条给他的更正:同一个函数结尾还有 `UPDATE mails SET status='read'`(**行级**全局写),
所以"按读者"这个性质只对**用 `mail_reads` 派生的数据**成立(`CountUnread`/`ListInbox` 是),
读 `mails.status` 的客户端仍然是邮件级语义 —— 两件事在同一个函数里,容易被看漏。
2. **§2 手势阈值**:核实结果 —— **WebUI 侧没有被任何判据钉住**(`CalendarView.tsx:204`
裸字面量 `Math.abs(dx) < 40 || Math.abs(dx) < Math.abs(dy) * 1.5 || !fast(600ms)`)。
所以撤掉"与 WebUI 一致"的写法,改标 **「待两边对齐」**(文档两处),并把他给的推广写成
规范 `CRITERIA.md` §12:**凡"与 X 一致"的判据,前置条件是 X 侧那个值自己有判据钉住**,
否则测的是"我抄的那一份" —— 与"两张表各缺一半时必须按 id 联接"同一族。
3. **§1 分支声明(最要紧的一条)**:写进 P6 分期段 —— 本步实现的是**窄屏那条**
(**容器自身带圆角 + 那一层能裁剪**);宽屏那条(起始侧/结束侧分开给)**不适用**,
因为它的理由是"中间是分隔线、四角全给会露底色",而手机是单栏、中间没有分隔线。
并写死这句:**"给对边"是跟着"中间有没有分隔线"来的,不是无条件的三件套。**
同时核实并写清现状:鸿蒙侧**还没有日历页**(全 ets 树无任何 calendar 提及)⇒ P6 是整页新建。
4. **§3 可验收性**:P6 三步各加一列 —— 第 1、2 步**本工作区可验收**(读 `.ets` 层结构),
并明确"**第 2 步不需要设备,不许登记成 `static` 欠账**"(那会虚增余额);第 3 步
**必然进欠账**(要设备:能装、能点),到期前提见探针表。
5. **§5 路由**:不再把 WebUI 改动挂成"等 pi"(他这条链上没有 shell)。按他给的三级路由执行:
优先在鸿蒙侧引用**已有令牌**解决;必须动 WebUI 时找 gui-lab 或按先例自己改。
|
2026-09-14 17:03:16 +08:00 |
|
|
|
05e9583fe5
|
test(预设): 钉住两端预设清单的 id 与顺序;更正"模糊只由壁纸层负责"这条被撤回的口径
pi 2026-09-14 的三处答复,逐条处理:
1. **§1 预设渲染(他担心的信息对等缺口)实际不存在**:鸿蒙侧 `model/Wallpaper.ts` 已有
`PRESET_IDS = ['aurora','dusk','mint','sand','ink','mesh']` + 标签 + 归一化 + `layersFor`,
计划文档 §P4 行也早写着"预设 6 档都能画出来 ✅"。所以 P4c(上传入口)不必让位。
**但缺口在判据上**:原先只钉了"服务端默认预设要在鸿蒙清单里"与"预设色板的值两边相同",
**id 集合与顺序没钉**。新增判据:`PRESET_IDS` 必须与 WebUI `PRESETS` 的 id **与顺序**逐项一致
(顺序也是契约:顺序不同会让两端的选择界面看起来"选错了")。变异验证:把 aurora/dusk 对调 → 红。
2. **§3 模糊口径**:他说判据"现在是后者"(只有导航必须有材质)—— 实际**两条都在**
(`harmony-appearance.test.mjs`:壁纸层不许有 `backgroundBlurStyle`、也不许有任何 `blur(`;
导航条必须有 `.backgroundBlurStyle(Theme.navMaterial)`)。所以这里不需要改代码。
需要改的是**文档里的规则原文**:`docs/HARMONY-ALIGN-PLAN.md` 第 9 行那句
"模糊只由壁纸层负责"是**被撤回的原话**(它其实是 WebUI 的架构结论:WebUI 壁纸自带
`filter: blur()`,所以浮在它上面的面再 `backdrop-filter` 就是糊第二遍),已换成两段式规则
"同一张底只许被模糊一次 / 模糊该出现在背后是可变内容的层",并把撤回本身留痕。
3. **§3 补的第二件(语义转换)**:§7.12 的表里新增一行 —— WebUI 的 `bg_blur` 是**像素半径**、
作用在壁纸图层;鸿蒙**没有对应物**,栏上的系统材质档位是唯一一次模糊,壁纸层不糊。
写清"不是同一个物理量,`bg_blur=8px` 与档位对不上不是 bug",否则以后必被当 bug 报。
|
2026-09-14 16:59:16 +08:00 |
|
|
|
ec5ee5eb9b
|
docs(mirror): 补一条实测 —— 上游匿名可完整克隆,「凭据过期」不是镜像掉队的原因
GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=/bin/true git -c credential.helper= clone --depth 1
https://gitea.jianfgit.xyz/jianf/MailUI4Agents.git → 成功,HEAD 就是最新提交。
这条当初是最像的一个假设(镜像项目一般靠存起来的凭据去拉上游),实测排除。
|
2026-09-14 16:11:00 +08:00 |
|
|
|
0309d09ac2
|
docs: gitcode 镜像仓为什么不会自己更新(附实测证据与触发方式)
用户(2026-09-14):「git@gitcode.com:JianFeeeee/MailUI4Agents.git 这个镜像仓
怎么也没更新?」
查下来三件事,写进文档免得下一个人再查一遍:
① 它是 gitcode 的**导入/镜像项目**,上游就是我们自己的 Gitea
(`import_url=https://gitea.jianfgit.xyz/jianf/MailUI4Agents.git`,
从项目页的 Nuxt payload 里读出来的)。
② **推不进去**,两条路都是服务端拒绝:
SSH `<CH.00905401> This operation is not allowed because the repository is an
image repository.`
HTTPS 403 `<CH.00905403>`(同样的措辞)。
「image repository」是它那边的「镜像项目」类型,不是容器镜像;语义是**单向**的。
③ **没有自动同步,也没有 API**:那个页面自己写着「对于滞后的提交,你可以通过
拉取更新从源项目获取最新的代码」—— 是手动一键操作。6 种猜测的 API 端点全部 404。
我们这边实测是好的:上游公网可达(直连 101.201.37.155 拿到 info/refs,HEAD
就是 d78f19f)、公网 DNS 有记录、匿名可克隆。镜像停在 c19eea5(09-12),落后 114 个提交,
纯粹是那边没人点「拉取」。
本机无法代点:动作要以仓库所有者身份登录,而共享浏览器 profile 里只有 gitcode 的
匿名 Cookie,没有登录态。文档里给了网页入口和「想自动化就只能换成普通项目由我们推」
这条替代路(会改 URL,属对外契约变更,要先问)。
|
2026-09-14 16:10:24 +08:00 |
|
|
|
456ae5a66d
|
跨端: pi 五条评审落地 —— 产物自证替代时间戳代理、静态判据可到期、判据能读一层标识符、值/来源配对规则、归因不许动别人的树
pi 2026-09-14 的评审(`9839f8a9`)五条,逐条落地;其中 §6 的两条是**核对后已成立**,不重复劳动。
## 1 产物自证:`dist/BUILD_INFO.json`(pi §1)
原来那条判据是"`dist` 比 `src` 新"——**代理变量**,pi 指出两层都靠不住:看不见"构建是否
成功"(实测过),而"`dist` 比 `src` 新"也不等于"dist 是从这份 src 构建的"(`checkout`/`cp`/时钟
都骗得过 mtime;我确实用 checkout 造过一次假红)。现在改成**内容自证**:
`scripts/build-info.mjs` 在构建最后一步写 `{gitRev, gitDirty, srcHash, srcFiles, buildCmd, builtAt}`,
判据重算当前指纹再**精确比对**(`test/build-stamp.test.mjs`)——"代理"两个字没有了,
报错能直接读出两边指纹。`release-linux.sh` 打完包把同一份信息打进日志(一个包自带
"它对应哪个源码状态")。`gitDirty` **只展示不判定**:共享工作区常年是脏的,拿它当红/绿依据
会天天误报。
实测三变异:改 src 内容不重建 → 红(两个指纹都打出来);BUILD_INFO 记成别的提交 → 红;
**`touch`(只动 mtime)→ 绿** —— 旧判据在这里是**假红**,新判据不误伤,这是它严格更好的地方。
## 2 静态判据的**欠账**与到期(pi §5)
"暂时"不是状态、是待办,规范里写下的"暂时"没有任何机制回来读它。改成可机检的形状:
`run-all.mjs` 登记 `STATIC_ONLY`(5 条:`.ets` 只能验形态)+ **必填到期前提**(探针,真跑
`hdc list targets`);汇总打 `RESULT static=N`(**欠账余额**);**前提一旦为真,这些判据当场
变红**并要求"改成行为判据或换更准的前提"。实测:探针恒真 → 5 条同时报"到期";把前提写成
`'vibes'`(未知探针/陈述)→ 套件红。这是"自报条数 < 登记条数"的**时间版本**。
## 3 判据能解析一层标识符(pi §3)
Go 默认值判据原来"值不是字面量 → 判据读不懂 → 红",长期结局是有人做一次无害重构
(`BgDim: defaultDim`)就把判据逼宽、再下一步少核一个字段。现在:字面量直接用;
标识符在**同一文件**查 `NAME = <字面量>`;查不到(跨包/计算/iota)才报"读不懂"。
实测:`BgDim: defaultDim` + `const defaultDim = 12` → **绿**(无害重构不再误伤);
`const defaultDim = baseDim + 0` → 红且报文说"读不懂"。**只解析一层**:再深就是"执行 Go"了。
## 4 §6.7 的可机检分流规则(pi §2)
新增 §6.7.0:**值 → 行为判据,来源 → 静态判据**,理由写成覆盖问题(行为判据只覆盖它跑到的
路径 ⇒ 原理上判不了"有没有别的路绕过去";来源约束要的是全程序可达性 ⇒ 只有读代码能答)。
两条推论:静态判据判值永远差一个反例、行为判据判来源永远差一条路径;**不是强弱,是分工**,
缺任一条那一对就是假判据。附本仓已有的完整样例(P5 命中区:值判据"≥44"+ 来源判据
"应用点必须引用常量"),新增 §6.8 记欠账机制、§8 记共享树归因纪律。
## 5 值/来源配对补齐 + 让位派生(pi §6 的两条)
- 命中区原来只有值那一半:补**来源**判据(`.ets` 里出现 `minHeight: 44` 这类裸数字 → 红,
报文点出"值判据管数字够不够大、来源判据管用的是不是同一个数字")。变异:写死 44 → 红。
- "`76` 应从条高派生":**核对后已成立**(`NAV_CONTENT_RESERVE = NAV_BAR_HEIGHT +
NAV_BAR_BOTTOM + 8`,判据也按常量算)。顺手把裸的 `8` 起名 `NAV_CONTENT_GAP`
(这一族里唯一还需要人判断的数),并加判据钉住**派生关系**:写回字面量 `76` → 红。
## 6 `legacy.bak` 的生命周期(pi §4):**有意永久残留**,并说清代价
pi 质疑成立:判据钉死"没有任何代码读它"⇒ 也没有任何代码能删它。**否决了"点重置外观时删"**:
最可能点重置的人正是外观被接管的那个人,而这份备份是他唯一的旧值,那时删等于把恢复数据
毁在最需要的时刻。所以定为"有意永久残留"(没有代码路径能判定何时安全删除——那取决于人),
并把代价量出来写进 §7.12:值受 `MAX_DATA_URL_BYTES`(2.4MB)约束,**最坏是一张用户原图的
整份副本**,共享机器上即一份别人的外观;要清就手工 `removeItem`。判据钉住这行**同时**给出
"为什么不删"与"怎么删"(变异:删掉删除方法 → 红)。
## 7 归因方法(pi §6 第三条):认错并写成纪律
我当天归因 `narrow-layout` 的 3 条红时用了 `git stash push -- MailView.tsx`,动的是**并发写
作者的未提交改动**。结论对、方法不行:它写共享工作区(别人崩溃/`git add -A` 就丢他的活),
而且只影响 tracked 文件(untracked 的 WIP 还在 ⇒ "干净了"是假干净,结论也可能假)。
纪律写进 CRITERIA.md §8:只读手段(`git show HEAD:path > /tmp/...`、`git worktree add`)或直接问作者。
## 验证
`npm test` 全绿(14 个判据文件 + vitest 266 + typecheck),`RESULT static=5`;
`hvigorw assembleHap` BUILD SUCCESSFUL;`scripts/release-linux.sh` 重打 deb(日志带产物指纹)。
提交前两道**真红**按预期挡了我:改了 src 未重建 → build-stamp 红;重建了 dist 未重打包 →
packaging 红。**未验**(照旧不写成已完成):鸿蒙侧视觉/交互观感、运行期换肤重算(静态判据,
到期前提见 `RESULT static=5`)。
注:本次 `dist`/deb 是在**共享工作区**上构建的,树里含并发写作者未提交的
`CalendarView.tsx`/`index.css` —— 产物里的 `gitDirty: true` + `gitRev` 正是为此留的,
复核时先看那一行。
|
2026-09-14 16:00:34 +08:00 |
|
|
|
71a3c35610
|
按 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 与包同批)。
|
2026-09-14 15:41:52 +08:00 |
|
|
|
ccd0a39e76
|
docs(P5): 悬浮玻璃导航落地记录 + 视觉验收的**实测**阻塞(不是"还没做")
- §5.4 的 P5 行由「⬜ 未做」改为「✅ 主体完成」,并写明**未验项与原因**。
- 新增 §7.21:改了什么(NavItems.ts 放尺寸常量让判据判**数值**;内容按 index 挂载;
内容底部让位 76vp —— 不让位则最后一行压在条底下,看得见点不到)、判据 6 条、
4 种变异、以及**判据自己修的两处**(非贪婪正则撞对象字面量的 `}`;色值检查读了含注释的原文)。
- §7.21 末节把"观感验不了"逐层测清楚:①模拟器要写工作区外的
/root/.Huawei/…/Log/qemu.log,本会话 workspace-write 判 Permission denied;
②放宽权限需审批而**这条链上没有人类**,工具明确拒绝;③不需要权限的替代路
(HOME 挪进工作区,复制实例 + 符号链接镜像,2.7GB)过了写入这关却卡在**许可协议**,
重放 y 等于替用户同意法律协议 —— 不做,已停止模拟器并删除暂存副本。
→ 要解除需人二选一:放行工作区外写入,或由人在本机起模拟器后把画面交回。
验证:hvigorw assembleHap BUILD SUCCESSFUL;npm test 14 个判据文件里 13 绿
(唯一红的 narrow-layout 由并发写作者的未提交 MailView.tsx 改动引起 —— 用 git stash 验过:
剥掉那个改动即 52 通过,与 P5 无关,未动他人的文件)。
|
2026-09-14 15:35:48 +08:00 |
|
|
|
dbdb2f5039
|
跨端: 修打包坑(构建失败被管道吞掉)+ dist 新鲜度判据;判据从"窗口式"改成同表达式
接手 pi 的 WebUI 开项途中踩到三个坑,都已修好并配判据(承接 e94313f)。
## 坑 1:`npm run build 2>&1 | tail -4 && electron-builder …` 吞掉构建失败
pipeline 的退出码是 `tail` 的 → 构建失败、`&&` 照样往下走、electron-builder
**拿旧的 dist 打了新包**。而所有判据都是绿的:
- vitest 绿(见坑 2);packaging 绿(它比 dist vs 安装包,两边都是旧的,自然一致)。
新增判据:**dist 必须比 src 新**(build-stamp)。变异:`touch` 一个 src 文件 → 红;
重新 `npm run build` → 绿。错误信息写明"注意别把它的退出码丢在管道里"。
已重构建 + 重打包(deb 与 app.asar 同批,15:19)。
## 坑 2:测试通过 ≠ 能打包
真正的失败:`AGGREGATE_ID` / `isUsableAccount` 被我当成 `accountStore` 的导出
(它们住在 `lib/accounts.ts`)。vitest 263 条全绿,生产构建直接报
`"AGGREGATE_ID" is not exported by "src/stores/accountStore.ts"` ——
测试运行时对缺的具名导出是宽容的(拿到 undefined)。**生产构建是一道独立的门。**
## 坑 3:判据写成"窗口式",被自己的变异测试抓住两次
`appearance-defaults` 的"取键函数必须把账号拼进去":
1. 第一版从 `export function` 切到 `}` → **参数表里的 accountId 满足了正则**,
把实现退回全局键仍然全绿(假判据!);
2. 第二版只取函数体 → `return accountId ? PREFIX : PREFIX;`(提了一下没用)又骗过去;
3. 第三版要求**同一个表达式里既有常量又有账号的插值/拼接**:
两种退化都红,合法的 `PREFIX + accountId` 写法仍然绿。
三种变异都验过(红/红/绿),还原后基线绿。
## 文档
§7.12 两行改为「已修」并写明依据(默认值=服务端契约;缓存键差异已消除,旧全局键
只作一次性迁移源);新增 §7.20 记这三个坑与推论(产物是 gitignore 的,
"源码修好"≠"用户手上那个包修好")。
## 验证
`npm test` 退出码 0(13 个判据文件全绿 + vitest 263 passed);
`hvigorw assembleHap` BUILD SUCCESSFUL;`npm run build` + electron-builder 均 exit 0。
|
2026-09-14 15:20:30 +08:00 |
|
|
|
d1e0ba32f9
|
跨端: 壁纸遮盖色改用页面底色系(mask 两套主题都深,浅色下方向与 WebUI 相反);模态遮罩保持 mask
pi 提了一个**方向性**疑问,让我先把值读出来再定 —— 读完确认**他是对的**,而且这是"机制上确定不同"。
## 实测(两张 SDK 表交叉验证,不靠记忆)
`ets/build-tools/ets-loader/sysResource.js` 给名字→id,`previewer/.../resources.txt` 给 id→值:
| 令牌 | 浅色主题 | 深色主题 |
|---|---|---|
| `ohos_id_color_mask_regular`(原 `Theme.overlay`) | `#99182431` **深蓝灰** | `#b2000000` 黑 |
| `ohos_id_color_background` | `#ffffffff` **白** | `#ff18181a` 近黑 |
⇒ mask 的 light/regular/thick 是**浓度档**(同一深色的三个 alpha),不是深浅两套值:
它在**两套主题下都是深色**(模态遮罩语义)。而 WebUI 的 `--bg-scrim` 浅色是**白**
(原话「浅色下用白把花哨的图案洗淡」)—— 壁纸遮盖用 mask 就是**反方向**:
浅色主题下把预设压暗,而 WebUI 是把它洗淡。
## 改法
- 新增 `Theme.wallpaperScrim = $r('sys.color.ohos_id_color_background')`(页面底色系:
浅色白、深色近黑,**自动换向**,与"朝底色淡化"同一意图);预设档与图片档的遮盖层都用它。
- `Theme.overlay`(mask)**只留给模态弹层**(那里语义确实是压暗背后)。
- 不复用 `pageBg` 的原因写进注释:页面底在背景开启时会被换成**透明**
(`bgActive ? Color.Transparent : Theme.pageBg`),而遮盖层**永远要一个真实颜色** ——
两个用途生命周期不同,共用一个名字迟早坏一头(这个仓库撞过四次的模式)。
## 判据(从 SDK 读真值再判,不写死结论)
断言「mask 两套主题都深」「页面底色系浅白深黑」「WebUI 的 `--bg-scrim` 浅色是白」,
再落到代码:两处遮盖层必须用 `wallpaperScrim`、弹层仍用 `overlay`、两个令牌不许混用。
**这样"令牌选错方向"以后不能靠记性避免。**
变异:遮盖退回 mask → 红 1 条;只改一层 → 红 2 条。
## 文档
§7.17b-2 记这次读数(表格 + 两张表怎么读 + 为什么不复用 pageBg + 变异结果);
§7.12 有意差异表加一行(遮盖色令牌:两个语义两个令牌,"不允许混用")。
## 验证
`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(12 个判据文件全绿 + vitest 258/258;harmony-appearance 23 → 24 条)。
|
2026-09-14 15:10:15 +08:00 |
|