|
|
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 |
|
|
|
c274dad462
|
fix(判据): 读取欠账登记改用 prose(判据目录不得出现裸 readFileSync)
我自己刚踩的:往 run-all.mjs 里写余额时用了 readFileSync,被判据目录的读取纪律抓住。
JSON 属于数据 → 按既有约定走 prose。
|
2026-09-14 17:18:07 +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 |
|
|
|
f5c4f56682
|
跨端: feat(预设): 静默兜底留一条可观测痕迹;"只写不读"在鸿蒙侧也补上判据(照 LEGACY_BACKUP_KEY 照搬);§10 出处落地
pi 2026-09-14 的三小条。
1. **§3 静默兜底要留可观测痕迹**:`normalizePreset` 把认不出的 id 换成 aurora 这件事,
原先在真实环境里**不留任何痕迹** —— §10 的登记只防"被误报成 bug",防不住
"没人知道它正在发生"。现在 `BackgroundPlan.presetSubstitutedFrom` 带出**原来那个 id**
(换过非空、没换过空串),页面据此打一行日志 ⇒ 后果从"可能发生"变成"**可数**"。
**痕迹记在返回值里而不是在这一层直接打日志**,理由写进代码:这一层是**纯逻辑**
(无 `@ohos` 依赖 ⇒ 判据能用 node strip-types 直接跑它);为打一行日志引入 `@ohos.hilog`,
等于把"能真跑的行为判据"换成"只能读源码的形态判据"——不划算的交易。
**判据两条方向都钉**:替换必须留痕(且带出原 id);**没替换时必须为空**
(痕迹退化成噪声就等于没有)。变异与正反例都在(5 条全绿)。
2. **§2 §10 的"谁批准"落成出处**:那一格原写"产品决定" —— 按 pi 的话这是**事后追认**。
现在写的是 **「无人类批准:这是实现时的默认行为(随 `model/Wallpaper.ts` 引入,
`git log --diff-filter=A` 可查出处,2e42aac),本行是补登记」**,并把**意图**与**批准**分开写
(不拿意图冒充批准)。另两行也补了出处(`Appearance.ts:39,52,88,104,133` /
`AppearanceStore.ets:142-195` / `backgroundStore.ts:200`)。
3. **§1 "可扫的字段清册"不必先造 —— 同形状已有一边是判据**:他说得对。
WebUI 的 `LEGACY_BACKUP_KEY` 早就钉着"只写不读",鸿蒙的 `bgBlur` 只有文档
⇒ 差的是**同一个形状只有一边有判据**。已照搬:`harmony-appearance.test.mjs` 新增
**"消费侧出现次数必须为 0"**;豁免**按文件登记 + 写理由**(域模型 / 状态同步 / 线上 DTO
三处是搬运与传输,不是消费)——与 `mail_status_readers_test.go` 的豁免同一形状,
登记表本身就是清册,不必另造一张。
变异验证:让 MainPage 读一次 `bgBlur` → 判据红,红的信息写着"**停下:那时必须先补
px ↔ 材质档位的映射判据**,而不是把登记值从 0 改成 1"。
SUITE 计数同步:harmony-presets 4→5、harmony-appearance 24→25。
|
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 |
|
|
|
c094a1b6e2
|
fix(判据): 汇总报本相位**实际跑了**几条,不报清单长度
install 相位会跳过 2 条构建相位判据,而汇总原先打 SUITE.length 并把全部文件名都列出来 ——
那是在一行里声称"15 条全过",实际只跑了 13 条。同一行里犯的正是这轮一直在修的错
(看起来健康的过度声明)。现在打实际跑了多少条,并注明另有几条属构建相位、本相位不判。
|
2026-09-14 16:55:39 +08:00 |
|
|
|
33488760ce
|
fix(相位/安全): 部署门禁只判产物自证;静默 break 改成出声;内核读数带时间坐标
pi 2026-09-14 的裁定与两条更正,逐条落地。
1. **相位裁定(选 c)**:`packaging`/`build-stamp` 属于**构建相位**,不属于安装相位。
`run-all.mjs` 现在有相位:`AGENTMAIL_CRITERIA_PHASE=install`(部署门禁用)。
每条判据登记它读的哪一侧(`ARTIFACT`/`SOURCE`),install 相位里出现 SOURCE 侧判据 → 红;
被跳过的判据**点名打印**,不静默丢。汇总打 `RESULT phase=build|install`。
规则入册 `test/CRITERIA.md` §11(含三个真实实例:check-shared-libs 恒红、
packaging 一改前端就卡死、HOME 在门禁跑完之后才炸)。
安装相位**真正能判的那一半**:`deploy/install.sh` 读**产物自证**(不重算 dist)——
`releaseCandidate !== true` → 拒绝;产物 `gitRev` ≠ HEAD → "这个包比源码旧" → 拒绝;
放行要显式 `--allow-dirty` / `--allow-stale`;`--check` 干跑只报结论不拦。
实测干跑输出:`产物:gitRev=6702cc2 树=dirty releaseCandidate=false | 当前 HEAD=6702cc2`
→ 报"不是发布候选 + 正式安装会被拒绝 + 要放行请显式说清"。
2. **别解析运行器文本**(pi §5):`broken`/`red` 的判定改成按 TAP 的**名字**——
文件级失败的测试名就是路径,断言失败的名字是判据名。变异双向验证:
未定义标识符 → 「跑不起来的判据」;把某条判据条件改成假 → 「红的判据」。
不再往关键字表里加补丁(那是往文本解析里加补丁,方向是错的)。
3. **静默 break 是安全相关**(pi §3):`session_update` 找不到活动会话时不再静默 break,
改成出声日志(走 journalctl 那条通道),写清两种成因(此刻没在跑 / **接管会话**重启后无法定位)、
方向(收紧被延迟)、以及兜底的**前提**("下次投递"要求这条会话还会收到新邮件)。
`lib/mail-session-id.js` 模块头同步改成安全相关措辞("人以为自己收紧了权限、实际没有"),
四桥逐字节同源,`check-shared-libs.sh` 退出码 0。
4. 内核读数补时间坐标(pi 13ea2fdf):`BUILD_INFO.txt` 里除原始 `dep`/`=>` 行外,
现在还有 `kernelBinMtime` 与**正在运行的进程启动时间** —— 二进制会在两次读数之间被换掉,
没有时间坐标的读数不成立。
|
2026-09-14 16:54:30 +08:00 |
|
|
|
6702cc2f5e
|
fix(webui): 窄屏日历/详情的圆角——圆角要给到「能裁剪的那一层」
用户(2026-09-14):「我这边看还是方角」。上一轮我只修了宽屏那条分支,
窄屏(手机/平板,<1024px)当时仍然是方角 —— 这次按像素量到了。
## 实测(真浏览器读渲染像素,不是读声明)
390x844 日历页左上角:
改前 (255,255,255) 纯白 → 方角;面板 computed radius = 0px
改后 (249,250,251) 页面底色 → 圆角;上边缘内缩 8px
## 根因:圆角落在**裁不住东西**的那层上
窄屏走 NarrowStack(`list`+`main` 的覆盖式容器,日历与收件箱详情都用它):
.narrow-shell > * ← 14px 加在这里(App.tsx 的 flex-1 min-h-0 flex)
NarrowStack 根 ← overflow:hidden,但 radius 0px
absolute inset-0 flex ← 透明
gridPane (bg-white) ← 0px,直角原样露出来
`overflow: visible` 的透明 flex 容器拿到圆角 = 什么也没发生。
NarrowStack 的根节点是**唯一**同时具备「自己 overflow:hidden」与「包住有底色那一层」
的层,圆角必须加在它上面 —— 加给它,底层页与滑入的覆盖层就一起被裁到圆角里。
顺带修好一处同类问题:**收件箱详情在窄屏也是方角**(同一个容器),
以前没人报过,这次一起圆了(实测同上)。
## 判据
narrow-layout 新增 2 条,分开钉两件事(分开一条都不算修好):
① 那层自己带上面板圆角(类名与 CSS 规则接得上);
② 那一层同时具备裁剪能力(overflow:hidden)。
三组变异全部红在对的那条上(去掉类名 / 去掉 border-radius / 去掉 overflow-hidden)。
narrow-verify(手动探针)新增**读像素**的能力与两条判据 —— 这件事只有像素能回答:
computed radius 是 14px、角上 2px 处"谁在画"也指着面板本身,**但渲染出来仍是方的**
(圆角加在没底色的层上,被内层直角原地盖掉)。所以这里截 1x1 再解 PNG,
只用 zlib,不引图像库。方角=255,255,255 / 圆角=249,250,251,两个数字就能分辨。
|
2026-09-14 16:51:31 +08:00 |
|
|
|
dae508b25b
|
docs(判据): 补「已知限制」与「缺省语义登记」两节;build.sh 把原始 dep/=> 行写进 BUILD_INFO
pi 2026-09-14 两件:
1. **登记册真的没有**。我上封信说"已写进 test/CRITERIA.md 的已知限制一节"——**不成立**,
只有 `appearance-defaults.test.mjs` 里有那段注释。已在 CRITERIA.md 补 §9「已知限制」
(标识符只解析一层;静态判据的到期前提是"本工作区能装能点")。
过度声明自己做过什么是这轮反复出现的那一类错,这次是同一个形状的又一例。
2. **新建 §10「缺省语义登记处」**(pi 的更正:不是无条件 fail-closed,而是"缺了的后果必须
有人登记 + 写明谁批准了这个方向")。三条入库,各带依据:
· 邮件 `permission_mode` 缺 → **不写、不改档**(窄),依据是本轮那个 `|| 'workspace'` 兜窄档的坑;
· HomeAgent `plugin.json.sdk` 缺 → 内核**不读**(不是语义,是文档),依据内核 manifest.go 结构体 + registry.go;
· HomeAgent `capabilities` 缺 → **不受限**(宽),依据内核注释明写的理由:17 个存量清单都没有它。
3. `build/BUILD_INFO.txt` 现在**原文贴入** `go version -m <内核>` 的输出,并附"这两行怎么读"
(`dep … vX.Y.Z` 后面跟 `=> … (devel)` 时那串版本号只是 require 行残留;`=>` 必须按模块名联接)。
理由:这场争论的全部内容就是这两行该怎么读,原始证据必须和结论放在一起。
|
2026-09-14 16:50:33 +08:00 |
|
|
|
f27ad31c91
|
fix(判据/部署): 探针不再假设清单穷尽;broken≠red;到期报文自带"要放行什么";部署加 --check 干跑
pi 2026-09-14(两封)提的六条,能做的都做了。
1. **`[Empty]` 不是"没有设备",是第三态**(pi §1)。改了,而且不是改成"一律红",
是**去证服务端健康**:探针现在读 `/proc/<pid>/cmdline` 找 `hdc -m`(server 模式),
服务端在 → 空集才是可判的"确实没有目标"(false);服务端找不到 → `unknown`(红)。
本机实测:`hdc -m -s ::ffff:127.0.0.1:8710` 在跑 → 空集可信。
这条用机制而不是用嘴回答"我检查过了"。
2. **硬编码候选清单**(pi §2):候选来源写清(`/opt/huawei/command-line-tools` 是文档安装根),
`hdc` 也走 PATH;**工具链根在、里面却没有 hdc → `unknown`**("装了但长得不一样"不是"没装")。
这与 build.sh 那次"第一个命中就算"是同一形状 —— 今天各咬一次。
3. **前提改成"本工作区能装能点"**(pi §2):原前提"设备存在"会让 5 条判据在**我修不了**的时候同时红
(模拟器要写 /run、/var/log)。现在前提是工作区能力,`need` 逐条写清,
并且**到期报文会把这些门槛打出来** —— 第一次真红不能被当成噪音消掉。
4. **broken ≠ red**(pi §3):跑不起来(语言级崩痕:SyntaxError/ReferenceError/…)单列
"跑不起来的判据(N)—— 不是红,也不算过",红是"判据说不成立",broken 是"判据没说话"。
变异验证:注入未定义标识符 → 报 broken ✓;绿基线 → exit 0 ✓。
第一版我用"输出里有没有 `not ok`"判,当场误判(node:test 把导入期 ReferenceError 也报成 `not ok`),
已改成语言级崩痕 —— 判据自己的第一版就得被现实修一次。
5. **标签要有消费点**(pi §4):`release-linux.sh --release` 遇脏树**拒绝**(`--allow-dirty` 才放行),
不加参数是自用打包(只出声)。"出声≠拒绝"这条说得对。
6. **`deploy/install.sh --check` 干跑**(pi §6):跑全部门禁、不写系统目录,末尾列出正式安装会写什么、
需要哪里的权限。干跑立刻抓到两个真缺陷:
· `set -u` 下 `$HOME` 未设 → `HOME: unbound variable`(cron/env -i/某些 sudo 下就是没有),
而它发生在**所有门禁跑完之后**——最贵的位置(这轮第三次同形状,前两次在 homeagent build.sh)。
· **packaging 这条门在部署路径上永远过不去**:install.sh 先 `npm test`(含 packaging),
而 packaging 要求"安装包里的 dist == 当前 dist",部署路径却不重新打包 →
前端一改,install.sh 就卡在这条门上(第二条"挂在部署路径上却恒红"的门,第一条是 check-shared-libs)。
这条需要决定:部署路径要么重新打包、要么把 packaging 排除在部署门禁外。**我没有擅自改口径。**
|
2026-09-14 16:49:08 +08:00 |
|
|
|
4f0a6e6097
|
fix(homeagent): 构建不许弄脏源码树 —— hmapdev 会重写 plugin.json,构建后还原并出声
实测:`hmapdev build` 会重写受版本管理的 `plugin.json`(只吃掉了文件末尾换行)。
"构建把树弄脏"这件事在 electron 那边刚定过规矩(脏树产物不得自称发布候选),
所以这里同样处理:构建前留快照,构建后若被改写就还原 + 打印一行说明
(不静默还原 —— 下一个人要知道这条工具会动源码)。
验证:`bash build.sh`(HOMEAGENT_SDK_DIR=…/sdk/v1.3.0)产出 build/plugin.bin(9018271 字节),
构建后 `git status plugins/homeagent-mail-bridge/` 只剩我自己改的 build.sh。
|
2026-09-14 16:41:54 +08:00 |
|
|
|
ef82bcdfd3
|
test(权限): 钉住"值没变也广播"—— 已降级会话的恢复路径靠这条否定事实
pi 2026-09-14 §3:这是一个**承重的"没有"**。我给"已降级会话怎么恢复"的答案里,
唯一"今天就能用"的手段是"人在 WebUI 里把同一个档位再选一次"——它之所以有效,
正是因为 `UpdateSessionPermission` **没有** "值没变就提前返回"。
判据是**行为**判据,不是读源码(形状判据挡不住"判断挪到 repo 层/挪到 middleware"):
起真库 + 真 SSE 客户端,连点两次同一个档位,数 `session_update` 帧 ——
第一次必须 ≥1(否则判据自己没接上,Fatal 而不是静默通过),第二次必须更多。
变异验证(先用 `db.DB` 注入 → 报的是 build failed,**不算判据红**,改用已有
`repo.SessionPermissionMode` 重注入):
--- FAIL: TestPermissionUpdateBroadcastsEvenWhenValueUnchanged (0.01s)
撤回后 ok。`go test ./...` 全绿(11 个包)。
|
2026-09-14 16:41:32 +08:00 |
|
|
|
7e696f9a8c
|
fix(homeagent): 撤回"另一条 SDK 血脉"的错误结论;build.sh 不再猜路径;清单判据改成一致性口径
pi 用只读文件系统逐条反驳了 357662e 的根因,三条我都验证并接受:
1. **"内核链 0.9.x 血脉"不成立 —— 那是我的搜索顺序造出来的事实。**
本机有 6+ 份 `third_party/homeagent-sdk` checkout:
/root/ha-test/…(0.9.0,C-ABI 时代,无 plugin.bin 支持)
/var/tmp/rel-1.3.12/…、/var/tmp/rel-1.3.11/…(1.3.0)
/var/tmp/release-main/…、/var/tmp/clean-check/…、/var/tmp/homed-p3/…(1.2.0)
而 build.sh 第一版按候选根目录**第一个命中就算**,命中的正是 ha-test 那份老 checkout。
内核自己用的是 1.3.0 那份,与钉子 `sdk/v1.3.0`、与 `<SDK_ROOT>/current` **一致**。
两条教训写进注释了:别用"第一个存在的路径"当权威来源;别把模块版本字符串当身份
(`replace => local (devel)` 时它只是 require 行的残留)。
2. **"API 不兼容"也是同一个错造成的。** 换成正确的 SDK 之后:
HOMEAGENT_SDK_DIR=/root/.homeagent/hmapdev/sdk/v1.3.0 bash build.sh
→ hmapdev build 成功,产出 build/plugin.bin(9018271 字节)
也就是说**这个插件在本机编得出来**,先前的 `SettingsAPI.DataDir` 报错是拿老 checkout 编的产物。
3. **判据把能工作的配置判红**(pi §2):第 4 步原先硬校验"产物 SDK 模块版本 == 内核模块版本",
而生产上能跑的组合恰恰是"内核 + v1.3.0 编的插件"。已删掉这个相等性判据:
SDK 源码**只认显式指定**(HOMEAGENT_SDK_DIR),不猜、不试探;
内核那条 dep/=> 只作为**提示**打印(并且按模块名精确联接、只接受紧跟 SDK dep 行的 `=>`,
不再取"输出里第一个 =>");身份改记 **realpath + 内容哈希**;
`meta.Version` 读取先剥注释(注释里的 `Version = "9.9.9"` 不再能赢)。
真正的不变量是 **wire 协议 protocol=2 + 一次真实握手**,写在脚本末尾(部署后回看日志)。
4. **清单判据改成一致性口径**(pi §5):不再"禁止 sdk 字段"——那会把正在工作的那份清单
(/home/newqqagent/plugins/homeagent-mail-bridge/plugin.json 声明 sdk=1.3.0,正是 08:30
那次恢复的处置动作)判红,而我没有"内核不读该字段"的证据。现在:可以不声明;
声明了就必须与构建机指针一致。
|
2026-09-14 16:38:28 +08:00 |
|
|
|
317f3265e3
|
fix(判据): 探针三值 + 发布候选标签 —— 顺带查出探针从写下那天起一次都没跑成过
pi 的两条"真实的洞",都落了,而且第一条当场抓到实证。
1. **探针三值**(可用 / 不可用 / 拿不准→红):`RESULT static=5 probe=ok|unknown`
把"欠账余额"和"探针是否健康"拆成两个数字。
**换完第一次运行就报 probe=unknown** —— 一查:探针调的是 `execFileSync`,
而这个文件 import 的是 `spawnSync`,**名字根本没定义**。也就是说
**探针从写下的那天起一次都没跑成过**,旧的两值设计把 `ReferenceError`
和"没有设备"一起吞掉、统一报成"设备不可用":机制在、闸门从没开过,
而它看起来完全健康。这正是 pi 描述的"恒不开闸",只是比预想更彻底。
现在:命令在但跑不成 → unknown → 红;所有候选都不存在(本机没装 hdc)→ 可判的
"没有设备工具" → false,避免没装 SDK 的机器天天假红。
附 `--probe-selftest`(只跑分类器,不跑套件)+ 变异验证(把 unknown 当"不成立"→ 红)。
2. **releaseCandidate = !gitDirty**(从展示升成标签):BUILD_INFO 现在自报
`releaseCandidate`,发布脚本在脏树时会打印"这个包不是发布候选"。
判据 `build-stamp` 断言"标签与 gitDirty 必须一致"。
**实证**:本轮我打的包正是这种情况 —— `gitDirty: true`(含着 gui-lab 未提交的
NarrowStack/index.css),`releaseCandidate: false`,日志里明确说了"不是发布候选"。
3. 附带:`criteria-hygiene` 加一条"用到 `code/prose/bytes` 就必须真的 import"。
理由是同一形状我这轮在三个文件里各犯过一次(最后一次是 `execFileSync`/`spawnSync`),
而它表现为"判据红了"(ReferenceError 抛在判据自己身上),看起来像判据失败、
不像判据写错。这条至少把最常写错的那几个名字变成明确的红。
|
2026-09-14 16:35:40 +08:00 |
|
|
|
357662ed08
|
fix(homeagent): 不再把 SDK 版本钉在源码里;构建期按内核的依赖表校验对齐
崩溃循环的根因不是"忘了重编",是**把 SDK 版本钉死**:
- `plugin.json`/`plg.json` 写死 `"sdk": "1.3.0"`(本机已装 21 个插件里只有 3 个声明该字段);
- `go.mod` 的 replace 指向 `/root/.homeagent/hmapdev/sdk/v1.3.0`(绝对路径 + 具体版本目录)。
于是"照原样重编"只会再造一个装上去就崩的包。实测本机内核链的是**另一条 SDK 血脉**:
$ go version -m /usr/local/bin/homed
dep gitcode.com/JianFeeeee/homeagent-sdk v0.9.2
=> ./third_party/homeagent-sdk (devel)
所以判据改成**两个二进制的依赖表一致**,而不是"版本号看起来像":
- `build.sh`:读内核的 SDK 依赖(`go version -m`)→ 据此解析 SDK 源码目录 →
用 `-modfile` 临时替换(不改动受版本管理的 go.mod)→ 构建 → **回头验产物**:
产物与内核链的 SDK 版本不一致就报错退出,绝不产出"装上去就崩"的包。
- `manifest_sdk_test.go`:① 清单不许写死 `sdk`;② go.mod 的钉子不得偏离构建机指针
(取不到基准就红,不许默默放过);③ `build.sh` 必须在(它是与内核对齐的硬校验点)。
三条都做过变异验证(写死 sdk / 钉子漂到 v1.2.0 / 删掉 build.sh → 各自红)。
顺带查出一个比"重编"更根本的事实:本机 `build.sh` 跑到编译就失败 ——
./plugin.go:217:18: sett.DataDir undefined (type sdk.SettingsAPI has no field or method DataDir)
即本插件源码用的是 **1.3.0 SDK 的 API**,而本机内核链的是 0.9.x 血脉:**重编解决不了**,
要么内核改成链 1.3.x 的 SDK,要么把插件移植到内核那份 SDK(这是 HomeAgent 侧的决定)。
|
2026-09-14 16:27:02 +08:00 |
|
|
|
d5cfcbdc9c
|
fix(权限): 409 的第二种含义是「本档不该问」——四桥都补上;状态写入点不再兜默认档
线上事故(jianf 经 pi 转达):补投路径漏传 permission_mode,插件拿 undefined 兜了
workspace 档,把 full 档会话写成 workspace-write + ask —— 不是"拦一次",是一整轮
工具能力降级,且状态留在会话里;随后该会话每次受守卫调用都撞 409。
四件事:
1. **状态写入点不接受默认值**(新增共享 `modeForStateWrite`):缺字段/脏值 → `null`
= 不写状态。"默认值可以出现在**决策**里,不可以出现在**状态写入**里。"
同时保留共享契约的 fail-closed:真读到 workspace 才写 workspace。
2. **409 的两种含义分开处理**。`allowed-once` 只绕过**审批**,改不了**沙箱** ——
所以 dsh 桥在放行前先把服务端给的权威档位**写回会话**(这也就成了自愈路径:
已经降级的会话,下一次带档位的 409 会把它修回来);只认服务端明说的 full,
plan 与"链上没有人类"照旧 fail closed。
3. **同一处缺陷在 zcode / opencode 也在**(`hooks/permission.mjs` 与 `index.js`
都把 409 当永久失败拒绝)。我先前在回信里写过"这两个桥不转发权限询问,不需要改"
—— 那句话是错的,我当时的搜索面只有 `<plugin>/src/*.mjs`。按 pi 的要求把这条
**否定性事实变成常驻判据**后,它第一次运行就红给我看。四桥现在都有
「409 + full → 放行」,且**排在永久失败分支之前**(含顺序变异自检)。
4. **共用测试重新同源**:`test/catchup.test.mjs` 从 `153985e` 起就是分叉的
(我那版把平台专属路径写进了共用文件),而 `deploy/install.sh` 第 24 行会跑
`check-shared-libs.sh` —— 也就是说**部署一直是红的**,我没跑过那个脚本。
共用文件只放契约(值/行为),跨平台配对judge 移到平台专属文件,四份逐字节相同。
另外把"判代码 vs 判理由"从记忆变成代码:`test/lib/read.mjs` 提供 `code()/prose()/bytes()`,
判据目录里不得再裸用 `readFileSync`(新判据 `criteria-hygiene` 管,含读取器自检)。
判据证据(每条都做过"能不能红"的变异):
- 写回去掉 → 红;纠正块挪到普通 409 之后 → 红;状态写入点退回兜默认 → 红;
- zcode/opencode 的放行分支拿掉 → 各自红;共用测试分叉 → check-shared-libs 红。
各套件:dsh 388、pi 443、zcode 387、opencode 333(均经 npm test,含 tsc);
electron `npm test` 15/15 判据绿 + vitest 266 + typecheck;`check-shared-libs.sh` 退出 0;
Go `go test ./...` 全 ok。
|
2026-09-14 16:21:27 +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 |
|
|
|
d78f19fe9a
|
fix(webui): 日历页面补上圆角(圆角要给到「有底色的那一层」)
用户(2026-09-14):「日历页面还没有添加圆角」。
根因不是「忘了写 border-radius」—— 外壳那条 `.app-shell > *` 是生效的,
它给日历**根节点**加了 14px 圆角。问题是根节点写着
`flex-1 min-w-0 flex min-h-0`、**自己完全没有底色**:
真正白底的是里面那两块面板(网格栏、右栏),它们的直角从透明外壳里戳出来。
所以 `getComputedStyle(根).borderRadius` 一直是 14px,看上去却全是方角。
两处不能照搬收件箱做法的地方:
① 网格栏与右栏**不是两张卡**(中间只有 1px 分隔线,没有外壳间距)
⇒ 只能给**外沿**:左边那块给左两角、右边那块给右两角。
内侧也给会露出底色缺口。
② 右栏外面还套了一层容器(管 `lg:w-[400px]` 与 `border-l`,自己不上色),
真正上色的是它里面的 DayAgendaPane / CalendarEventEditor
⇒ 圆角要**再往下给一层**,只加在容器上会被内层直角原地盖掉。
(左栏本身就是白底面板,不能再往下给:它的第一个子元素是工具条。)
实测(1280×800,真浏览器读渲染像素):
改前 左栏 radius=0px、右栏 radius=0px,四个外角都是白像素(方角)
改后 左栏 `14px 0px 0px 14px`、右栏内层 `0px 14px 14px 0px`,
四个外角像素都等于页面底色(被切掉);上边缘内缩 8px,
与收件箱详情栏(对照)完全一致;内侧分隔线两侧保持直角。
判据:narrow-layout 新增 4 条 —— 钉的是「规则与 JSX 接没接上」「给的是哪几条边」
「右栏有没有再往下给一层」「左栏有没有多给一层」,不是那条 14px。
wide-regression 新增一条**渲染层**判据:从 `elementFromPoint` 沿祖先链找第一个
自带底色的元素,四个外角都必须正是那个圆角面板(光看 computed style 区分不了,
因为被盖掉的形态 computed 也照样是 14px)。
顺手修掉 wide-regression 里一条**假失败**:`#root > div` 现在指向壁纸幕布
(`app-backdrop` 后来挂成了 `#root` 的第一个子节点),于是「仍是三栏并排」
一直报「栏数=0」,而三栏好好的。假失败比没有判据更贵 —— 看的人会去查一个
本来没坏的东西。
|
2026-09-14 16:05:15 +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 |
|