|
|
e63a5ba81d
|
docs(debt): 给那条被我误判的"不该写进判据"加**醒目自我取代标记**
`88f4b8a` 已用"三个带"更正了这个判定,但**更正写在后面**,而误判那句留在前面 ——
一个读到那里就停的人(或一个 grep 抓单句的人)只会看到 "**不该写进判据**",
而那正是我这条工作线上反复在防的"**半截更新**"。
原句划掉(`~~…~~`)+ 就地写明"我判错了、已被本节末取代、结论是**顺序约束**"。
⇒ 规矩: **更正一条已写下的判断时,要在被更正的那句旁边留标记** ——
只在末尾补一段,等于让"错的那句"继续独立生效。
(本节里那句的上下文是 pi `8f6f6e6e` §三;实测见同一节末"三个带"。)
验证: JSON 合法;`TestDebtLedgerMatchesMeasurement`/`TestDebtSummaryReadsAuthoritativeLedger` 通过;
`debt-visibility.test.mjs` 1/1。
|
2026-09-25 06:57:24 +08:00 |
|
|
|
88f4b8ab5e
|
fix(debt): 我上一条把 pi 的 hop 建议判成"假问题"——**判过头了**;实测有**三个带**,最坏那个会清零 hop 守卫
★ 更正我自己上一条 `a05509c`(同一天、我写的)。我写:
"`CountTrailingRelayHops` 是重算 ⇒ '计不计'由有没有落库唯一决定 ⇒ **不该写进判据**"
**那半对,但我漏了两个形**,而漏掉的那个正是最坏的。实测(`/tmp/hop.db` 合成库):
```
基线 3 封全绑: 序列 1 1 1 ⇒ hops = 3
加 1 条占位行(mail_id NULL,无 mail 行): 序列 1 1 1 ⇒ hops = 3 **不受影响**
加 1 封有 mail 行但未绑 relay: 序列 **0** 1 1 1 ⇒ break ⇒ **hops = 0**
```
⇒ 抑制点的位置有**三个带**,后果完全不同:
```
① 在 :384(CountTrailingRelayHops)之前 return ⇒ 既不落 mail 也不落 relay ⇒ 真正"不计" ✓
② :396(ClaimRelay) 之后、:474(CreateMail) 之前 ⇒ 落占位行 ⇒ hop 不变,但**白占幂等键** ⇒ 重试被挡
③ :474(CreateMail) **之后** ⇒ 落"有 mail 行、无 relay 绑定" ⇒ is_relay=0 ⇒
CountTrailingRelayHops **break ⇒ hops 归零** ⇒ **hop 上限对该会话失效**
```
★ ③ 不是"多算或少算 1",而是**主动清零守卫** ⇒ 环可以**绕过 5 跳上限**。
而 ③ 恰是"抑制逻辑写在 CreateMail 之后"这种最自然的写法会落进去的带(那里才拿到 mailID)。
⇒ 所以 pi 的直觉**是对的**,我错在把它当成"可配的口径"而整体否掉。
正确的结论(取代上一条): 这不是口径,是**必须满足的顺序约束**,而顺序约束**更要**进判据:
```
「抑制必须在 CountTrailingRelayHops 之前 return」——理由: 落在其后会造出"有 mail 行无 relay 绑定"
的邮件,把 hop 守卫清零
判据形状: 构造一封因抑制而不发的报告,断言同会话 CountTrailingRelayHops 不降为 0
(**能失败** —— 把抑制挪到 CreateMail 之后就红)
```
★ 教训与这条线同形: 我用"重算"这个**机制事实**否掉了一整条建议,而没有把机制在**所有落点**上跑一遍。
pi 说的是"位置影响读数",我说的是"机制是重算" —— **两句都真,但我的那句不能推出"所以不必写"**。
验证: `TestDebtLedgerMatchesMeasurement`/`TestDebtSummaryReadsAuthoritativeLedger` 通过;
`debt-visibility.test.mjs` 1/1。
|
2026-09-25 06:56:29 +08:00 |
|
|
|
a05509c51c
|
docs(debt): 补记判据⑥(permission 分叉点)+ 判据清单落定 + 否掉一条落点细节
pi `8f6f6e6e` 提的三件事,我 `476d22ad` 已逐条答过;本条把**判据清单落定**进登记
(此前只活在邮件里 —— 与上一条同样的问题)。
## ⑥ permission 分叉点,必须钉住
```
pi 的判据: 「本封来信 id ∈ relayed_mails」 ⇒ 抑制
我的判据: 「parent 本身是 failure-relay」 ⇒ 抑制
构造: 一封 permission 询问(kind=permission,也是 relay)发出 → 对方处理失败 → 发失败报告
pi : 来信(permission) ∈ relayed_mails 为真 ⇒ **抑制**(错: 首报该发出去)
我 : parent 不是 failure 类 ⇒ **放行** ✓
```
**可达性我验了**(这是它必须钉住的原因):
```
permission-relay 的子邮件 = **137**(mail_type: normal 58 / permission_decision 79)
⇒ 子邮件确实会被产生 ⇒ 那一侧处理失败就会产出失败报告 ⇒ 分叉点可达
当前: failure 报告的 parent 是 permission-relay 的 = **0** ⇒ 今天还没分叉
⇒ 而这正是"两条判据现在等价(都 10 / 差集 0)"的来源
```
★ 所以"等价"是**当前数据的性质,不是机制的保证**。选判据的理由不能是"它们现在等价",
而是**我的判据把"是不是失败类"读了出来**,pi 的没有 —— 信息量更大的一点更耐久。
(判据清单 ①–⑥ 一并落定;⑥ 需要失败才能构造。)
## ⚠ 同时否掉一条落点细节(免得下一个人重走)
pi 建议"插在 `:384` 前 ⇒ 被抑制者不消耗 hop,这个口径要写进判据"。
我读实现后判它为**假问题**: `CountTrailingRelayHops`(`relayhops.go:54-80`)是**重算**
(每次 `mails LEFT JOIN relayed_mails` 现扫),不是自增计数器 ⇒ "计不计"由"有没有落库"
唯一决定,**不是口径选项** ⇒ **不该写进判据**(写进去会让人以为可配)。
唯一相关的事实(与 hop 无关): 抑制点必须在 `ClaimRelay`(`mail.go:397`)**之前** return,
否则白占一个幂等键、把后来的重试也挡掉。
验证: `TestDebtLedgerMatchesMeasurement`/`TestDebtSummaryReadsAuthoritativeLedger` 通过;
`debt-visibility.test.mjs` 1/1。
|
2026-09-25 06:55:13 +08:00 |
|
|
|
efa07126c4
|
fix(debt): 更正我上一条自己写错的计数 —— **8 条语句 / 5 个文件**,不是"7 处 / 5 个包"
★ 这是我上一条 `3f394c8`(同一天、我写的)里的一处**未经核对的数**,而我刚刚还在
另一封里要求 pi 的规则"数必须有精确定义"。自证一下:
```
我写的 : 7 处 / 5 个包 / 3 种语言
按"生成 relay_key 的语句"逐条数:
plugins/dsh-mail-bridge/src/index.ts:1240 model-failure:
plugins/dsh-mail-bridge/src/index.ts:1749 empty-reply:
plugins/pi-mail-bridge/src/worker.mjs:625 model-failure:
plugins/pi-mail-bridge/src/worker.mjs:715 model-failure:
plugins/zcode-mail-bridge/src/index.mjs:304 zcode-failure:
plugins/homeagent-mail-bridge/plugin.go:929 homeagent:failure:
deploy/service-failure-notify.mjs:92 service-failure:(有 INVOCATION_ID)
deploy/service-failure-notify.mjs:94 service-failure:(退化为 sha256)
⇒ **8 条**(不是 7)
按文件去重: dsh / pi / zcode / homeagent / deploy = **5 个**("包"→"文件"更准)
```
**错在哪**: 我先写了"7",然后把它当成已知去查证 —— 而**没有回头数一遍**。
`service-failure-notify.mjs` 有**两处**(`INVOCATION_ID` 分支 + sha256 分支),
我数成了 1。⇒ 这正是"**报数必须给数据源/口径**"那条:口径写了("产生点"),
但**没真按口径数**。
★ 讽刺的是这条修正**不改变任何结论**(8 vs 7 都是"散在多个包、无载体"),
但它必须改 —— 否则下一个引用它的人会带着一个错的数往下走,而**错数比错结论更难发现**
(结论有争论,数看着就像核过的)。这正是本会话反复吃的那族形状。
验证: `TestDebtLedgerMatchesMeasurement`/`TestDebtSummaryReadsAuthoritativeLedger` 通过。
|
2026-09-25 06:53:35 +08:00 |
|
|
|
3f394c83c7
|
docs(debt): 追加「失败报告抑制」的**前置**问题 —— 谓词"是不是失败报告"没有载体
pi `334710a1` 的 A/B 落点分析(网关侧抑制 vs 桥侧省调用)**都**要先回答
「parent 是不是失败报告」。我按实产查了它的全部产生点 —— 它没有载体。
## 实测:7 个产生点 / 5 个包 / 3 种语言,写法互不相同
```
dsh-mail-bridge/src/index.ts:1240 model-failure:${data.mail_id}
dsh-mail-bridge/src/index.ts:1749 **empty-reply**:${mailSessionID}
pi-mail-bridge/src/worker.mjs:625 model-failure:${...}
pi-mail-bridge/src/worker.mjs:715 model-failure:${...}
zcode-mail-bridge/src/index.mjs:304 zcode-failure:${...}
homeagent-mail-bridge/plugin.go:929 "homeagent:failure:"+replyTo ← Go
deploy/service-failure-notify.mjs service-failure:${INVOCATION_ID|sha256} ← systemd 脚本
```
网关侧一无所知:这几家前缀 + empty-reply 在 `server/**/*.go` 里 **0** 处引用。
★ "用现成的 `RelayKeyForMail` 判一下"只对一半 —— 它返回 `(relay_key, kind)`,而
**`kind` 区分不了失败**:`kind='summary'` 里 failure 98 / 非 failure **321** ⇒
用 kind 判会连 321 行普通搬运一起豁免,**正是已否掉的修法①**。
★★ 活反例:`empty-reply:` 是失败类但**不含 `failure` 字样** ⇒ `LIKE '%failure%'`
永远抓不到它(代码 1 处、当前 **0 行** ⇒ **将来第一次触发就是静默漏判**)。
⇒ 推论: 在网关硬编码这四家前缀 ⇒ 第 6 家桥出现时**静默漏判** ⇒ 报告不再被抑制 ⇒
环回来(假绿)。所以修法应**先把分类变成网关拥有的东西**(枚举第三类 / 或 relayed_mails
加一列),再由各桥**声明**而非拼串。
⚠ 加枚举会撞上一条**故意**的锁: `TestRelayKindsIsExactlyTwo`(`relay_test.go:60-64`)
"免配额类型是白名单…新增前请确认它确实是 harness 代劳" ⇒ 必须同步改它 ——
而那正是它本来就该问的问题。
验证: `TestDebtLedgerMatchesMeasurement`/`TestDebtSummaryReadsAuthoritativeLedger` 通过;
`debt-visibility.test.mjs` 1/1。
|
2026-09-25 06:52:53 +08:00 |
|
|
|
5b06f7cbec
|
docs(debt): 登记「并列失败不得被合并」—— pi a948cdbb 的判据⑤(此前只活在邮件里)
pi 提了一条判据⑤:**同一父信下的两封并列失败报告不得被合并**。我上封(`a792717a`)
已认它该进判据,但**仓库里没有任何地方记着它** —— 只留在邮件里。
## 为什么它必须进登记
```
B 口径(agent, failure-父链根)今天: 98 封 → 92 组、抑制 6
组内「两个成员共享同一父」的组数 = **0**(我逐组实测;5 个多成员组全是真链式)
⇒ 今天确实不会误合并 —— 但这是**当前数据的性质,不是设计的保证**
```
只要出现「同一封来信被两个不同 agent 各回一封失败报告」,root-keying 就会合并它们,
而那些是**内容各异的并列失败**。★ 这形状**本系统真实发生过**(我复算确认):
```
d042cc4c: 22 封失败报告、distinct parent = **22**、其中 parent 本身是失败报告的 = **1**
⇒ 21 封并列;(agent, 线程根) 口径下塌成 4 组(9/7/5/1)⇒ 一次丢 **18**
```
⇒ 「同级并列」不是边角情况,所以判据要钉的是**机制**、不是"当下恰好成立"。
## 为什么现在建不了
抑制机制**尚未实现**(`grep -c 'suppress|抑制' server/**/*.go` = **0**)⇒ 这条判据此刻
**无对象可测**。所以登记为欠账,到期条件写成 **"失败报告抑制机制落地时"**,
并列出已否掉的修法(①跳过 kind=summary 会豁免它要拦的那类;②仅根 root-keying 正是本条要防的),
免得接手的人重走。
★ 这条的处境正是本会话那个结论的又一例:**一个没有执行者的结论会一直"在讨论"**——
写进登记 + 到期条件,才会让接手的人**必须**遇到它。
验证: `TestDebtLedgerMatchesMeasurement` / `TestDebtSummaryReadsAuthoritativeLedger` 通过;
`debt-visibility.test.mjs` 1/1 通过。
|
2026-09-25 06:42:32 +08:00 |
|
|
|
3c2b7d1385
|
fix(deploy): 还清欠账 redeploy-script-unguarded-steps —— 四类裸步骤接收退出码
出口是这条欠账**自己写的**到期条件("下一次改 `deploy/` 下任一脚本时"):
当天因修 `--dry-run` 落地写入而动了这个脚本,所以一并还。
## ① 前端同步三步
`rm -rf assets` / `rm -f index.html` / `cp -r dist` 原先调用点不接退出码,
而 `run()` 内部是 `eval`(脚本只有 `set -uo pipefail`,无 `-e`)⇒ 失败既不中断也不上报。
`cp` 那条尤其要紧:前端产物没拷进去 ⇒ go:embed 把**旧界面**打进二进制,而所有单测仍绿
(2026-09-14 踩过,见脚本内那段注释)。三处都改成 `|| { bad …; exit 2; }`。
## ② `systemctl stop`(本条欠账原文点名的"stop 失败而状态没人看")
加它的理由比原文**更强一层**:`systemctl start` 对**已在运行**的服务是 **no-op** ⇒
stop 没成功时后面那句 start 什么也不做,**旧进程继续跑旧代码**,而脚本一路走到
后置验证、报"部署成功"。也就是说,"部署脚本跑过了 ≠ 线上跑的是当前代码"这个第 2 类漂移
会被脚本**自己在内部**造出来 —— 与今天线上那件事(09-19 二进制跑了 6 天)同形,只是成因在脚本内。
## ③④ DRY_RUN 分支
am-sandbox 的 `go build`/`install`、以及 `install -d $PREFIX/bin` 与通知脚本 `install`
一并挪进 DRY_RUN 分支(上一提交 7a65b27 只修了后者的"会落地",这里补齐构建/安装两步)。
## 边界(如实记,避免读成"全脚本已无裸步骤")
只覆盖 `redeploy-gateway.sh`。`redeploy-plugin.sh` / `install.sh` 的同类步骤仍未加守卫 ——
那是另一条欠账 `deploy-interrupt-trap-other-scripts` 的范围。
## 验证
`bash -n` 通过;`--dry-run` rc=0 且不落地(只留 0 字节锁);
Go 侧 `TestDebtLedgerMatchesMeasurement` / `TestDebtSummaryReadsAuthoritativeLedger` 通过;
`node test/debt-visibility.test.mjs` 1/1 通过。
|
2026-09-25 06:22:09 +08:00 |
|
|
|
65de1c3884
|
跨端对齐:授权栏 navigator_only + 组件按页拆分 + 服务器补 permission_options
用户两项裁定落地(均为 ask_user 明确选择):
① 授权栏口径 = navigator_only(照 WebUI 架构)
· 新建 pages/PermissionPanel.ets —— 详情页的决策面板,
对应 MailView.tsx:693 的 PermissionPanel(审批型 / 主动提问 / 已处理 三态)
· 决策入口从授权栏移到 MailDetailPage;MailDetailPage 原来只显示一个
「权限请求」小标签、根本没有决策入口(比 WebUI 少一整块,且反了:
栏里能决策、点进详情反而不能)
· PermissionTab 删掉内联「同意/拒绝」+ 备注框 + decide():
整卡可点 → onOpenMail(对齐 WebUI PermissionList.tsx:81 的 pick())
· PermissionRequest 补 source_account_id(客户端侧记来源,跳详情要定位网关)
② 服务器补 permission_options —— 修一条真实的、跨端共有的缺口
· mails.permission_options 从 INSERT 起就写进去,但**从来没有任何读路径
选过它** ⇒ 详情端点永远返回空。WebUI 的决策面板读 mail.permission_options,
所以提问型的预设选项**两端全部落空**(审批型靠 ['同意','拒绝'] 兜底蒙混)
· GetMailByID 补选该列 + JSON 反序列化(与 cc_list 同款)
③ 组件按页封装(用户要求「以便与 WebUI 一一对应」)
MainPage.ets 4592 → 3192 行
· pages/PermissionTab.ets 720 行 ↔ PermissionList.tsx
· pages/ContactsTab.ets 796 行 ↔ ContactPanel.tsx
· pages/NavDestinations.ets 181 行 ↔ Navigation 壳
· pages/NavShared.ets 65 行 ↔ 跨栏共用件
④ 判据跟着组件搬家(否则静默失效,不是红)
harmony-logic 的 pageCode / harmony-nav 的 navSrc 改为显式文件名单;
harmony-appearance 的 PANE_SOURCES 补 ContactsTab;harmony-contacts 三个
test 并入 ContactsTab;harmony-logic 的决策断言改指 PermissionPanel,
并新增「授权栏不许再有内联决策」两条(navigator_only 的正形状)。
animation-audit:共享元素转场判据从「同文件共址」改为「按 id 找驱动」。
旧形状把 in/out 端必须在同一文件当成代理,而两端**天然在两处**;
抽出写信页(NavDestinations 持有 in 端)后误报。新判据仍要求每个 id
都有 Motion.morph 驱动 —— 变异实测:把驱动换成裸 animateTo 仍判红。
判据:files=34 checks=556 red=1(仅 build-stamp,产物待重构建)
|
2026-09-24 10:10:32 +08:00 |
|
|
|
006f813066
|
docs: 登记 3 条口头承诺过的未验项(此前只存在于对话里)
用户问「全部完成了是吧」时我核对了一遍,发现上一轮我**只在回复里**说过
「这项没验」,而它们**没有进任何登记文件**。
这正是本仓反复消的形状:**「说过」不等于「记着」** ——
对话一结束/一压缩,那三句就没了,而登记文件才是能活下来的地方。
(`DEBTS.json` 自己的注释就写着这条纪律:理由不能只存在于某个人当时的记忆里。)
补登记 3 条(都是 `kind: env`,即"本机能做但当前环境验不了"):
1. `harmony-morph-unverified-middleframes`
两处共享元素转场的**中间帧**没看到 —— `snapshot_display` 往返 1.5-3s,
比 220ms 的动画慢一个数量级。结构/接线有判据钉着(三条变异验过会红),
但"真的在动"只能真机看。附了本仓那条教训(探针不敏感时量的是噪声)。
2. `harmony-account-errors-banner-unverified`
聚合失败横幅只验了「不出现」那一半。这条横幅**全部价值就在它出现的那一次**,
所以"逻辑对齐 + 编译通过"不能算验过。
3. `harmony-permission-history-render-unverified`
「历史 n 条」的分组是纯函数、有跨端判据,但**渲染那层**没验
(本机 `permission_request` 0 封)。附了复现路径。
★ 顺带说明为什么这次要写进文件而不是再回一句:
前两条我自己都**明确说过"不声称已验"**,但两次都只是消息。
第三条更是只在 commit message 里提过。三者有一个共同点 ——
**它们都是"我以为说过了"就够了的**,而实际不会有人回头翻聊天记录。
|
2026-09-21 16:48:44 +08:00 |
|
|
|
d857352e29
|
跨端: 闭合 harmony-permission-history —— 授权栏补上「已决策的历史」
这是 `docs/DEBTS.json` 里登记的一条,它的到期条件原文是
「做『授权栏与 WebUI 对齐』时」—— 就是现在这一轮。
## 原缺口
鸿蒙的 `PermissionTab` 只调 `GET /permission/pending`(服务端
`ListPendingPermissionsFor`,SQL 带 `WHERE pr.result IS NULL`)
⇒ **只拿得到待决的**,于是"这条会话批过哪些事"完全看不到;
而 WebUI 有(`PermissionList.tsx:182` 的「历史 {n}」)。
## 关键判断:**不照抄 WebUI 的 inbox 分组**
我先把 `PermissionTab.load` 整个改成读 inbox + `groupPermissions`,
**改到一半发现行不通**(编译报 `question`/`context`/`agent_name` 找不到):
· WebUI 从 inbox 分组,但它的 `PermissionRow` **只渲染**
`subject`/`created_at`/`permission_result`/`permission_expires_at`
(逐字段 grep 过,全文件没有 `question`/`options`/`context`);
· 而**待决**那一段我们要显示 `question`/`options`/`context`/`kind`
—— 那四个字段在 `permission_requests` **表**里,
inbox 回包(`models.Mail`)**没有它们**(模型逐条核过,只有 `permission_result`
与 `permission_options`)。
⇒ 两条来源各有各的信息量,不是二选一:
· **待决**继续走专用端点(信息更全、能直接决策);
· **历史**走 inbox 补上。
代价是每账号多一次请求 —— 这是**有意的取舍**,写在代码注释里。
(半成品已 `git checkout` 撤掉,没有把它留在提交里。
撤掉的原因如实记在注释里,免得下一个人以为"照着 WebUI 改"就行。)
## 落地
· `model/MailGrouping.ts`:加 `groupPermissions` + `PermissionGroup` +
`isPendingPermission`,逐条对齐 WebUI 的 `groupPermissions`,
含它那**三步排序**(有待决的先来 → 待决多的更靠前 → 最新一封倒序)。
· `PermissionTab`:从 inbox 取 `mail_type=permission_request &&
permission_result != ''` 的,分组后渲染「历史 n 条」(只读、不可操作)。
· 空态判据从 `requests.length === 0` 改成**两者都空**才显示 ——
否则"有待决的历史"会被误报成"没有待决策的请求"。
## 判据自己抓到了我
`cross-client-logic.test.mjs` 的「缺口只减不增」在我补上 `groupPermissions`
之后立刻变红,并给出准确指引:
减少(harmony 补上了功能)→ 请把 gaps 里对应的名字删掉
⇒ 已清空 `gaps`。**这条判据在这轮里三次发挥作用**:
第一次报出这个缺口(09-20),第二次在我半成品时红了,
第三次确认闭合。`pass=7 fail=0`。
`docs/DEBTS.json` 的 `count` 已改 0、`due` 记完成、`note` 写明修法与取舍。
## 设备验证(如实)
✓ 授权页正常渲染,进程存活(23343),无新 jscrash
✓ 空态文案正确(本机确实没有权限邮件)
✗ **"历史 n 条"真的显示出来**这条路径没能实测:
本机没有已决策的权限请求(服务端实测 `permission_request` 0 封)。
逻辑逐条对齐 WebUI、编译通过,但我不声称已看到它渲染。
|
2026-09-21 16:45:23 +08:00 |
|
|
|
5e4a1b616c
|
跨端: B 的交付物(跨端纯逻辑一致性判据)+ 照 skill 回扫修掉两处隐形债
用户:「你为什么不加载鸿蒙开发相关skill?」—— 说得对。那份
`arkts-grammar-standards` 写着 "REQUIRED before writing the first .ets file of a
session",而我这轮一直在写 `.ets`。补加载后照它的规则表**逐条回扫**,
当场抓出两处此前没人管的违规。
══ ① 用户要做的 B:`cross-client-logic.test.mjs`(新,6 条判据)
背景:两套纯逻辑各写一份且已分叉(replyTarget 214/170 行、mailGroups 178/459、
appearance 187/324、calendar 208/446)。当天已**踩到**两处分叉
(`participantAddress` 的 `||`、`ThreadPage` 字段全错)。
做法:**同一张用例表喂给两边,逐条比结果**(`--experimental-strip-types`
直接在 node 里跑两侧源码 —— 两边的 model 层都是纯逻辑、无 SDK 依赖)。
不选"生成一份共享源码":harmony 不能 import 工程外文件,
且两边类型系统不同(ArkTS 禁解构/any/对象字面量要具名类型),
生成器要维护"两边都能过"的子集,是另一个大工程。
★ **首轮运行就报出两处真分叉,都不是我踩到才发现**:
① `formatAddress('dsh', undefined, undefined)`:electron 返回 `"dsh"`,
harmony **抛** `Cannot read properties of undefined`。
—— 又是 `omitempty` 那个坑(**第三次**),这次是判据先报的。
② `monthGrid`:electron **固定 6 行**(`grid-rows-6`),harmony **4~6 行**
⇒ 翻月时网格高度跳动。WebUI 的注释明写要避免这个("行数变化会让整个
网格高度跳动,翻月时页面内容上下弹")。
③ 顺着 ② 又发现:WebUI 邻月格子**填真实日期并置灰、可点**
(`CalendarView.tsx:545-556`),harmony 留**空白格**。
★ 判据自身的两次错,都留了档(判据的 bug 与代码的 bug 一样危险):
· 第一版把 `args[0]` 当单个参数传,字符串被当可迭代对象展开 ⇒
`formatAddress('d','s','h')` —— **判据自己造出假分叉**。
· 第一版 `weekStart` 传 0(周日),而两端实际都是 1(周一)⇒ 又一处假分叉。
差一点就去"修"一个不存在的问题。
· `monthGrid` 的投影第一版按 `inMonth ? [y,m,day] : null`,
把"邻月填不填真日期"这个**真分叉**抹平了 —— 投影只该换表示,不该替我看不看。
★ 三类"不同"要分清(写进文件头):**命名不同**(投影归一,不是分叉)、
**签名不同**(ArkTS 没 Date 重载习惯;语义必须一样)、**行为不同**(是分叉,以 electron 为准)。
══ ② skill 回扫抓出的两处隐形债(编译器只告警、判据也不管)
· **正则字面量**(`arkts-no-regexp-literals`):`MailDetailPage.ets:615` 的
`/^\d+$/`(从 2026-09-19 活到今天)。
· **废弃的全局 `router`**:`api/Logout.ets:76` 的 `router.replaceUrl(...)`。
它是个独立函数(没有 `this`)⇒ 拿不到 `UIContext`,改成由调用方传
(两个调用点都持有 `getUIContext()`,零成本)。
★ 这两条为什么能活这么久:**编译器对它们只告警、不挡构建**,
全仓也**没有判据**管 ⇒ 规则事实上不存在。已补两条判据,都做了变异验证。
══ ③ 顺带修正一条**恒真的同义反复**断言
`harmony-calendar` 里 "today 不在本月:不许标在别的月" 那条:
它是在"邻月格子是空 `DayCell`(`iso` 为空串)"时写的 ⇒ `c.iso === today`
**永远不可能**匹配 ⇒ `count === 0` 恒真,**看起来守着一条规则,其实什么都没守**。
改成真不变量:**"被标为今天的那一格,iso 必须就是 today;至多一格"**,
并反向核对"2026-10-01 确实出现在 9 月网格里"(否则那段是空转)。
实测 WebUI `CalendarView.tsx:547` 是逐格 `isSameDay` ⇒ **它会标**,
所以原来那条"不许标"本身就窄了一半。
══ ④ 登记两处盘点发现(**没有**顺手改,因为需要人决定)
· `harmony-dead-pages`:`InboxPage.ets`(238 行) 不可达(不在页面表、无人导航),
`SessionsPage.ets`(170 行) 唯一引用来自 InboxPage ⇒ 一起不可达。
没删是因为 `HARMONY-ALIGN-PLAN.md:214` 把它当变异测试靶子用过 ——
删掉会永久丢代码,是否只是"早期留存"我判断不了。
· `harmony-permission-history`:WebUI 授权栏显示**待决 + 已决策历史**两段
(拿 inbox 自己分组,`PermissionList.tsx:27/174/182`);鸿蒙调专用端点
`/permission/pending`(SQL `WHERE pr.result IS NULL`)⇒ **只拿得到待决的**。
已在 `cross-client-logic` 的 gaps 里如实登记,判据会盯着"不要再少"。
══ 判据状态
`files=33 ran=33 checks=530 pass=530 fail=0 skip=0 red=0 broken=0 unreported=0`;
`baseline=7/7✓`(底本第 9 次重算,已按规矩先 `git diff --quiet HEAD` 取证 + 记录理由)。
`verdict=red` 残余仍是 5 条静态判据的**设备到期提示**(既有机制)。
══ 环境
模拟器昨天起卡死(hdc 能连、shell 超时、CPU 150%、跑了 34 小时),
导致设备判据各跑 836 秒后失败 —— 看起来像"套件卡死"。用户批准后杀掉重启
(`Emulator -start HATriple -noWindow`,`devecocli` 那套因 x11 起不来),
现在**75~150 秒**跑完整套。
|
2026-09-20 22:35:35 +08:00 |
|
|
|
25e7d8f3bf
|
跨端: 补附件区(两端一直都有这个功能,我上次误判成"死代码")+ 修两个真 bug
══ ① 更正我 2026-09-19 的一个**错误结论**(已写进 docs/DEBTS.json 留档)
那天我审计后写下:`GET /me/mail/inbox` 的回包**既没有 `attachments` 也没有
`has_attachments`** ⇒ `MailList.tsx:261` 的 `mail.attachments?.length ?? 0`
恒为 0、WebUI 那个 📎 是**死代码**。并据此在鸿蒙侧**有意不抄**这个标记。
**这个结论是错的**,错在取证方法:我**只看了一封没有附件的邮件**,
看到 key 不在,就断言服务端从不返回它。事实:
· 服务端 `GetInbox`(`mail.go:586`)**明确调了 `fillAttachments`**,注释还写着
理由:「Agent 靠收件箱列表得知有哪些附件可下载,否则它不知道该调 attachment_id」
· `Mail.Attachments` 的 tag 带 **`omitempty`** ⇒ **没有附件的邮件根本不输出这个 key**
实测 `limit=200`(96 封):带 `attachments` 的 **2 封**,正是真有附件那两封。
★ 教训:**`omitempty` 字段的"缺失"不等于"服务端不返回"**。
判「某字段有没有」必须拿**确实有值的那条**去验,而不是拿一条恰好为空的数据。
这与同一天那个白屏崩溃(`session_workspace` 缺失 → `undefined` → 抛)
是**同一个坑的两面** —— 那天是"缺失 → 客户端崩",今天是"缺失 → 我误判成不返回"。
══ ② 补附件区(鸿蒙原来完全没有)
· `model/Attachment.ts`(新)—— `formatSize` / `attachmentLabel`,纯逻辑无 SDK 依赖,
逐字对齐 WebUI `api/client.ts:390`(三档 + 保留一位小数)。
· `MailApi.downloadAttachment` —— 走 `getBytes`(不是 `get<T>`:后者假定 JSON,
取二进制会炸;壁纸当初踩过)。
· `IcsFile.saveBinaryFile` —— 与既有 `saveIcsText` 同一套流程,只是写 `ArrayBuffer`。
· `MailDetailPage` 正文之后渲染附件清单(回形针 + 文件名 + 大小 + 下载),
位置/形态对齐 WebUI `Attachments.tsx`(无附件时**整块不渲染**)。
· 列表行的 📎 + 数字(`attach_count`,与 `cc_count` 同形状派生)。
══ ③ 顺带撞出并修掉两个**真 bug**
**bug A(差一点就是 94/96 必崩)**:我第一版写 `mail.attachments.length` ——
而 `Attachments` 带 `omitempty`,96 封里只有 2 封有这个 key ⇒ 其余 94 封是
`undefined` ⇒ `.length` 抛。**与当天早些时候那个白屏崩溃是同一个坑,
我刚修过、还在 `Models.ets` 里写了一大段注释,然后加新字段时照踩。**
⇒ 说明"记住别这么写"不管用,要在每个真正读的地方把 `?? []` 写出来。
**bug B(潜在白屏)**:`PermissionTab` 读 `req.session_alias` ——
而服务端 `PermissionRequest` struct **根本没有这个字段**(`models.go:239-255`),
`ListPendingPermissionsFor` 的 SELECT 也没查它,WebUI 的类型里同样没有。
它是我照"授权卡总得显示会话名"的直觉加出来的。⇒ 恒 `undefined`,
一旦有待办就抛。**一直没暴露只因为当前待办数一直是 0**(实测 `{"requests":[]}`)。
⇒ 改成服务端确实有的 `agent_name`,并**删掉那个字段声明**:
让误用变成**编译错**,而不是运行时白屏。
同样是 `body_preview`(`omitempty`,值是 `Body` 的截断)——
空正文 ⇒ 空串 ⇒ 服务端省略 key ⇒ `undefined.length` 抛。那批 96 封恰好都有正文,
所以"看起来没问题"——那正是这个坑的形态。已加 `?? ''`。
══ ④ 新判据:`omitempty` 字段的读法(形状,不是实例)
从 `server/internal/models` **算出**"只以 omitempty 形式出现过"的字段名
(不在判据里手抄名单),再扫鸿蒙侧对它们的裸成员调用。
★ 关键:**不能按字段名一刀切** —— 我第一版就是这么写的,报了 6 处、4 处误报:
`session_alias` 在服务端有**两个**声明(`Mail` 上带 omitempty、`repo.Contact` 上不带),
鸿蒙那 4 处读的全是 `Contact` ⇒ 恒有值、不是 bug。
⇒ 只扫"从未不带 omitempty 出现过"的名字,那 4 处自动排除。
已逐个核实 4 处豁免(每条都写了取证理由,不是"看着像就放过")。
变异验证:把 `?? []` 去掉 → 判据转红,且**正是**报 `MailStore.ets: mail.attach_count = mail.attachments.length`。
══ ⑤ 数据路径已实测(模拟器)
临时把 `INBOX_PAGE_SIZE` 提到 200(因为有附件那两封在下标 51/52,
默认 limit=50 **根本取不到** —— 这也解释了为什么之前一直没发现),
加临时 hilog 后拿到:
AttProbe: mail=531a1629-… attach=1
AttProbe: mail=b68cbbe8-… attach=1
正好是那两封。验完已撤掉探针、`INBOX_PAGE_SIZE` 恢复 50。
══ ⚠️ 本轮**未能**完成设备端视觉验收
模拟器已卡死(`hdc` 能连上但 `shell` 超时;进程 152% CPU、已跑 32 小时),
导致套件里的设备判据各跑 836 秒后失败("要能拉起应用")。
主机可用内存只剩 ~3.7GB。附件区的**渲染**(清单外观、下载落盘)
尚未在设备上看过 —— 待模拟器恢复后补。
|
2026-09-20 21:14:20 +08:00 |
|
|
|
21132647bc
|
跨端: 12 处「深色下字看不见」的真 bug + 判据基建补上「看像素」这一层
## 一、判据基建:本目录终于能**看像素**了
此前只能靠 `dumpLayout` —— 那是**结构化描述**,报的是"组件声明了什么",
不是"屏幕上画成什么样"。两者会分叉,而观感类结论只能在像素上得出来。
新增 `lib/harmony-device.mjs`:`screenshot()` / `pixelAt()` /
`hexToRgb()` / `closeColor()`(用 ffmpeg 转 1×1 原始 RGB,不引依赖)。
**它当场证明了它的价值**:`cross-client-theme` 新增的设备判据
用真实像素抓到下面这个 bug —— 静态判据全绿时它藏得好好的。
## 二、真 bug:**12 处**把 `Theme.surface` 当前景色用
`Theme.surface` 是 `sys.color.ohos_id_color_list_card_bg` ——
一个**跟随系统主题翻转**的 Resource:浅色近白、**深色近黑**。
- 浅色下当白字用**碰巧对**(白字压蓝底)
- **深色下字变成黑的**,压在品牌蓝 / danger 红 / warn 琥珀上**几乎看不见**
设备现场:写邮件悬浮球是品牌蓝 `#2563EB`,截图里那个铅笔图标**几乎是隐形的**;
读圆心像素得到 `rgb(32,34,36)`。往左偏 50px 读到底色才见 `rgb(36,99,235)`。
`Theme.accentFg`(`#FFFFFF`)的注释原话就是「品牌底上的文字」—— 为这个场景存在,
却**一处都没用**。
修:12 处 `fontColor/iconColor(Theme.surface)` → `Theme.accentFg`
(`MainPage` 10 + `InboxPage` 1 + `SessionsPage` 1)。改完全仓 0 处残留。
另在 `Theme.ets` 给 `surface` / `accentFg` 都补上"能当什么、不能当什么"的注释。
## 三、判据(两条,都做了变异验证)
1. **设备条**(`cross-client-theme`):读悬浮球像素 ——
① 品牌色**真的画成** `#2563EB`(声明 ≠ 渲染);
② 球上图标与底色 **WCAG 对比度 ≥3:1**(压在上面的东西得看得见)。
把 `.accentFg` 改回 `.surface` ⇒ **判红**;还原 ⇒ 绿。
2. **静态防线**(同文件):全局 grep「`fontColor/iconColor(Theme.surface)`」一处不许有。
设备条只能看一处,而这个错法有 12 处 —— 静态防线管住整类。
## 四、判据自身踩的三个坑(都写进注释了)
- **采样点撞上图标**:第一版取球心,读到 `rgb(32,34,36)`,差点当成"品牌色没渲染"。
截图一看球是蓝的,深色那点是**铅笔图标**。⇒ 往中心左偏 30% 球宽。
- **假设错了 FAB 的位置**:按"屏幕右下角"找(`x1 > 屏宽*0.6`),
实测 `[942,1997]`(`x1=942` vs 阈值 1910)⇒ 永远找不到、**静默跳过**。
原因是列表窗格是**左栏**,球在"左栏的右下角"。⇒ 形状只用站得住的那部分(下半部)。
- **设备判据要自己搭现场**:不加自导航时它**永远跳过**(前面的判据把前台留在管理页),
而那看起来像"功能没了"。加自导航后立刻开始工作并抓到 bug。
## 五、欠账
- `harmony-maildetail-missing-three` → **count 0(结算)**:三块都做完了
(转发 `b7c5d8b` / 改名建议 `c2f35d1`+`e79a86a` / 往返预算 `ac62daf`)。
如实记着**未验**的那点:预算条的**点击**没在设备上走通
(模拟器顶部 155px 是系统手势区,折叠头部恰在其中)。
- `static-criteria` 5:`cross-client-theme` **升级了一半**,仍留在名单里 ——
`.ets` 那半只有悬浮球这一处上了设备,其余令牌仍是静态对齐。
- `debt-visibility` 登记 `cross-client-theme` 1 处边界声明(带出处)。
`run-all.mjs` → `checks=513 pass=513 fail=0 skip=0 red=0 broken=0 unreported=0`;
Go 侧 `./internal/repo/...` 通过。
|
2026-09-19 19:29:49 +08:00 |
|
|
|
1bf687f506
|
判据: 图片上传链的设备判据(用真实素材)+ 静态欠账从 6 减到 5
继续升级到期的静态判据。这一批做 `harmony-imageprep`(上传链),
并顺手把已完成的 `harmony-admin` 移出欠账名单。
## 一、图片上传链:用**真实素材**验压缩决策的输入
`run-all.mjs` 的 `STATIC_ONLY` 里那条登记写着
「上传链的设备侧:`@ohos.multimedia.image` + 相册要设备才能真跑」。
上面 30 条判的都是 `model/ImagePrep.ts` 的纯逻辑(阈值、单调性、边界…),
它们全绿时有一件事从未验过:**那些数字与设备上真实的图片对得上吗**。
新判据用**用户真上传过的那张壁纸**(`GET /me/appearance/image` 取回,
1402×1122 / 152570 字节 —— 不是合成图),断三个跨端事实:
① 设备上读到的像素尺寸 = 决策时用的尺寸;
② 真实体积在客户端上限之内;③ 该尺寸走 `planCompress` 首档**不缩小**
(长边 1402 < 2560)+ `judgePick` 放行。
★ **为什么不合成图**:纯色能压到几 KB、噪声几乎压不动,用它们验阈值
会得到"怎么都对"的假绿。真实照片的行为才是要验的那个。
★ **诚实标注了没做的那一半**(写在判据注释与 `DEBTS.json` 里):
本判据用的是**设备上的 `file`/`ls`** 这一独立来源读素材属性,
**没有**跑 `image.createImagePacker()`。跑它需要一个**用户选图**入口
(`DocumentViewPicker`,要人操作系统选择器),自动化里没有稳定路径;
而编一个"绕过选择器直接调 `packJpeg`"的测试专用入口,会是**只有测试在用的代码**
——那种代码不会被真实场景触到,验它等于验一个不存在的东西。
## 二、静态欠账 6 → 5(还完就划掉)
`harmony-admin` 那条**已升级为设备判据**(上一批做的),
所以它**不该再留在 `STATIC_ONLY` 里** —— 那个名单是给"还欠着的"记账的。
留着会让余额虚高,而这正是这个机制要防的(欠账不显形就等于没有)。
## 三、途中被两条"登记一致性"判据拦了两次(都按它们给的方向修)
1. `debt-visibility`:我在 `harmony-imageprep` 里新增了两处边界声明
("未覆盖/未验"这类词),而余额里没登记 ⇒ 红。
**按它要求的顺序做**:先补 `docs/DEBTS.json`(`harmony-p4c-boundary-decls`
那一笔的 note 里写明"只做了一半"),再把登记次数 4 → 6。
2. `commit-hygiene`:`DEBTS.json` 说 `static-criteria=6`,实测 5 ⇒ 红。
—— 这条正是"可见的那个数字是副本,漂移了必须两边一起改"。
改数字之外还在那一笔里写了**为什么减**(admin 升级并移出名单)。
★ 第三处被拦很有意思:`debt-visibility` 是**按词表数自己**的判据,
我第一次修时在那个文件里写了一句话里含"仍未覆盖",于是它把自己数多了 1 处
(12 → 13)。那一刻是**判据在正确地工作**("多一处即红")——
我写的其实是**引用**另一笔账,不是新的边界声明,所以改成了不带判定词的措辞。
## 四、判据
`run-all.mjs` → `files=32 ran=32 checks=507 pass=507 fail=0 skip=0
red=0 broken=0 unreported=0`。`harmony-imageprep` 30 → 31;
`harmony-admin` 移出静态名单(仍在套件里,28 条)。
`hvigorw assembleHap` 成功;前端重建。
**剩余到期未升级**:`harmony-appearance`(已有 2 条设备判据)、
`harmony-logic`、`cross-client-theme`、`appearance-defaults` —— 逐条来。
|
2026-09-19 16:18:10 +08:00 |
|
|
|
81621974b9
|
跨端: 日历「新建」按钮补文字(原先只有一个加号)+ 详情页三处缺失登记
## 一、日历的「新建」按钮(对齐 WebUI 的主动作)
WebUI `CalendarView.tsx:362-369` 那个按钮是工具栏里**唯一的实心按钮**:
`px-2.5 py-1 bg-blue-600 text-white` + `<PlusIcon/>` + **「新建」两个字**。
鸿蒙原先只有一个光秃秃的 `Button('+')` —— 丢掉的不只是文字,是**主动作这层语义**:
用户得猜"这个加号加什么"(加日程?加订阅?加日历?)。
改成蓝底 + 图标 + 「新建」,与 WebUI 同形。**设备实测截图确认**。
★ 与这条对照的是它左边那两条「导入/导出」:鸿蒙**有意**用文字而非图标,
理由已写在代码注释里(WebUI 有 `title` 可悬停,手指没有悬停)。
同一个道理在主动作上更成立 —— 主动作不该是个谜语。
## 二、邮件详情页缺三块功能(审计发现,服务端都已支持)
对照 `MailView.tsx` 逐段核对,鸿蒙 `MailDetailPage.ets` 缺:
| 功能 | WebUI | 服务端 |
|---|---|---|
| `RenameProposalBar` | `MailView.tsx:324` | ✅ `rename_proposal.go` 已在解析 `propose_alias` |
| `ForwardBar`(转发) | `MailView.tsx:378` | ✅ `forward.go`(含 `forwardSubject` 的 Fwd: 叠加处理) |
| `BudgetEditor`(预算) | `MailView.tsx:235` | ✅ `max_rounds` 字段 |
改名建议那条尤其重要(WebUI 注释:「Agent 干到一半自己改掉,人上一秒记住的
地址下一秒就失效」)—— 是否决制而不是 Agent 单方面改,这是**寻址稳定性**的设计。
三条都**没有**在这批里硬做:各含交互 + 接口 + 状态,不是顺手能补的量级。
登记进 `docs/DEBTS.json`(`harmony-maildetail-missing-three`)——
硬塞的结果是每条都半成品,那比缺着更糟(缺着是可见的,半成品是不可见的)。
## 三、审计中确认**已对齐**的部分(不是漏做)
- 通信:工具条三页签分组与顺序、列表行的字段集(发件人/主题/摘要/时间/未读点/
账号徽标/档位徽标)、会话折叠与组头(别名/主题/未读/封数)、空态文案、
发件箱的三处差异(数据源/`to_name`/不筛权限)、归档确认框。
- 日历:工具条分组与顺序、宽屏两栏(左网格 + 右 400vp 常驻)、右栏三态、
月/周/日三档网格与农历小字、非本月淡出、今天高亮、事件圆点、
点某天的行为(宽屏换右栏 / 窄屏滑入)、导入导出的三条结果分支。
- 联系人:卡片/列表两视图、字段集、归档确认框(含破坏性操作的二次确认)。
- 「我的」:八个分段的字段与顺序、壁纸选择器、主题三态、多账号入口。
- 管理页:列与操作、管理员门禁。
- 外壳:侧栏三项 + 底簇、底栏四项、徽标三档色调与位置、玻璃材质、让位。
## 判据
`run-all.mjs` → `checks=504 pass=503 fail=1`(唯一那条是 build-stamp 的
产物过期,重建后 7/7)。`hvigorw assembleHap` 成功。
`docs/DEBTS.json` 新增三条登记(断点差异 / 列表附件数 / 详情页三功能)。
|
2026-09-19 15:19:57 +08:00 |
|
|
|
f655453424
|
跨端: 审计补强 —— 邮件行的「抄送 N」+ 断点差异登记 + 两处"看起来有其实没有"的澄清
继续「全面对齐 WebUI 和鸿蒙」。这一轮做的是**逐页对照审计**(子代理通道被
session daemon 的端口占用堵死,改为自己逐处读源码对照)。
## 一、补上邮件行的「抄送 N」(真缺失)
WebUI `MailList.tsx:350` 行上有「抄送 N」,鸿蒙**完全没有**。而数据一直在:
`cc_list` 服务端确实返回(实测回包字段列表里有),只是鸿蒙的 `MailLike`
接口漏了这个字段 ⇒ 一封抄送给多个人的邮件在列表里看不出任何区别。
修法:接口加 `cc_count`,`MailSummary` 加 `cc_list` + `cc_count`,
收件箱/发件箱两处填充派生值,行上按 WebUI 的位置渲染。
**设备实测**(造了一封带 2 个抄送的真邮件):行上出现「抄送 2」,
位置与 WebUI 一致(主题行下方、灰字)。
★ 接口用**数字**而不是 getter:`MailSummary implements MailLike`,
而 ArkTS 的 interface 里不能声明 getter(编译报 "incorrectly implements interface")。
## 二、有意**不抄**附件标记(发现 WebUI 那段是死代码)
WebUI 行上还有一个 📎 + 数量的标记(`MailList.tsx:351-355`)。但核对服务端
**实测回包**:`GET /me/mail/inbox` 既没有 `attachments` 也没有 `has_attachments`
⇒ `mail.attachments?.length ?? 0` **恒为 0**,那个标记在 WebUI 上**从不出现**。
所以鸿蒙这一轮**有意不抄它** —— 照抄一个不工作的东西,只会多一处
"看起来有、永远不亮"的代码。要做这个功能得先让服务端在列表回包带上附件计数
(一次 JOIN 的事),那是独立的一件事,已登记进 `docs/DEBTS.json`
(`mail-list-attachment-count`)。
★ 这一条与鸿蒙 `MailSummary.has_attachments` 那个字段一起处理掉了:
它还留在那里会误导人(服务端永不返回它 ⇒ 恒 false)。
## 三、两端宽屏断点不同 —— 登记 + 判据(此前**无任何记录**)
审计点名要核实的这条确认成立:
WebUI: `NARROW_QUERY = '(max-width: 1023px)'`
含义 = 「三栏(60 导航 + 320 列表 + ≥520 详情 ≈ 900px,再加余量)放不下就退化单栏」
鸿蒙: `isWide = width >= 768`
含义 = 「要不要显示**侧栏**」(鸿蒙内容区是一个窗格,没有并排三栏)
**含义不同,所以数值不同本身不算错** —— 这与手势阈值同一条口径
(语义各自成立时,数值不必强求一致)。但**用户可见的后果**是:768–1023 宽
(常见竖屏平板、窄窗口)下 WebUI 是单栏+底栏、鸿蒙是侧栏+内容,
同一宽度在两端长得不一样。
处理:① 登记进 `docs/DEBTS.json`(`wide-breakpoint-divergence`,
带三个待定选项);② 加判据 —— 它**不**要求两端取值相同,而是要求
「取值可读 + 含义写清 + 差异被登记」三件事同时成立。
★ 写这条判据时又踩了同一坑:用 `code()` 读注释 ⇒ 永远红。
`criteria-hygiene` 判据的头顶就写着"判代码用 code、判理由用 prose"。
## 四、判据
`run-all.mjs` → `checks=505 pass=505 fail=0 skip=0 red=0 broken=0 unreported=0`
(cross-client-theme 15→16)。`hvigorw assembleHap` 成功。
**未验**:附件标记(有意不做);抄送行在**深色**下的对比度未单独验。
|
2026-09-19 15:13:51 +08:00 |
|
|
|
28e3da76d4
|
跨端: 左右滑动翻页(P6 第 3 步)+ 还 gesture-semantics 债 + 修跑不起来的判据基建
★ 这一轮从用户一句「滑动手势呢?」开始。查下去发现它不是"顺手加个手势",
而是 `docs/DEBTS.json` 里挂着的一笔债 —— `gesture-semantics` 的原话是:
「P6 第 3 步:鸿蒙侧出现滑动手势代码时**立即建**判据
(此前建 = 只有一端存在的假判据)」
也就是**先有手势、再钉语义**。WebUI 2026-09-14 就有滑动翻页(用户当时
亲口提的),鸿蒙一直没有 ⇒ 之前建判据会是空真(∀x∈∅)。
## 一、手势本体(两端语义逐项对齐,数值各自定)
按 `HARMONY-ALIGN-PLAN.md:118-128` 显式选的 **(b) 口径**:
「手势的物理量本来就不该强求同值……该对齐的是**语义层**」。
· 判定逻辑放**纯逻辑层** `model/Calendar.ts` 的 `judgeSwipe`(可被 node 直跑,
写在 .ets 里就跑不了判据,语义没法被单测钉住)。四道门:位移 / 纵向优先 /
快滑窗口 / 方向。
· 阈值**不引用** WebUI 的 40 / 1.5 / 600(那是把巧合当契约),
各自定为 56vp / 1.4× / 700ms,并在注释里写出取值依据。
· 接线在 `CalendarPage.ets`:`PanGesture({direction: Horizontal})` +
`onActionStart`(记时 —— `GestureEvent` **没有时间戳字段**,我查了 SDK
的 gesture.d.ts 确认)+ `onActionEnd`(读 offsetX/offsetY)。
· ★ 挂在**网格列**上而不是整页:右栏(日程/编辑器)里有输入框与可滚内容,
整页挂会让「在表单里横划一下」变成翻月。
· ★ 翻页复用 `shiftRange()`(与 ‹ › 按钮**同一个来源**)—— WebUI 的注释
专门交代过:各写一套的话,阈值、边界、三档行为迟早分叉。
手势回调里**不准**直接改 year/month(锚点是唯一真相,年月只能由
`shiftRange → syncYearMonthFrom` 派生)。
**有意差异(记录在案,不是漏做)**:WebUI 在周/日档会额外检查「触点是否落在
可横向滚动的区域里」,是则让给滚动条(用户 2026-09-14 报过这个冲突)。
鸿蒙周档是「一行 7 格按 layoutWeight 等分」、**不横滚** ⇒ 该条件不适用。
哪天加了横滚必须同时补上它。
## 二、判据(8 条语义契约 + 行为层)
新增 `cross-client-gesture.test.mjs`:两端都真有手势 / 方向映射逐项相同 /
纵向优先 / 快滑窗口 / 复用同一翻页函数 / 无边界回弹 / 有意差异被记录 / 自检。
**只比语义、不比数值**,并反向断言鸿蒙的阈值常量不得直接取 WebUI 的那三个数。
`harmony-logic.test.mjs` 加行为判据:真跑 `judgeSwipe`,把四道门各自验一遍
(只钉字符串的话,一个 return 写漏了照样全绿)。
## 三、顺手修掉的三处**判据基建**缺陷(不修就没法验证上面这些)
1. `run-all.mjs` 只认 `# pass N`,而 node v24 打的是 `ℹ pass N`
⇒ **21 个文件被记成"没自报条数"**、套件在 HEAD 就恒红(memory 里记过这条,
修法也记过,今天终于落进代码:4 个正则加 `(?:#|ℹ)`)。修完 `unreported` 27 → 0。
代价是暴露出一批此前被"没自报"掩盖的真实问题(下面 4~6 条)。
2. `harmony-nav` 的设备判据 `navItemsOf`:宽屏过滤条件从「左边缘靠左 1/6」
改成「**整个盒子在侧栏轨道内**」。旧条件把**日历网格的格子**
(实测 `[229,511][366,794]`)也当成导航项,一屏数出 11~12 个。
这是设备实测抓出来的 —— 我第一版还以为是"底部簇混进来了",
打印真实数据才发现是隔壁页面的格子。
3. `harmony-logic` 两处:从 `NAV_ITEMS` / `NAV_SIDEBAR_ITEMS` **各自的数组**里取
label。原先把全文件 `label:` 一网打尽 ⇒ 得到 7 个(4+3 混在一起),
任何一边改对了它都会红。
## 四、被判据拦住后的正经修法(每条都按判据自己给的方向改,不改判据迁就代码)
· `cross-client-theme` A2 拦住我:新增的 `navActiveBg`/`navBrandFg`/`badgePlain`
未登记;又拦住我:`sseColorOf` 里四个裸色值。→ 抽成 `Theme.sseConnected` 等
四个令牌(取值对齐 WebUI 的 Tailwind 类)+ 登记 + 在 Theme.ets 的表里写理由。
· 同一条判据的"死令牌"检出:`Theme.durBase` 声明了却从没人读。
**查 WebUI 才发现壁纸淡入是真有的动效**(`index.css:742-752` 的
`.app-backdrop` 从 opacity:0 → 1,180ms)⇒ 补上而不是删令牌
(删掉等于把差异抹平、还说成"清理")。reset 在 `animateTo` **外**,
与 `calPaneIn` 同一条纪律。
· 玻璃登记:`NavItemBuilder` → `SidebarItem`(重构后按最近的 @Builder 命名),
登记同步跟上。
· `harmony-admin` 的退出判据:退出逻辑抽成 `api/Logout.ets` 的 `performLogout()`
(两个入口——「我的」页与侧栏底簇——必须做同一件事,尤其"先注销推送 token"
那一步)。判据相应改成**追到实际执行处**(两半都断:按钮调了 + 函数真清了全部),
并写明"别再退回直接匹配 SETTINGS_PAGE 的写法"(那会随重构假红,
下一个人只会去改判据而不看行为)。
· `harmony-widescreen` 的连接点色值:色值搬进 Theme 后,判据改成断
「令牌定义对了 + 侧栏真的用了它」两半(只断任一半都有假绿形态)。
· `align-refs`:`CalendarView.tsx` 变了,按判据要求**读一遍差异**再更新登记
(差异只有农历小字的灰阶档位 gray-300→gray-400/500,**骨架未变**)。
· `criteria-hygiene`:`harmony-contacts` 自造了一个 `code()`、`harmony-widescreen`
裸用 `readFileSync` ⇒ 都改用 `lib/read.mjs` 的共享入口。
途中撞出一个**判据自己的 bug**:`code()` 的块注释正则
`/\*[\s\S]*?\*\//` 会把注释里出现的 `/*`(如 `/」**` 这种中文夹星号)
当成块注释起点,一路吃到几十行后的 `*/`,把中间的 import 全吞掉 ——
于是 hygiene 判据假红"没 import"。改掉那处写法后正常。
## 五、验证
判据面:`run-all.mjs` → `files=32 ran=32 checks=497 pass=497 fail=0
skip=0 red=0 broken=0 unreported=0`。
其中新/改判据:gesture 8、nav 18、widescreen 7、logic 30、admin 27、
cross-client-theme 15、criteria-hygiene 6。
构建:`hvigorw assembleHap` 成功;前端 `npm run build` + 重新打 AppImage/deb
(`build-stamp` 7/7、`packaging` 5/5)。
**设备实测(HATriple 三折叠 3184×2232,hdc 连 127.0.0.1:5555)**:
· 农历在格子里真的显示(1=二十 / 7=廿六 / 19=**初九** / 11=八月),与 WebUI 一致;
这条同时验证了**服务端农历路由已部署**(之前线上是 404)。
· 日历左右两栏:编辑器出现在**右栏**、左栏月份仍可见(单栏模式下编辑器会整页盖掉它)。
· 侧栏 3 项 + 底部一簇;徽标回到图标右上角。
**仍未验(如实标注)**:滑动翻页的**手感**(阈值 56vp/1.4×/700ms 是否合适)
只能真人滑过才知道;我只验了判定逻辑与接线形态。动画同理 ——
机制已验证(`animateTo` 驱动 + reset 在窗口外),但"看起来顺不顺"未做取样验证。
docs:`DEBTS.json` 销掉 `gesture-semantics`(并记结算说明)、
`HARMONY-ALIGN-PLAN.md` P6 从「✅(滑动翻页除外)」改为 ✅。
|
2026-09-19 14:01:21 +08:00 |
|
|
|
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 |
|
|
|
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 |
|
|
|
f8fe14d124
|
docs(debts): 登记 pi-bridge-adopt-cwd-mismatch —— 沙箱 rw 与 worker cwd 的第三个来源(接管路径)未对齐,且这类错位没有任何一层提示
|
2026-09-15 06:47:24 +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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|