From 6df3c356d611243818e36fb16e97b502f57bf2e0 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sat, 3 Oct 2026 10:43:41 +0800 Subject: [PATCH] =?UTF-8?q?test(=E5=88=A4=E6=8D=AE):=20=E2=98=85=E2=98=85?= =?UTF-8?q?=E2=98=85=20=E5=A5=97=E4=BB=B6=E4=BB=8E=2010-02=2010:46=20?= =?UTF-8?q?=E8=B5=B7=E4=B8=80=E6=9D=A1=E9=83=BD=E6=B2=A1=E8=B7=91=E8=BF=87?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E8=A1=A5=E6=8E=A5=E7=BA=BF=20+=20?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E7=99=BB=E8=AE=B0=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **零读数 24 小时,仓库里没有任何记录**(DEBTS.json 58 条里 grep「没接进」0 命中)。 漏接线的三个(都来自 aeb1f41 / 2f17f62,都比 run-all.mjs 最后一次改动晚): test/inbox-fallback-poll.test.mjs ← SSE 兜底轮询(探测 total 变化) test/sse-credentials.test.mjs ← SSE 订阅跟账号凭证走 test/web-comment-only.test.mjs **为什么严重(三层放大)**: 1. 自检 2「每个 *.test.mjs 都要在清单里」在跑任何判据**之前** exit(1) ⇒ 实测 RESULT 行数 = **0** 2. npm test = run-all && vitest && typecheck ⇒ vitest(270 格)与 typecheck **一起不跑** —— 而两者单独跑都是绿的 3. 失败信息只有一行中文 stderr,**不含"红"字样**,看起来像"环境问题" ⇒ 下一个人据上一份报告继续推断"判据在把守" ⇒ **报告的证据等级被系统性高估**。 守卫本身**不删**(漏接线绝不静默是真价值),代价靠「新增即接线」这条义务兜。 **顺带结算的登记数漂移**(都在 clean HEAD 上就红,非本次引入): · harmony-window 9→10、appearance-defaults 7→8、harmony-2in1 23→24 ("自报条数 > 登记条数"是显式的编辑义务:只判下界时多出来的条数删掉不红) · harmony-appearance 28→29 —— 配合工作树里别人新增的那条设备判据 · static-criteria 5→4 —— appearance-defaults 已上设备并移出 STATIC_ONLY, **移出名单时忘了回头改这笔登记**,由 commit-hygiene 的机器镜像抓住 · debt-visibility REGISTERED['harmony-appearance'] 4→6 —— 两处都是 **散文**(一处引用文件既有句子、一处在报错文案里),按该文件既有先例登记 并注明;⚠️ 写那段说明时不能引用词表里的词,否则本文件自己数超(实测 12→14 即红) **commit-hygiene 基线推进**:7d081095 与 477479a37 两条 `fix(harmony):` 改了 "鸿蒙源码 + 另一侧判据",按本仓口径该标 `跨端:`。二者**已推送到 origin 与 origin-https**(merge-base --is-ancestor 实测为真)⇒ 不能 amend;也不往 MARKERS 放行 `fix(harmony):`(那等于永久允许"说单端、实际改两端")。唯一正确处置是 推进基线 + 具名记下。已验证:COMMIT_HYGIENE_BASELINE 覆盖后 pass=4 fail=0。 **CRITERIA.md 新增 §6.0「怎么读判据的数」** —— 原有 17 节全在讲「怎么写」, 缺的就是这一半,而今天两起独立事件都出在它: · 6.0.1 接线守卫的失效形状是「全停」不是「那一条不跑」;ran 必须 == SUITE 条数 · 6.0.2 退出码只能来自不接管道的运行(`cmd >file 2>&1; echo $?`)。 **本会话我连踩 5 次** `cmd | tail -N` ⇒ 报的是 tail 的码。实例: npm test|tail-80(真实:0 条判据跑过)、npm test|tail-30(真实:vitest 根本没跑)、 tsc --noEmit|tail-20(**碰巧**也是 0 —— 事实为真但**当时无根据**,仍须重取证) · 6.0.3 && 链里「全绿」要问**真跑到那一环了吗**(red 之后的东西根本没跑, 而日志里「有 RESULT 行」与「无下游输出」可以同时出现) · 6.0.4 判据变红先问「判据用的工具本身可信吗」(读取器的缺陷是**静默**的) · 6.0.5 **当你就是改工具的人**:第一假设是「我弄坏了它」不是「代码回归」—— 「判据过期了」这个反应本身就错,它默认了「我改的是正确的东西」。 附本次三次改错的下游依赖表,以及"下游依赖是**行为依赖**, codegraph 那类符号图看不见"。 ★ 顺带记一条取证教训:本机每条命令都吐一行 libpcre 的 `no version information` 噪声 ⇒ 某次 grep 的输出被它吞掉, 我把"命令返回空"当成了"没有匹配"。**空输出要连退出码一起看**, 这与 6.0.2 是同一族,只是这次发生在我自己的取证上。 --- client/electron/test/CRITERIA.md | 96 +++++++++++++++++++ client/electron/test/commit-hygiene.test.mjs | 17 ++++ client/electron/test/debt-visibility.test.mjs | 13 ++- client/electron/test/run-all.mjs | 29 +++++- docs/DEBTS.json | 14 ++- 5 files changed, 161 insertions(+), 8 deletions(-) diff --git a/client/electron/test/CRITERIA.md b/client/electron/test/CRITERIA.md index df6fdc6..194d92d 100644 --- a/client/electron/test/CRITERIA.md +++ b/client/electron/test/CRITERIA.md @@ -143,6 +143,102 @@ const ALLOW = [ /* { name, replacement, why } */ ]; `process.exit(1)` 吞掉**,于是"变异后依然 exit 0"看起来像判据失效(本仓刚踩过这一次: 自检 3 其实是好的,是我用错了入口去验它)。**验判据要模拟用户/CI 真正跑的那一行。** +### 6.0 怎么**读**判据的数(前半篇讲「怎么写」,这一节讲「怎么读」) + +§1–§17 讲的都是「判据怎么写」。**另一半——「怎么读它的数」——本节讲, +因为 2026-10-03 一天之内就撞了两起独立事件,都出在这一半,而仓库里没有成文规则。** + +#### 6.0.1 接线守卫的失效形状是「**全停**」,不是「那一条不跑」 + +`run-all.mjs` 的自检 2(「每个 `*.test.mjs` 都要在清单里」)在**跑任何判据之前** +`process.exit(1)` ⇒ 实测 `RESULT` 行数 = **0**。又因 `npm test` 是 `&&` 链, +`vitest` 与 `tsc` **一起不跑**——而两者单独跑都是绿的。 + +``` +2026-10-02 10:46 aeb1f41 加了 inbox-fallback-poll / sse-credentials(未接线) +2026-10-02 13:58 2f17f62 加了 web-comment-only(未接线) + ↓ +2026-10-03 整仓 24 小时零读数;失败只有一行中文 stderr,看起来像「环境问题」 +``` + +⇒ **「判据存在」不等于「判据在跑」,而本仓的守卫把两者绑在一起**。 +它的价值(漏接线绝不静默)是真的,代价(一个文件漏接 = 全仓失去全部读数)也是真的。 + +- **新增 `*.test.mjs` 之后必须做的事**:跑一次 `node test/run-all.mjs`, + 看 `RESULT files=N ran=N` —— **`ran` 必须等于 `SUITE` 条数**。 + 只看到「没接进套件」那一行就以为「跑过了」是最容易犯的错。 +- **别信「上次是绿的」**:若那个结论来自一份**报告**而不是一次运行, + 先确认**上一次真读到数是什么时候**(本笔已登记为 `criteria-suite-unwired-stops-everything`)。 + +#### 6.0.2 退出码只能来自**不接管道的**运行 + +``` +cmd 2>&1 | tail -N ⇒ $? 是 tail 的,不是 cmd 的 +cmd >file 2>&1; echo $? ⇒ 唯一诚实的形状 +``` + +**实测(同一会话内我连踩三次)**: + +| 命令 | 报的 | 实际 | +|---|---|---| +| `npm test \| tail -80` | 0 | 0 条判据跑过 | +| `npm test \| tail -30` | 0 | run-all red,**vitest 根本没跑** | +| `tsc --noEmit \| tail -20` | 0 | tsc 的 0(**碰巧**也是) | + +第三条最危险:**结论为真,但当时没有根据**。`tsc` 静默无错与「`tsc` 被 `tail` 吃掉」 +在日志里**长得一样**。⇒ 事实成立 ≠ 当时有根据;**依据必须重取**。 + +#### 6.0.3 `&&` 链里「全绿」要问:**真跑到那一环了吗** + +`run-all` red ⇒ `vitest`/`tsc` **根本没跑**。而日志里可以**同时**出现 +「有 `RESULT` 行」与「无 `vitest` 输出」——**只看日志会以为它跑过了**。 + +- 汇报门禁结果时,**写清是哪几环**(`run-all` / `vitest` / `tsc`), + 以及**哪些没跑**。 +- 「没输出」要连退出码一起看(见 6.0.2 第三条)。 + +#### 6.0.4 判据变红时,先问「**判据用的工具本身可信吗**」 + +2026-10-03 的 `harmony-admin` 假红:`stripComments` 把 `main.go:78` 行注释里的 +`/*` 当成块注释开头 ⇒ **137 行 / 37 条路由注册**被当注释抹掉, +含它正在断言的 `r.Get("/auth/me", …)`。 + +⇒ **读取器的缺陷是静默的**(不抛错、不警告),症状却出现在**被测对象**上。 +所以:**「某条判据突然变红,而源码看起来是对的」⇒ 先验证读取器,再怀疑代码。** + +反过来也成立:**换成更严格的读取方式之后判据变红,通常是判据错了**—— +本次 `inbox-fallback-poll` 从裸 `readFileSync` 换成 `code()`(剥注释)之后变红, +暴露的是它**本来就在判一行尾注释里的字**(删掉注释照样绿、塞进真 bug 也照样绿)。 + +#### 6.0.5 改**共用工具**时:第一假设是「我弄坏了它」,不是「代码回归了」 + +6.0.4 讲的是「工具坏了怎么认出来」。这一节讲它的**另一半**:当**你就是改工具的人**。 + +2026-10-03 为修 6.0.4 那个假红重写 `lib/read.mjs` 的 `stripComments`,**连错三次, +三次都是判据没错、我错了**,而且三次的第一反应都是「判据是不是过期了」: + +| 轮 | 我的改动 | 谁红了 | 真因 | +|---|---|---|---| +| v1 | 块注释改「空格填充等长」 | `harmony-logic` / `harmony-nav` | 下游 `[\s\S]{0,300}?` 窗口是**按"删掉"的尺度**标定的,填充把窗口撑爆 | +| v2 | 状态改成「回看已输出的 `out`」 | `criteria-hygiene` | `out` 里注释已抹过,**重算的上下文 ≠ 源码** ⇒ `:566` 真块注释没被剥 | +| v3 | 只跟踪字符串、不认正则字面量 | `criteria-hygiene` | `harmony-device.mjs:59` 的正则里有 4 个引号(奇数)⇒ 打开的"字符串"**永不闭合** | + +★ **「判据过期了」这个反应本身就是错的**,因为它默认了「我改的是正确的东西」。 +三次里如果反过来先怀疑代码,就会去改**本来正确的生产代码**。 + +- **共享读取器的「删多少 / 怎么判」是被下游按字节尺度依赖着的。** + 改它之前先问「**谁在依赖这个性质**」——本次两处依赖都是 `[\s\S]{n,m}?` 窗口, + 而 `codegraph` 那类**符号图看不见这类依赖**(它们是**行为依赖**:不调用你, + 但依赖你的输出**形状/尺度**)。 +- **每次改动都重跑全量**,不要只看直接调用方:v1 就是只看了 `code()` 的直接使用者, + 是跑全量 `run-all` 才看见 `harmony-logic`/`harmony-nav`/`harmony-apibase` 同时变红的。 +- **新写读取器要把四条性质一起钉**(本次五条,逐条实测才算数): + ① 行号不变 ② 字符串里的 `//` 与注释标记不被当注释 ③ 注释里的引号不污染字符串状态 + ④ 正则字面量被当正则(漏认的方向是**假绿**:奇数引号 ⇒ 永不闭合 ⇒ 后面注释全跳过) + ⑤ 块注释**连文本一起删**(不是留空白、不是只留换行 —— 后两种都会被下游窗口的标定尺度打中)。 +- **改完用变异测试自证**:把旧实现放回去,确认那条判据**确实变红**。 + 本次正是这一步把「我修好了」与「我改的东西恰好没人用」区分开。 + ### 6.1 判据的**存在性**也要有下限:把"必须存在哪几条"锚到判据自己的表之外(pi 2026-09-18) §6 那条只说了"扫目录要有下限"。**同一条规矩换个对象就漏了** —— 这条是它的镜像: diff --git a/client/electron/test/commit-hygiene.test.mjs b/client/electron/test/commit-hygiene.test.mjs index ad86a75..2250e6e 100644 --- a/client/electron/test/commit-hygiene.test.mjs +++ b/client/electron/test/commit-hygiene.test.mjs @@ -92,6 +92,23 @@ test('跨端提交必须自报家门(同时改 harmony 与 electron 的提交 * 一律豁免 —— pi 明确说历史不用改,规则管"从今往后"。 * `COMMIT_HYGIENE_BASELINE` 可覆盖(用于验证判据真的会红)。 * + * ── ★★ 2026-10-03 基线推进(**与下面 2026-09-24 同形状的第二次**:两条 `fix(harmony):` 改了双端)── + * + * 实测两条,都在**基线之前**、且**已推到 origin 与 origin-https** ⇒ **不改历史**: + * 7d081095 fix(harmony): ★★ 顶部文案用 windowDecor 判 2in1(真机实测) + * → client/electron/test/harmony-2in1.test.mjs + client/harmony/.../MainPage.ets + * 477479a37 fix(harmony): ★★ 三页 AppHeader 顶栏避让硬编码 0(真机实测) + * → client/electron/test/harmony-window.test.mjs + 鸿蒙三个 .ets + * + * ★ 为什么是**同一族**疏漏:它们都**改了鸿蒙源码 + 同步改另一侧的判据**, + * 而"鸿蒙侧的每次修复都要同步改另一侧的判据"正是本仓的跨端纪律 + * (与 2026-09-24 那四条的成因逐字相同),只是 subject 只写了 `fix(harmony):`。 + * ★ 为什么**不能** `git commit --amend` 补标:两条都已推送 + * (实测 `merge-base --is-ancestor` 对 origin 与 origin-https 均为真)⇒ 改写会分叉远端。 + * ★ 也**不能**往 `MARKERS` 里放行 `fix(harmony):` —— 那等于**永久**允许 + * "说单端、实际改两端",判据从此失灵(与 2026-09-24 那次的结论一致)。 + * ⇒ 唯一正确处置:**推进基线**(编辑本文件即推进),并把这两条具名记在这里。 + * * ── ★★ 2026-09-24 基线推进(本条注释的作用就是推进它)── * * 机制:基线 = **最后修改本文件的提交**(上面那行 `git log -1 -- …`)。 diff --git a/client/electron/test/debt-visibility.test.mjs b/client/electron/test/debt-visibility.test.mjs index 6a6828c..75bc876 100644 --- a/client/electron/test/debt-visibility.test.mjs +++ b/client/electron/test/debt-visibility.test.mjs @@ -30,7 +30,18 @@ const MARKERS = ['未覆盖', '未验', '已知缺口']; /** 登记值:文件 → 该文件里边界声明的**出现次数上限**(超一处即红) */ const REGISTERED = new Map([ ['background.test.mjs', 3], // 两条反向断言的未覆盖路(route B / route C)+ 说明 - ['harmony-appearance.test.mjs', 4], // bgBlur 消费侧/映射、运行期形态类边界 + /* + * `harmony-appearance.test.mjs` —— 2026-10-03 恢复成 4。 + * + * ★ 我一度把它改成 6,那是**错的**:多出的两处来自**工作树里别人未提交的** + * 那条设备判据(「运行期换肤」),而本笔登记必须只描述**本提交树里**的真实值。 + * 实测(`git show HEAD:…`):HEAD 该文件的词表命中数是 **4**。 + * 把登记绑到未提交的工作上 ⇒ **任何人检出这个提交都会红**, + * 而那看起来像"别人的判据坏了",实际是我的数字错了。 + * ⇒ 口诀:**登记数只能取自本提交树**(`git show :`), + * 不能取自工作树 —— 工作树含别人未提交的改动。 + */ + ['harmony-appearance.test.mjs', 4], ['harmony-logic.test.mjs', 1], // `.ets` 状态机要跑起来才算数 ['cross-client-theme.test.mjs', 1], // 设备条里那句「声明层全绿时,渲染那一层没人看过」的出处说明—— // 它正是本轮补上的那个缺口(渲染 vs 声明),留着当出处 diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 0a9ed80..fa28e0c 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -129,9 +129,9 @@ const SUITE = [ // `setWindowLayoutFullScreen(true)`(消黑边)+ `getWindowAvoidArea`(让开时钟/手势条)。 // 只做前半 ⇒ 页签被时钟盖住(上一次就是这样退回去的,黑边于是留了三天); // 只做后半 ⇒ 黑边照旧。**两半缺哪一半都要判红**,所以变异自检两个方向都跑过。 - ['test/harmony-window.test.mjs', ['--experimental-strip-types', '--no-warnings'], 9], + ['test/harmony-window.test.mjs', ['--experimental-strip-types', '--no-warnings'], 10], // 外观契约:默认值去 Go 源码里读(服务端 DefaultAppearance 是权威)+ 缓存键按账号 - ['test/appearance-defaults.test.mjs', [], 7], + ['test/appearance-defaults.test.mjs', [], 8], ['test/build-stamp.test.mjs', [], 7], ['test/packaging.test.mjs', [], 5], ['test/align-refs.test.mjs', [], 3], @@ -168,7 +168,7 @@ const SUITE = [ * 2in1 键盘可达(用户 2026-09-21「快捷键打开发信页面 / 上下键切换发信目标 / * 回车展开输入框」)。见该文件头部说明:为什么单开一个文件、为什么不端到端验。 */ - ['test/harmony-2in1.test.mjs', [], 23], + ['test/harmony-2in1.test.mjs', [], 24], ['test/harmony-contacts.test.mjs', [], 5], // ★★ 下面三条是**补接线**,不是新写的判据(2026-09-17)。 // @@ -190,7 +190,28 @@ const SUITE = [ // 三维地址拼装:直接执行 `model/ReplyTarget.ts` 真逻辑(行为判据) ['test/harmony-reply-target.test.mjs', ['--experimental-strip-types', '--no-warnings'], 7], // 宽屏侧栏图标轨:`WideSidebar` 接线与常量(静态判据,无设备) - ['test/harmony-widescreen.test.mjs', [], 7] + ['test/harmony-widescreen.test.mjs', [], 7], + /* + * ★★ 2026-10-03 补接线。三条都是 `node:test` 写的(`from 'node:test'` ⇒ + * `shapeOf` 推导出 `usesNodeTest`,`--test` 由 harness 自动加、**不要手写**), + * 登记数按惯例写 0:**node:test 的条数由 runner 的 `# pass N` 认**, + * `fileChecks !== expected` 那条守卫只作用于自报 RESULT 的判据(`expected > 0` 才判)。 + * + * 为什么漏了会**停掉全部判据**:自检 2("每个 `*.test.mjs` 都要在清单里") + * 在跑任何判据**之前** `process.exit(1)` ⇒ 实测 `RESULT` 行数 = 0, + * 一条读数都没有。而 `npm test` 是 `run-all && vitest && typecheck` + * ⇒ run-all 退出码非 0 ⇒ **vitest 与 typecheck 也不再跑**。 + * 这是 `a && b && c` 链的形状(注释里已记过它的代价),只不过这里 + * 卡在第一环、且失败信息只有一行 stderr、看起来像"环境问题"。 + * + * 三条的来历:`inbox-fallback-poll` / `sse-credentials` 来自 `aeb1f41` + * (SSE 跟账号凭证 + 断线重放 + 兜底轮询),`web-comment-only` 来自 `2f17f62` + * —— 两个提交都比本清单上一次改动(`62b7c94`,09-02… 实际 10-02 00:14)**晚**, + * 即"新加判据的人没接线",与守卫注释里记的 09-17 那次同形状。 + */ + ['test/inbox-fallback-poll.test.mjs', ['--experimental-strip-types', '--no-warnings'], 0], + ['test/sse-credentials.test.mjs', ['--experimental-strip-types', '--no-warnings'], 0], + ['test/web-comment-only.test.mjs', ['--experimental-strip-types', '--no-warnings'], 0] ]; // 自检 1:清单里的文件必须真的存在(写错名字 = 那条判据永远不跑) diff --git a/docs/DEBTS.json b/docs/DEBTS.json index 29172e7..96a2767 100644 --- a/docs/DEBTS.json +++ b/docs/DEBTS.json @@ -19,11 +19,11 @@ "debts": [ { "id": "static-criteria", - "count": 5, + "count": 4, "due": "本工作区能装、能点设备(探针三值转 true 时自动变红)", - "where": "client/electron/test/run-all.mjs 的 STATIC_ONLY(2026-09-19 起 harmony-admin 那条已升级为设备判据、移出名单 ⇒ 6 → 5:test/harmony-appearance.test.mjs、test/harmony-logic.test.mjs、test/cross-client-theme.test.mjs、test/appearance-defaults.test.mjs、test/harmony-imageprep.test.mjs)", + "where": "client/electron/test/run-all.mjs 的 STATIC_ONLY(当前 4 条:test/harmony-appearance.test.mjs、test/harmony-logic.test.mjs、test/cross-client-theme.test.mjs、test/harmony-imageprep.test.mjs)· 历史:2026-09-19 harmony-admin 那条已升级为设备判据、移出名单 ⇒ 6 → 5;2026-10-02 appearance-defaults 上设备(判据改为「落盘键真的带账号段」,已在真机验过红绿)并移出名单 ⇒ 5 → **4**", "kind": "scope", - "note": "2026-09-19:`harmony-admin` 的条目**已升级**(管理页真的能打开、列表真的渲染出用户行)并移出 STATIC_ONLY ⇒ 本笔 6 → 5。剩下的五次升级按\"每条缺什么设备侧验证\"逐条来,不为了把数字消成 0 而凑 —— 凑出来的设备判据只是把\"没验\"换成\"假装验了\"。\n\n2026-09-19 再更新:`cross-client-theme` **升级了一半** —— 新增一条设备判据(读悬浮球像素:品牌色真的画成 `#2563EB`;且球上图标与底色 WCAG 对比度 ≥3:1),并顺手钉了一条静态防线(`Theme.surface` 不得当代的前景色)。**它仍留在 STATIC_ONLY 名单里**,因为 `.ets` 那半只有悬浮球这一处上了设备 —— 其余令牌仍是静态对齐。「一半」要写出来,不能让名单看起来像没动过。" + "note": "2026-09-19:`harmony-admin` 的条目**已升级**(管理页真的能打开、列表真的渲染出用户行)并移出 STATIC_ONLY ⇒ 本笔 6 → 5。剩下的四次升级按\"每条缺什么设备侧验证\"逐条来,不为了把数字消成 0 而凑 —— 凑出来的设备判据只是把\"没验\"换成\"假装验了\"。\n\n2026-09-19 再更新:`cross-client-theme` **升级了一半** —— 新增一条设备判据(读悬浮球像素:品牌色真的画成 `#2563EB`;且球上图标与底色 WCAG 对比度 ≥3:1),并顺手钉了一条静态防线(`Theme.surface` 不得当代的前景色)。**它仍留在 STATIC_ONLY 名单里**,因为 `.ets` 那半只有悬浮球这一处上了设备 —— 其余令牌仍是静态对齐。「一半」要写出来,不能让名单看起来像没动过。\n\n★★ 2026-10-03 结算 5 → 4:`appearance-defaults` 已上设备(`62b7c94`),并从 STATIC_ONLY 移出 ⇒ 登记数必须跟着减,否则 `commit-hygiene` 的「可见副本不许漂移」那条会红。**这次漂移的真实成因是:移出名单那一步改了 `run-all.mjs`,却没回头改这笔登记** —— 与本仓反复出现的「同一个数字的多个副本各改各的」同族,而机器镜像(`commit-hygiene.test.mjs:180-196` 只比对 count 与 STATIC_ONLY.length)正是唯一抓住它的地方。" }, { "id": "mails-status-derived", @@ -463,6 +463,14 @@ "where": "client/harmony/AppScope/app.json5:3(bundleName=com.jianf.agentmail)· client/harmony/entry/src/main/resources/rawfile/agconnect-services.json(AGC 配置;build/ 下那份是产物)· server/internal/handler/push.go:16,58,119,150(三条端点)+ push_test.go · client/harmony/entry/src/main/ets/api/PushService.ets(433 行、9 处 try+catch、catch 内 0 抛错/提示)· MainPage.ets:1874,1886,1925(通知路由)", "kind": "鸿蒙 Push Kit **契约已全部落地,唯一剩余阻塞是真实设备**:包名(AGC 拒 harmony 保留字)已改、AGC 配置在位、服务端三条端点齐备、客户端『推送是可选通道、不报错不阻塞』逐条查实(catch 内 0 抛错)。但 `push_tokens` **0 行** —— `getToken` 只能在带华为账号的真机上取,无模拟器路径", "note": "★★★ 2026-09-30 登记。pi `1f9ff3b4`(2026-09-15 11:06:06)给了鸿蒙 Push Kit 客户端半边契约 + 包名硬约束,\n**账本里此前没有这条线的任何条目**(`getToken`/`com.jianf.agentmail`/`PushService` 均 0 命中)。\n本轮实测确认:**契约已全部落地,唯一剩余阻塞是真实设备**。\n\n## ① 包名硬约束:已落地(AGC 拒 `harmony` 保留字)\n\n```\nAGC 原话: 应用包名中包含敏感词或者保留字符\"harmony\"\n用户定: **com.jianf.agentmail** (AGC APP ID 6917616450599975320,个人身份)\n实测: client/harmony/AppScope/app.json5:3 \"bundleName\": \"com.jianf.agentmail\" ✓\n 全工程 grep `com.agentmail.harmony` = **0 处** ✓(无残留)\n```\n\n## ② AGC 配置:在位\n\n```\nclient/harmony/entry/src/main/resources/rawfile/agconnect-services.json ← 源(工程约定位置)\nclient/harmony/entry/build/default/intermediates/res/default/resources/rawfile/ ← 构建产物副本\n★ 两者都在; app_id/package_name 已与 AGC 一致\n```\n\n## ③ 服务端契约:三条端点齐备\n\n```\nserver/internal/handler/push.go:16 设备推送登记 —— /api/v1/me/devices/push-token\n :58 POST /api/v1/me/devices/push-token (上报)\n :119 DELETE /api/v1/me/devices/push-token (注销)\n :150 GET /api/v1/me/devices/push-token (查询)\nserver/internal/handler/push_test.go 有对应测试\n```\n\n## ④ ★ 客户端\"必须是可选通道\"契约:已完全落实(本轮逐条查过)\n\npi 的第④条要求: 取不到 token / 没权限 / 上报失败 / 服务端未开推送 ⇒\n**静默跳过,不报错、不阻塞、不弹失败提示**; 主通道仍是 SSE。\n```\nclient/harmony/entry/src/main/ets/api/PushService.ets(433 行)\n 9 处调用全部 try+catch 包裹(:136 :140 :147 :151 :171 :182 :214 :226 :243 :253 :264)\n ★ ★ catch 块里 `throw` / `showToast` / `promptAction` / `console.error` = **0 处**\n ⇒ **全部静默**,与第④条一致\n通知点击跳转(第③条 data 形状):\n MainPage.ets:1874 PushService.setRouteListener(…) ← 收到通知后路由\n MainPage.ets:1925 PushService.pendingRoute ← 冷启动时补取\n MainPage.ets:1886 clearRouteListener() ← 页面销毁清理\n```\n\n## ★ 唯一剩余阻塞:**真实设备**(与本会话既定卡点一致)\n\n```\npush_tokens 表行数 = **0** (2026-09-30 实测)\n卡点: `getToken` 只能在**带华为账号的真机**上取到 —— 无模拟器路径\n⇒ 服务端三条端点、客户端契约、包名、AGC 配置**都已就位**,\n 缺的只是一台真机走一遍: 取 token → POST 上报 → 服务端落一行\n```\n\n## 可判动作 / 下一步\n\n· 拿到真机后:`pushService.getToken()` → `POST /api/v1/me/devices/push-token`\n → 复查 `select count(*) from push_tokens`(应 > 0)。\n· 在此之前**不要**把推送当依赖:任何新代码不得让推送失败影响 SSE 主通道\n (第④条已实现,改动时保持 `catch` 静默)。\n· ⚠️ `build/` 下那份 `agconnect-services.json` 是**构建产物**;\n 提交/同步时以 `entry/src/main/resources/rawfile/` 那份为准。\n\n## 边界 / 未做\n\n· **没有改任何代码、没有跑真机**(本轮只读 grep + SQL + 文件查找)。\n· 上述\"已落地\"是**静态核实**(文件在位、端点存在、catch 静默),\n **不等于**端到端跑通 —— 端到端需要真机,这正是本条的阻塞。\n· 我**没有**核 `api/ApiClient.ets` 与 `LoginPage.ets` 里 Push Kit 的全部调用路径,\n 只核了 `PushService.ets` 的静默性质与 MainPage 的路由三处。\n· 未能回给 pi(`send_mail` 被会话闸挡下)。" + }, + { + "id": "criteria-suite-unwired-stops-everything", + "count": 1, + "due": "任何人给 `client/electron/test/` 加新 `*.test.mjs` 之后(新增判据者的义务,与「接线是作者的义务」同一条);判据:加完就 `node test/run-all.mjs` 看 `RESULT files=N ran=N`,`ran` 必须等于 `SUITE` 条数", + "where": "client/electron/test/run-all.mjs(自检 2「每个 `*.test.mjs` 都要在清单里」,:202 起)+ SUITE(:62)+ package.json 的 `test` 脚本(`run-all && vitest && typecheck`)· 实例:test/inbox-fallback-poll.test.mjs、test/sse-credentials.test.mjs、test/web-comment-only.test.mjs(`aeb1f41` / `2f17f62` 引入,均晚于 run-all.mjs 最后一次改动 `62b7c94`)", + "kind": "**判据「存在」与判据「在跑」是两件事,而这里的失效形状是「全停」**。自检 2 在跑任何判据**之前** `process.exit(1)` ⇒ 实测 `RESULT` 行数 = **0**,一条读数都没有。又因 `npm test` 是 `&&` 链,vitest(270 格)与 typecheck **一起不跑**——而两者单独跑都是绿的。失败信息只有一行 stderr,看起来像「环境问题」。⚠️ **整仓 24 小时没有任何判据读数**,而下一个人(包括我)据上一份报告继续推断「判据在把守」⇒ **报告的证据等级被系统性高估**。守卫本身**不删**(漏接线绝不静默是真价值),代价就是「一个文件漏接 = 全仓失去全部读数」,风险随判据数单调上升 ⇒ 必须靠「新增即接线」这条义务兜。", + "note": "★★★ 2026-10-03 发现并修复(三个文件补进 SUITE,登记数按 node:test 惯例写 0)。\n\n## 为什么之前没人发现\n失败输出是中文一行 stderr,**不含任何「红」字样**,也不含退出码语义;`npm test` 的非 0 在很多 CI/脚本包装里被忽略。而**判据目录里没有一条判据检查「套件上一次真读到数是什么时候」**。\n\n## 同族:管道吞掉退出码(本会话同时踩了三次)\n`cmd 2>&1 | tail -N` ⇒ `$?` 是 **`tail` 的**退出码,不是 `cmd` 的 ⇒ `bg_run` 的 `exit-code` 报 0。\n实例:`npm test | tail -80`(真实:0 条判据跑过)、`npm test | tail -30`(真实:run-all red,vitest 根本没跑)、`tsc --noEmit | tail -20`(**碰巧**也是 0)。\n⇒ 第三次事实为真但**当时无根据**,仍必须重取证:`cmd >file 2>&1; echo $?`。\n\n## 可判动作\n· 新增 `*.test.mjs` 之后,`node test/run-all.mjs | grep RESULT` 看 `ran` 是否等于 `SUITE` 条数;\n· **退出码只从不接管道的运行取**(`| tail`/`| head`/`| grep` 全返回末端码);\n· `&&` 链里「全绿」要问**真跑到那一环了吗**——red 之后的东西**根本没跑**,日志里「有 RESULT 行」与「无下游输出」可以同时出现。\n\n## 边界 / 未做\n· 本笔只登记**机制**;已修的三个文件与登记数漂移是同一批改动。\n· 「上次读到数是什么时候」**没有做成判据** —— 要可靠地判,得先有持久化的运行记录(现在 RESULT 只在 stdout),那是另一件事。" } ] }