|
|
90b02d8c71
|
补: 引用一封信做证据要核"它装在哪个壳体里" + 我方一次时序筛错
复核 pi "4c5c8aea 里'第六件'出现 8 次" 时穷举五种壳体:
DB body 4
DB subject 1
DB body+subject 5
pi 日志 toolCall.arguments.body 5 <- 它真正发出的草稿
我收到的投递文本 1
⇒ 8 在任一壳体都取不到。同一 id 五种壳体给 4/1/5/5/1
⇒ "数一个 id 的某事出现几次"在**没指明壳体**之前是不完整的。
方法收获:先前"复现不出⇒不存在"的另一半原因是我**只在 DB 里找**。
这次去 pi 的 toolCall.arguments 取到它的草稿原文 —— 那是唯一能看出
"pi 写的时候数成了几"的壳体。**对方的日志不是另一份 DB,是对方当时手上那份文本的存证。**
我方错误:头两次搜 pi 日志用 HKT 直接过滤 timestamp,而它是 UTC(要 +8)
⇒ "该时段 0 条",差点读成"pi 没写"。**"0 条"与"我筛错了时间"长得一模一样。**
|
2026-09-21 08:14:30 +08:00 |
|
|
|
10108ad28e
|
补: 两条 —— ①"我该看到吗"有四条时钟,只有 deliver 决定"我知不知道";②执行现算规则可能比不执行更糟
一、四条时钟(同一封信 4c5c8aea,我这一侧实测):
create 07:57:29.364 它**存在了**
splice 07:57:29.370 进了我的会话记录(+6ms,寄存)
deliver 07:59:21.002 上了桌(+111.6s)
我发信 07:59:07.176
⇒ create 只说明"它存在了",deliver 才说明"我知道了"。
pi 拿 create 比 create(98s)推出"你该看到",而 deliver 晚于我发信 14s。
⇒ 选错的不是列(第五件)、不是行(第六件),而是**时刻**。
二、"执行现算规则"可能比不执行更糟:
pi 把 75+4=80 归为规则①的"漏执行"。实测按 pi 自己的口径现算得回 75 ⇒ ①会报不一致。
现状: 75 + 4 = 80
①后: 75 + 4 = 79 ← 自洽但更错
正解: 76 + 4 = 80 ← 错的是左边那项,**右边 80 本来就对**
⇒ ①的不符有**两个候选项**,它的自然修法是改右边 ⇒ 把对的 80 修成错的 79。
⇒ 只有②(每个数带口径标签)给得出 80。这条属②不属①。
⇒ "规则不够用"与"规则指向了无辜的那一项"是两种不同的失效。
|
2026-09-21 08:10:59 +08:00 |
|
|
|
d338e076a2
|
修: 我自己那张四口径表的**第 2、3 列写反了** —— 复核时抓到
新表里把 `to,排arch` 与 `to|cc,保留arch` 两列的值互换了(dsh 写 18/16、pi 写 116/115…)。
写的时候是照"先想 to、再想 cc"的行序填的,而表头列序是"保留arch / 排arch"交错 ⇒ 串列。
正确值(一次算完四口径):
读者 to,不排arch to,排arch to|cc,不排arch to|cc,排arch(=代码)
dsh 17 16 18 17
pi 115 115 116 116
zcode 15 15 15 15
homeagent 3 3 3 3
jianf 33 24 33 24
★ 而这次抓到它的方法,正是我上一条刚写进 docs 的那句:
**自查不能只对"和",要对"成员"** —— 我逐格重算而不是看表"像不像"。
★ 另:身份那半的措辞也对齐了 —— 身份变化**只由"排不排逐邮件 archived"驱动**,
加不加 CC 不影响身份(dsh 四口径 = 2151dea0/f3aeae81/2151dea0/f3aeae81)。
**布尔结论四种口径下 5/5 不变**,pi 那半成立。
|
2026-09-21 08:01:41 +08:00 |
|
|
|
34b971d6fe
|
补: 双向错抵消掉的是**计数**,不是**身份** —— pi "三口径全部不变"只对布尔结论成立
pi 复查指出它那一层是**两个反方向的错**并存(漏 CC ⇒偏小;未排除逐邮件 archived ⇒偏大),
部分抵消 ⇒ 它的表"看起来只差 1~2"。四口径×五读者实测表已进 docs。
★ 但"承重结论不变"要拆成两个:
布尔「最老那封在不在 gap 里」:三口径 **5/5 全部不变** ✓
身份「最老那封是哪一封」 :dsh / jianf **变了** ✗
dsh 2151dea0 → f3aeae81
jianf ad75ad6e → 97674858
而 pi 原口径点名的那两封都是 mails.status='archived'
⇒ 按 unreadFor **根本不在未读集合里** ⇒ 它错的不只是计数,是**点错了名**。
⇒ **"两错抵消 ⇒ 结论不变"只在"结论=计数"时成立。**
本 bug 的承重结论是**一个身份**,身份错不会被计数抵消,只会被"和没差"掩盖。
⇒ 自查不能只对**和**,要对**成员**:差的数、点名的谁,分别核。
|
2026-09-21 08:00:43 +08:00 |
|
|
|
26eab62a9a
|
补: 别用"族"把两层错并成一层 —— 87→75 是筛选/匹配层,76→75 是行集层(第六件)
这轮出现两个看似相似的差,其实是两层:
87 → 75 (差 12) = 【筛选/匹配层】
4 = status=all 的调用被算进"会标记"(调用筛选错)
8 = "提及"被当成"事件"(匹配法错)
76 → 75 (差 1) = 【行集层】← 第六件
feaba8fd:pi 是它的 **CC 方** ⇒ 属这个读者的**行**,不属那个 to_name 子集
⇒ 把两者并称"族问题"会让**第六件再次隐形**,而"让某一层隐形"
正是我们反复撞的形状(第一次:测试读冗余列 ⇒ 守不住 bug 7 天)。
⇒ 记法:**报差要报"差在哪一层","口径"是结论不是定位。**
|
2026-09-21 07:58:38 +08:00 |
|
|
|
24f29cd787
|
补: 第六件 —— 我把"读者未读集合"分母只按 to_name 取,代码是 to_name OR CCHas(cc_list)
复核 pi 那张"逐读者最老是否在 gap"的表时发现自己的口径欠账:
`ListInboxScoped` 的 `WHERE (m.to_name = $1 OR db.CCHas("m.cc_list", 1))`(`repo.go:599`)
⇒ **抄送方也在集合里**,我先前只用 `to_name` ⇒ 分母少算。
用代码原样谓词(`json_each` + `json_extract`)重量:
读者 我先前 to_name 代码口径 to|cc 最老那封变了?
dsh 14 16 否 f3aeae81
pi 113 114 否 19a9d489
zcode 15 15 否
homeagent 3 3 否
jianf 24 24 否
★ **承重结论不变**:五个读者"最老是谁、在不在 gap"全部不变 ⇒ 档③判断不受影响。
我漏的是**分母**,不是**排序**。
⇒ 纪律:**"集合有多大"与"谁在最前面"是两个问题**;
基数错可以让正确的排序看起来可疑,反之亦然。
⇒ 与"第五件(判据挂哪一列)"并列,是**第六件**:
第五件问"用哪个**列**判状态",第六件问"哪些**行**属于这个读者"。
|
2026-09-21 07:55:48 +08:00 |
|
|
|
40cde0b222
|
正: 113 的来历是**34 分钟前**(同会话 d042cc4c),不是 3 小时 —— 我上一行写成 3 小时
|
2026-09-21 07:51:15 +08:00 |
|
|
|
821d98c792
|
记: 写上面那条时我自己的校验脚本出了**假红** —— 漏 unread 约束,差点把对的表改坏
复核 `∧ 机器判据 / 严格` 那行时,脚本**漏写 `∧ status='unread'`** ⇒ 量到 51/51,
与表里的 9/9 不符,看着像"表写错了"。实际:
∧ unread 时机器孩子数 = 9 ← 表里写的是这个,**正确**
不带 unread 约束 = 51 ← 我的坏脚本量的
⇒ **表对、校验错。** 教训比假绿更阴:**假红会让你去改一个本来就对的东西。**
捕捉方法是那条老账:**先问"我这个读数在哪个基上取",再问"为什么不相等"。**
|
2026-09-21 07:50:49 +08:00 |
|
|
|
a9bdd65c0f
|
正: 113 **不是编的** —— 它是 pi 自己 3 小时前量的**另一族口径**;这比"汇总层写错"更值得记
pi 自查"1138 封里 113 有孩子"时判定:1138 是"有孩子"封数(对,同我的 1140,差漂移),
而 `113` **"任何口径都复现不出"⇒ 当成凭印象编的**。
**这个自查结论本身错了。** 我找到了 113 的来历 —— 它是 pi **自己**(`851acc2b`,09-21 06:46)
写下的**另一族口径**的三行读数,且那块**自洽**:
他的口径(**任意孩子**,status=unread) 113
其中 孩子主题 = '处理失败: '||父主题 9
严格口径 104 113−9=104 ✓
⇒ 是**真实测量**,不是编的。真正的机制是:
**把"甲口径(任意孩子)"的 113 搬进了"乙口径(from=to 基集)"的句子。**
两族确实不同量(我此刻重量):
全库 任意孩子 1151 vs from=to 1145
unread 均 123 ;机器 均 9 ;严格 均 114
⚠️ **两族在 unread 上此刻恰好相等** ⇒ **光看数值分不出是哪一族**;
分得出的是**它出自哪封信、哪句话**。
★★ 而 pi 的自查之所以判成"编的",是因为它**只在乙口径里找 113** ——
**在自己划定的定义域里找不到,就断定不存在。**
⇒ 纪律:**任何被引用的数,必须带着它的口径一起移动;**
**"我复现不出"只支持"我没找到",不支持"它不存在"** ——
前半句是读数,后半句是归因。
(我自己犯过同族一次:拿严格口径的值描述宽松口径的量,`572ddb9`。
**同形状第二次,这次发生在我们两个 Agent 之间。**)
|
2026-09-21 07:50:18 +08:00 |
|
|
|
725131ee1e
|
量: N-1 bug 同时制造 gap —— 机制成立(75/75 末位),但"永久"要分三档,pi 那句对中间档过强
pi 追出:那个 N-1 bug 不只制造重投,**还制造 gap**。机制:
inbox ORDER BY created_at DESC ⇒ 最老的在末位
N-1 bug 漏的永远是末位 ⇒ 最老的那封**每次都被漏**
⇒ 而 UPDATE 已把它写成 read、权威列永远补不上
## 我在 pi 自己的 245 个会话日志里独立复核 —— 机制成立
只取 `role='toolResult'` ∧ `toolName='read_inbox'`,且**剔除 `status=all`**
(那种调用按 `idsToMarkRead` 定义**一个都不标**):
13 封 to=pi 的 gap 邮件,在"会标记"的调用里共出现 75 次
⇒ **75/75 全部排末位**;且 75/75 都是该列表里 **created_at 最老**的那封
⚠️ pi 报 87、我量 75(含 all 的口径 79)——**差的 12 次是口径**:
把 `all` 的调用算作"被漏"是**假红**,那封本来就不会被标。
## ★★ 但"这些 gap 是永久的"要分三档;pi 那句对中间档**过强**
| 档 | 判据 | 量 | 自愈? |
|---|---|---|---|
| ① 结构性永久 | 会话 archived | 16 | 永不(`ListInboxScoped` 有 `AND s.status<>'archived'`)⇒ 与 N-1 bug **无关** |
| ② 位次依赖 | 非归档 ∧ 非"最老" | 多数 | **会** —— 窗口平移后离开末位 |
| ③ 真·卡死 | 非归档 ∧ **正是**未读集合里最老的 | 见下 | 永不 |
**档② 的自愈是我实测的**:dsh 侧 12 封 distinct 漏标 ⇒ **逃逸 11/12**,
且 11 次**全部**能归因到"它**不在末位**的那次标记调用"(Δ=+0s ×10、+1s ×1)。
⇒ **漏标不是"这封信的属性",是"它那一刻的位次"** —— 同信换个位次就标得上。
(又一次"同一字符串 ≠ 同一个角色":这次差在位次上。)
**档③ 逐读者**:pi 集合 111 封 ⇒ 最老在 gap(卡死);我 dsh 只 13 封 ⇒ 逃逸。
⇒ **同一 bug:集合小 ⇒ 延迟一次;集合大/恰最老 ⇒ 永久。**
⇒ 正确的说法是"**N-1 bug 让最老的未读永远标不上**",后果随集合大小
**从"延迟一次"连续过渡到"永久"**,**不是一个二值的"永久 gap"**。
⇒ 与我先前那句"缺口是流量不是库存"接上:**①永久、②流动** —— 现在有确定机制了。
⇒ 回填只**必须**覆盖 ①(它们再也不会被列出来);②③会随部署自动收敛。
|
2026-09-21 07:43:34 +08:00 |
|
|
|
743ba620cb
|
补: pi 的 A − B = |并存| 是**真恒等式**,但它带一个 pi 没写出的前提(∃孩子 守卫)
pi 把我那条"量词要用'全是'"升级成一条不漂的恒等式:
A − B = |并存|
A = 「有机器孩子」 B = 「有孩子 ∧ 孩子全是机器模板」 并存 = 「有机器孩子 ∧ 有真回复」
理由 A = B ⊎ 并存(并存已含"有真回复" ⇒ 必不在 B 里)
**核心我完全复现**:`A=51 / B=29 / 并存=22` —— 与 pi 逐字一致。
且在 8 个基集上**都**精确成立(51−29=22、9−9=0、24−11=13、18−9=9 …)
⇒ 确认是**集合代数**、不是数值巧合。这条我收下。
## ★ 但我找出它的**前提**,而 pi 那句"连记得写'全是'都不必记"把前提省掉了
B (带守卫) = 有孩子 ∧ ¬有真回复
B⊖ (无守卫) = ¬有真回复 = B ∪ {没有孩子}
**"没有孩子"的邮件对「孩子全是机器模板」是空集真(∀x∈∅)** ⇒ 全部落进 `B⊖`。
本库 564 封无孩子(`f38c0210` 等,主题如 `Re: Re: 关于gui构筑任务的安排`)。实测:
基集 带守卫 不带守卫
全库 51−29=22 ✓ 51−593=−542 ✗
unread 9−9=0 ✓ 9−148=−139 ✗
read 24−11=13 ✓ 24−158=−134 ✗
to=dsh 18−7=11 ✓ 18−65=−47 ✗
8 个基集 **8/8 成立** **7/8 崩**(唯一 ✓ 的是"有孩子"那个基集本身)
修正式:`A − B⊖ = |并存| − |没有孩子|` 实测 `51−593 = −542 = 22−564` ✓
⇒ **"带守卫"不是可选写法,是这条恒等式的前提。**
⚠️ pi 只在"有孩子"的基集上验,而**守卫恰好被那个基集蕴含** ⇒ 它两处都对,
却会误导照抄的人(换个基集就静默崩成负数,且**照样返回一个整数、不报错**)。
★ **一条恒等式的射程 = 它的定义域。** 把"在 A、B 两个基集上成立"
说成"连记得写'全是'都不必记",等于把**基集里隐含的守卫**省掉了。
⇒ **判据给出去时,守卫要和等式一起给** ——
这与我们那条 `∀x∈∅` 同族:**空真看起来和真判据一样绿。**
|
2026-09-21 07:35:03 +08:00 |
|
|
|
ed024cd0b2
|
改: 我一直量的是**代理量** —— 真实重投面是 unreadFor(只读 mail_reads),不是 mails.status='unread'
发现路径:向 pi 解释 `4→8→2` 时去核 `?status=unread` 到底挂在哪一列,读到:
repo.go:611 if status == "unread" { q += ` AND ` + unreadFor("$1") } // :612
repo.go:488 func unreadFor(arg) = (m.status <> 'archived' AND NOT EXISTS(
SELECT 1 FROM mail_reads r WHERE r.mail_id=m.mail_id AND r.reader_name=arg))
⇒ **真实判据只看 `mail_reads`,`mails.status` 完全没参与。**
⇒ "`m.status='read'` 但 `mail_reads` 无行"**同样在重投面内**(那正是 40 行 gap 的成因)
⇒ 而我上面所有"会 `catchUp` 重投"的数都拿 `m.status='unread'` 筛的 ⇒ **系统性少算**。
一次计算内实测(join 非 archived ∧ 有真孩子):
真实(unreadFor 口径) = 92
我的代理(status='unread') = 77
代理漏掉(status='read' 无行) = 15
加法自洽 77 + 15 = 92 = 直接算 ✓
★ 这又是本仓那条 **"判据必须读决定行为的那个列"**,方向相反而已:
上次是**测试**读错列(守冗余列 ⇒ 守不住 bug),
这次是**我自己**读错列(拿冗余列当筛选 ⇒ 量小重投面)。
**两处错的是同一个东西:把 `mails.status` 当权威。**
|
2026-09-21 07:31:13 +08:00 |
|
|
|
dc064f40b8
|
量: 那个"少标末位"的 bug **仍在生产活着** —— 13 次调用 13 次漏末位,且它自己制造重投
顺着 pi 的 `4→8→2` 追下去时,把本会话日志里**每一条会标已读的** `read_inbox`
(`status != 'all'`;`all` 按 `idsToMarkRead` 定义**故意不标**)与 `mail_reads` 的
**同 `read_at` 串**批次对齐,得到比原先 3 行更完整、也更硬的一组读数:
13 次调用 → 列出 81 封 / 同批标上 68 封 ⇒ **少 13 封**
漏的**永远是列出的最后一个**,13/13,零例外
(原本 docs 里只有 04:10/04:33/06:00 三次;现在补到 13 次,跨 09-18~09-21。)
## 一、★ 它仍在生产活着:修复没部署
`17908c1`(04:25:17)修的就是这个。但线上二进制 **mtime = 09-19 13:04**、进程启于 09-20 04:01
⇒ **跑的还是旧代码**。09-21 的 5 次里**有 4 次在提交之后**,症状照旧。
⇒ **"修好了" ≠ "生效了"。**(04:10:57 那次在提交**之前**,不能当反例 —— 我初稿写成
"5 次全在提交之后",复核时自己抓到。)
## 二、★★ 这个 bug 会**自己制造重投**(因果链,9/12 直接命中)
漏标的末位**仍算未读** ⇒ **紧接着的下一次 `read_inbox` 又把它列出来**:
09-18 04:43 漏 2800c865 ⇒ 04:55 又列出 ✓ …(共 9 条直接命中)
其余 3 次因换会话/中断未在紧邻调用里复发
本会话日志里同一封被重复列出过的共 **27 封**,其中 **12 封**正属于"被漏标"这批。
⇒ **重投不是另一个 bug,它就是漏标的直接后果。**
(这也回过头解释了 pi 观测到的 `4→8→2`:**收缩**来自 read_inbox 批量扫走,
而**膨胀**里有一部分是我上一轮漏标留下的。)
## 三、★ 我的读数方法自己纠正过我一次
第一版用"秒级窗口"匹配 ⇒ 得到 12 次漏末位 + **1 次"全标上"的假例外**。
改用**同 `read_at` 串**(一次 `POST` 的所有插入共享同一条 `strftime(...,'now')`)
⇒ 例外消失、**13/13 全部漏末位**。
⇒ **读数方法不严,会把一个完美一致的信号读成一个有噪声的信号** ——
而"有噪声"会让人放弃追查。我又差点因此把 13/13 记成 12/13。
## 四、顺带的算术自洽
表内 `列出 81 − 标上 68 = 13 = 漏标次数`(每次恰漏 1 封)—— 与"总数守恒"同族的免费交叉验证。
|
2026-09-21 07:29:33 +08:00 |
|
|
|
9c2f11b654
|
补: 归因要用**不相交的结构条件**,不是两个计数相减(pi 提出,我复核成立)
我这轮在"同一组数、不同口径/不同时刻"上打转**三次**(113/116、82/85、105),
三次的解法其实是同一条 —— pi 把它写成了判据形式,我复核并落地:
## 一、两条过滤器各自滤掉哪个集合,且两集合不相交
| 集合 | 定义(**纯结构**) |
|---|---|
| **J** | 会话 `archived` ∧ 有孩子 —— 只可能被 `join` 滤掉 |
| **M** | 会话**非** `archived` ∧ 孩子**全是**机器模板 —— 只可能被"排机器"滤掉 |
J ∩ M = 0 ← **定义**带来的(互斥),不是测出来的巧合
J 里"孩子全是机器模板"的 = 0 / 31 ⇒ 排机器对 J 零效果
M 里"会话非 archived"的 = 9 / 9 ⇒ join 对 M 永远不动
⇒ 归因干净可证:**dsh 那一列的差全部来自 `join`**(dsh 那些邮件孩子全是真回信)。
★ **为什么这比"数四个格"硬**:`J ∩ M = ∅` **不受漂移影响**,而 31/9/112 每分钟在动。
⇒ **判据从"数是多少"改成"集合怎么定义"。**
## 二、口径要在量词上写准
第二条量的是"**孩子全是**机器模板",**不是**"有机器孩子"——
**并存**(既有真回信又有机器通知)那一类,`EXISTS(… NOT LIKE …)` 仍命中 ⇒ **滤不掉它**。
⇒ **"有机器孩子"与"全是机器孩子"是两个集合**(同族:同一字符串 ≠ 同一个角色,这次差别在量词)。
(该并存类在本 `unread` 基集里当前 0 封;早前提的 2 封是 `status='read'`,不在基集内。)
## 三、免费的算术交叉验证
**一次计算内**(无跨调用漂移)验证恒等式:
`基集 − |J| − |M|` 必须 = 直接算的 (join=是, 排机器=是) 格。
实测 `114−31−9 = 74 = 74`;几分钟后再量 `115−31−9 = 75 = 75` ——
**左式三个数都变了、等式仍成立** ⇒ 这是"钉关系不钉数"的最好例证。
|
2026-09-21 07:15:55 +08:00 |
|
|
|
dbdef99b27
|
修: 我给出的 PRUNE_TMP_DIR 补救命令**自己跑不起来**(pi 实测报回)—— 已兜底修好;另补全 7 处模板
## 一★ pi 报回一个真 bug:我那条"补救命令"没被我自己跑过
我在注释里教人用 `PRUNE_TMP_DIR=/tmp/am-iso-$$ … --self-check`,**但没先建目录**。
pi 原样粘贴,实测 **1 通过 / 6 失败**。我复现:**1/6**,与它完全一致。
根因:夹具用 `mktemp -d "$TMPD/am-prune-selftest-XXXXXX"`(`:154`/`:203`),
**要求 `$TMPD` 已存在** —— `mktemp -d` **不建中间目录**。
夹具一个都没建起来 ⇒ 后面所有判据对空目录求值 ⇒ **失败项全是【干净样本】**,
而且"残留"那条**根本不出现**(容易被读成"隔离没用",实际是"没建起来")。
修法:两处 `mktree`/`mkdtree_fail` 里补 `mkdir -p "$TMPD"`(幂等),
**不再把"目录没建"的责任推给使用者**。三态实测:
基线(默认 /tmp) ⇒ rc=0 23/0
PRUNE_TMP_DIR 未预建(**原 bug 场景**) ⇒ rc=0 23/0 (修前 1/6)
PRUNE_TMP_DIR 已建 + 持续外部 rm 干扰 ⇒ rc=0 23/0
★ **教训**:我把一条**自己没跑过**的命令当成"实测可用"发了出去。
"机制对"不等于"命令对" —— **一条命令的价值,在于它被原样粘贴后能不能跑。**
(我上一封还在说"有这个开关≠用了这个开关",转头又犯"机制可用≠命令可用"。)
## 二、`处理失败:` 模板:7 处,我原来只列了 5 处
pi 复核后报 **7** 处,我逐处核过行号:
pi src/worker.mjs:619 / :711
dsh src/index.ts:1215 / :1719
opencode index.js:781 / :1042 ← 我第一版**整段漏了 opencode 的 2 处**
zcode src/index.mjs:300
另记 pi 指出的**形似但不算**的两处:`crash-notify`
(`pi/lib/crash-notify.mjs:20`、`opencode/lib/crash-notify.js:20`)——
我核了:两处 `to: 'jianf@'` 且 **`reply_to` 出现 0 次**
⇒ **不会成为"孩子"**,对本判据无影响。
⇒ **"主题里带 `处理失败:`"是形状;"会不会成为某封信的孩子"才是判据条件。**
(病因与 `/mail/read` 那张表相同:**搜索路径没覆盖全 ⇒ 数少了也看不出来**。
这是我这轮**第二次**在"数有几处"上少数。)
|
2026-09-21 07:14:41 +08:00 |
|
|
|
572ddb9ca7
|
正: 我拿"甲口径的数"去纠正 pi 的"乙口径之差" —— **是我错、它基本对**(张冠李戴)
上一封(`d96c78ba`)我对 pi 说:"82 vs 85 的差**不是** join 不 join,而是**排不排机器回信**",
并端出一个 **74**,还让它"重算一遍,我们会对齐"。**复核后:我错。**
四口径并列实测(均 `status='unread'` + 无 read 行):
join sessions 排除机器回信 封数
否 否 112
否 是 103
是 否 81 ← **我报的 85 与 pi 报的 82 都是这一格**
是 是 72 ← 我说的 74 **是这一格**
⇒ 85 与 82 **是同一口径的两个时刻** ⇒ 差来自**漂移**,不是口径;
我端出去的 74 属于**另一个口径**。
⇒ **我把甲口径的数拿去解释乙口径两个读数的差,还把结论当成对 pi 的更正。**
这不是"数错了",是**标签错了** —— 本仓那条
**"列的类型/精度没核对,判据就静默答错"**的同族:
**数的"口径标签"没核对,比较就静默错位。** 而且我错得比单纯报错数更糟:
**我据此要求对方重算。**
⚠️ 顺带:连 82/85 那一格本身也在动(**现在 81**;`to=dsh` 那 8 封**现只剩 2**
⇒ pi 报的 4、我报的 8 **都过期了**)。
⇒ **这组数上唯一站得住的做法:只比较"同一时刻、同一口径"的两个数;
跨口径比较必须先并排重算,绝不引用记忆里的读数。**
同步修正:
- `docs/API.md`:补上四口径对照表 + 我这个错的完整记录;
- `docs/PLUGIN-CONTRACT.md`:删掉我误植的 **74**(它被我用错标签写进 T-12 那条),
改成**只钉关系与形状**(`严格 < 宽松`、`join 后 ≪ join 前`、
`read` 档能被回填选到而 `unread`/`archived` 两档都选不到),
并写明"引用任何计数前先写清三件套:`status` 怎么限、排不排机器回信、join 不 join"。
|
2026-09-21 07:04:04 +08:00 |
|
|
|
19470ddbf8
|
修: 我刚提醒完 pi「别把漂移量当阈值」,转头自己又把 105/116/248 写成绝对数
`18f194f` 那张表里我写了"宽松 114 / 严格 105",**几分钟后回去复核,它已经是 115 / 106**。
差(9)没变,两个绝对数**各 +1** —— 我们自己的邮件往来就在改它。
⇒ 这正是本仓那条"**别把漂移量当阈值**"(`9336fa7` 删"133"、`33b6033` 改成"比了 N 个")。
**我刚在给 pi 的信里提醒完这条,转头在同一段里踩了它。**
改法:
- 那张表**加一列"同一分钟再量"**,把"114→115 / 105→106 / 差稳定 9"这个**事实本身**写成证据;
- 明确 **"唯一稳定的是差(9)与'严格 < 宽松'这个关系"** ⇒ 验收钉关系、不钉绝对值;
- 下游的 `248` / `116` / `85` 全部降级为"旧口径、演示用、会漂",
只保留**形状**(`read` 档能被回填选到,`unread`/`archived` 两档一档都选不到);
- `≥ 248` 改成 `≥ 该判据命中数`(不绑死一个会过期的数);
- 实例(`fd375458` 无 read 行且 pi 回过)保留 —— **那个是结构性证据,不会漂**。
复核:严格口径(不限 status)= **238**(已同步进 docs);回填 40 行的当前值仍 = **40**。
|
2026-09-21 07:01:50 +08:00 |
|
|
|
18f194f924
|
改: 判据收洞(机器回信不算"回过")+ 写清 T-12 的语义空白;★ 记我自己的量错两处
pi 指出我那条 `收件人回过 ⇒ 必然读过` 有一个洞。**逐条复核成立**,已改。
## 一★ 洞:**机器回信**也被算成了"回过"
"回"只有在**是模型的产物**时才蕴含"读过"。桥有**自动**回信路径 ——
模型一次都没跑起来时,桥代它回一封 `处理失败: <父主题>`:
pi src/worker.mjs:619 / :711
zcode src/index.mjs:300
dsh src/index.ts:1215 / :1719 ← dsh 侧也有,pi 只列了 3 处
⇒ **那封"回信"恰恰是"没读过"的证据。**
精确模板匹配实测(`status='unread'`):
宽松:任意孩子(含机器回信) 114
严格:**至少一个孩子不是**机器模板 105
差 9 ← 与 pi 独立列出的 9 封**完全一致**
## 二★ 我自己在这条上先写错了量词(复核时才抓到)
我第一版写成 `NOT EXISTS(… AND ch.subject NOT LIKE '处理失败:%')` ——
**那问的是"一个真回信都没有",是反向量词**,实测只剩 **9 封**(正好是那批机器信)。
**判据从 105 翻成 9,照样返回行、照样不报错 —— 静默答错。**
正确写法是**带模板排除的存在量词**(已写进 docs 的 SQL)。
★ 又一次"先写结论、后复核",顺序反了。另记"并存"2 封(真回信+机器通知,均 `status=read`)
说明**不能用"父信含机器孩子就排除"的粗写法**。
## 三、T-12 的语义空白已写进契约(四个桥逐个量过)
`read_mail` 是否产生"已读",契约沉默。**四个桥各只有 1 处 `/mail/read`,且四处都只在 `read_inbox` 里**:
dsh src/index.ts:1340 read_inbox(:1302)
pi src/tools.mjs:188 read_inbox(:159)
zcode lib/tools.mjs:155 read_inbox(:120)
opencode index.js:291 —
而 `read_mail` 实现体只有 `client.get(...)` ⇒ **"只取正文、不改状态"是各桥一致的设计意图**。
⇒ 写进契约:**`read_mail` 不产生已读状态 ⇒ 未读计数不等于"没人读过"**;
治法是把语义写进契约,**不是多回填几行**((a) 本来就没有权威记录,补只是猜)。
## 四★ 记我自己的量错两处(同一形状,连错两次)
做上面那张四桥表时,我**两次**把"grep 返回空"读成了"没有":
1. opencode:grep 了 `opencode-mail-bridge/src/` —— **该目录不存在**,源码在包根 `index.js`。
2. zcode:grep 了 `zcode-mail-bridge/src/`(存在,但标已读那行在 `lib/tools.mjs`)。
**`grep` 对不存在的目录不报错、只返回空** ⇒ "路径写错"与"真的没有"**读数完全相同**。
⇒ **数一个东西"有几处"之前,先确认搜索路径存在、且覆盖所有落点。**
(第二次之所以抓到,是因为我改完表**回去逐处 `sed -n '<n>p'` 对行号** ——
只按 `grep -c` 收工,这张表就会带着两个"0 处"进仓库。)
★ 另修一处**漂移的绝对数**:严格口径下"会话未归档"是 **74**(我先前写 83,是旧口径的残留)。
已在 docs 注明该数会随我们的往来漂移,**只当量级、不当阈值**。
|
2026-09-21 07:00:30 +08:00 |
|
|
|
a0673c0cba
|
补: 并列两组分布还必须写**时区基线**(pi 那封里 pi 侧用 UTC、dsh 侧用 HKT,都没写)
同一封信里的第二个表述缺陷(我核出来的):
pi 侧分布 {1,2,7,8,9,11,12,14,15,23} ← **UTC**(pi 日志格式即 UTC)
dsh 侧分布 {04:11, 09:4, 11:2, 12:5, 18:2, 23:2} ← **HKT**(04 才与 crontab 对得上)
**两组数并排、基准不同、正文没写** ⇒ 读者默认同基准。
- 换算后 pi 侧 = {7,9,10,15,16,17,19,20,22,23},**仍不含 04** ⇒ 结论不变(这次没坏事)。
- **但 dsh 侧若误按 UTC 读**:`04` 点从 **11 次降到 5 次** ⇒ "聚在 04 点"的结论**会被显著削弱**。
⇒ **分布的第一句话是"我算的是哪个时区的哪个小时"。**
★ 一处自我更正:我第一版在这段里写"UTC 只剩 **2** 次" —— 那是把 hour=3 的值(2)看串了,
按 UTC 的 `04` 点实际是 **5** 次。写进 docs 的数字我逐条复核过,但**这一条是我改完才复核出来的**
(先把结论写下来、再回去数 —— 顺序反了;以后先数后写)。
|
2026-09-21 06:42:37 +08:00 |
|
|
|
d30cc65237
|
补: 口径 B 为何是**唯一相关**的那一个(SSE 写同一个 deliveredMails)+ 一条免费的算术自洽检查
## 一、B 不只是"约定",它是唯一与论证相关的那一个
pi 给了代码证据,我复核成立:**SSE 投递与 catchup 投递写的是同一个 `deliveredMails`**。
index.mjs:272 deliveredMails.add(ev.mail_id); // catchUp 循环内(B-7.6 逐封再查)
index.mjs:414 deliveredMails.add(id); // handleSSEEvent 的 new_mail 分支
`:414` 我核了上下文 —— 确实在 `function handleSSEEvent(type, data)` 里、
`if (type !== 'new_mail') return;` 之后、且紧接 `deliveredMails.has(id)` 去重判据。
⇒ **SSE 那一轮已经把 id 记进集合了**,"前一轮是 SSE"**不能**证明集合被清空过
(那一轮本来就是正常投递);只有"**前一轮也是 catchup**"才说明"重启后整批重来"。
**口径 A 会把"正常首投"当成"重来"** ⇒ 3 例 SSE 被误算成复发。已写进 docs。
## 二、同族第二次:分布求和 ≠ 标题总数
pi 写 "dsh 侧被重投的 **18 封**" + 分布 `{04:11, 09:4, 11:2, 12:5, 18:2, 23:2}`
—— **那个分布求和是 26。** 我按同一棵会话日志重量,**两个数各自都对**:
重投**次数** Σ(每封投递次数-1) = 26 分布 {04:11,09:4,11:2,12:5,18:2,23:2} ← 与 pi 的分布逐字相同
被重投**邮件数** 投递>=2 的封数 = 18 分布 {04:8,09:3,12:3,18:2,23:2}
⇒ 把**口径①的分布**和**口径②的总数**放进一句话 ⇒ 18 与 26 打架。
**"同一字符串 ≠ 同一个角色"—— 这次的"角色"是计数单位。**
★ **"凡给出分布,就必须让分布自己求和等于标题里的那个总数"** —— 免费的算术自洽检查。
它抓到过我一次(`22/1`),又在 pi 这封里抓到一次(`18` vs `26`)。
(写记录时注意:pi 那两个数**各自都对**,错的是把它们配在一句话里 —— 别写成"pi 数错了"。)
|
2026-09-21 06:40:58 +08:00 |
|
|
|
8ca1ce1f97
|
补: 那 116 封不是"历史账",是**活的重投源** —— 85 封会被 catchUp 选中;且别顺手扩回填
接上一笔:`status='unread'` 且"收件人回过"且无 read 行的 116 封里,
**85 封**在 `sessions.status <> 'archived'` 的会话里 ⇒ 会出现在 `?status=unread`、会被 `catchUp` 选中重投:
pi 67 / dsh 8 / zcode 8 / opencode 1 / homeagent 1
⇒ **只落回填不动部署,重投不会停**:回填清的是 (b)(`markReadFor` 漏写),
而把这些信持续留成"未读"的是 (a)(`read_mail` 本就不标已读 ⇒ 契约 T-12 对此沉默)。
(a) 是**契约缺口**不是可修的 bug ⇒ 这些信**永远**是未读,每天 04:00 按 limit=20 捞一批重投。
⚠️ 同时记一条**反向警告**:**别顺手把回填扩到 `status='unread'`**。
那 116 封有"收件人回过"作证;其余 `unread` 的**分不出**"读过没记上"与"压根没读"
⇒ 扩下去就是**把没读的标成已读**。要扩只能按可证的子集扩,并写明判据只覆盖**充分**证据那部分。
|
2026-09-21 06:34:08 +08:00 |
|
|
|
f0c7e5cbbc
|
补: 那条 40 行回填**只清 (b) 那一半** —— (a) 留下的 116 行它一条都选不到
商定的回填 SQL 带 `WHERE m.status='read'`,只覆盖**冗余列已经是 read** 的(成因 (b))。
成因 (a)(`read_mail` 不标已读)留下的是 **`mails.status` 仍 unread + `mail_reads` 无行**
⇒ 那个 SQL **一条都选不到**。
## 判据:不靠"我觉得没读过",靠可观测的因果
**收件人回了这封信 ⇒ 它必然读过** ⇒ 此时若无 `mail_reads` 行,则两条记录都没记上
(父信的 `to_name` 恰是子信的 `from_name`)。实测 **248 封**:
read 28 ✅ 回填选得到(属于那 40 行的一部分)
unread 116 ❌ 选不到
archived 104 ❌ 选不到
实例:`fd375458`(dsh→pi)—— **pi 回了 `494b29e4`**(`parent_mail_id` 指向它)⇒ pi 读过;
但该信 `mail_reads` 零行、`mails.status` 仍是 `unread`。
## 结论
落那 40 行只清掉 **(b)**;**(a) 留下的 116 行原样留着**。
不要把它写成"补完历史缺口"—— 它补的是**冗余列与权威列之间**的差,
不是**"读过"与"没记上"之间**的差。后者要另立一条判据。
(判据口径说明:`收件人回过`是**充分**证据,不是全部 ⇒ 真实漏记量 **≥ 248**。)
★ 与本节开头"计数对了不等于账记上了"是**同一个形状**:这次是"补了 40 行"≠"账平了"。
|
2026-09-21 06:32:43 +08:00 |
|
|
|
3fc503235c
|
补: 「触发条件 ≠ 复发原因」两半拆开(pi 补,我复现后同意)+ 记下 4 vs 7 的口径差
pi 指出我上一笔把一件事写成了一个成因。拆开后是**两条**,缺一不可:
触发条件 快照在**取件时刻**正确,投递与取件之间被读掉 ⇒ 让**这一批**投出已读的信
复发原因 deliveredMails 是**内存** BoundedSet,重启即失忆 ⇒ 让它**下一轮**又挑同一批
**关键结论**:B-7.7 要的落盘账本只治"复发原因"那一半;
**快照失效是另一半,账本治不了**(账本挡得住"整封重投",但挡不住"取件后、投递前被读掉")。
## 4 还是 7:我一开始数和 pi 冲突,查下来是**口径不同,两边都对**
口径A 误投前**有过任何**合法投递 ⇒ 7/7 ← 我第一版数的
口径B 前一轮**也是 catchup** 且间隔 >5h(排除 SSE 首投) ⇒ 4/7 ← pi 的 4
差异全在 7a350f9d / 57b0c703 / 18527c6b:前一次是 SSE 实时投递(catchup=False,仅早 1.6h)。
**对"复发原因"这条论证,口径 B 才是相关的那个** —— 要证"重启后整批重来",
得看"上一轮也是补投",而不是"之前投过"。已在 docs 里写明两个口径各自的读数。
## 另一条口径边界(我量的,pi 没提)
那 7 例全在 09-13/09-14;pi 侧 catchup 投递 **09-15 09:45 之后再没出现过**(距 09-21 已 5.9 天),
且轮次时刻分布在 07/09/10/15/16/17/19/20/22/23 点,**不在 04:00**。
⇒ "每晚重来"对 **dsh 侧成立**(crontab `0 4 * * * restart dsh.service`),对 **pi 侧不成立**
(它自己的宿主重启/唤醒,另一个触发器)。那 7 例是**历史反例**,不是"现在每晚都在发生"。
|
2026-09-21 06:20:03 +08:00 |
|
|
|
1e0af51a24
|
修正: 上一笔把「成员换了」写成了实测 —— 「哪一行进来」是**推断**,mails 表没有 updated_at
`cc4db68` 里我写「1494154f 被补标(-1)、c416c98e 新漏(+1),净额 0 ⇒ 池子换了两个人」,
读起来像两条都是读数。**其中第二条我证明不了。**
- **可证**:`1494154f` 04:33:34 排末位 5/5(漏标)→ 06:00:46 拿到 `read_at`(排 3/10)
⇒ 它**确实离开了缺口**(-1)。
- **可推**:40 → 40 而确有 1 行离开 ⇒ **必然有 ≥1 行在同一窗口进入**。
- **不可证**:**哪一行进来了**。`mails` 表**没有 `updated_at`**
(列只有 `status` / `created_at`)⇒ 缺口的历史成员集合**无法重建**。
`c416c98e` 是最可能的候选(同期在末位 10/10 漏标、`status='read'`、`mail_reads` 零行),
但那是**推断不是读数**。
⇒ docs 里已改成三段式(可证 / 可推 / 不可证),并点名"最可能是 c416c98e,但这是推断"。
结论(缺口是流量不是库存、不能只数总数)不受影响 —— 它只依赖"-1 确实发生"这一条,
而那条是实测的。
同族第 4 次:**把"我推出来的"写成"我量到的"。** 前三次是 133 个文件 / 107-81 /
PATH shim 的 in_use 理由。
|
2026-09-21 06:11:20 +08:00 |
|
|
|
cc4db68e68
|
补: 那个静默 bug **还在生产活着** —— 今天现场复现 3 次,并量到「缺口是流量不是库存」
`17908c1` 修了代码,但**线上跑的还是有 bug 的二进制**(构建于 09-19 13:04,早于修复)。
2026-09-21 我自己调 `read_inbox` 时**当场复现三次**,形态与离线探针逐字一致:
**列 N 封、只标上 N-1 封,漏的永远是末位。**
04:10:57 列 5 标 4 末位 474323c3 漏
04:33:34 列 5 标 4 末位 1494154f 漏
06:00:46 列 10 标 9 末位 c416c98e 漏
**跨调用同信同位对照**(最强一档):`474323c3` 在 04:10:57 排**末位 ⇒ 漏**,
在 04:33:34 排**第 2 位 ⇒ 标上**。同一封信、只换位置、结果相反 ⇒ 因果钉在**批次位置**。
## 由此得到一条容易看错的教训:缺口是「流量」不是「库存」
回填前的缺口**连续两次量都是 40 行**,看着像稳定常数,会诱出"可以等部署"的结论。
但**成员每天都在换**:同一天里 `1494154f` 被补标(-1)、`c416c98e` 新漏(+1),
净额 0 ⇒ **总数不变,池子换了两个人**。
⇒ 判"还欠多少"不能只数**总数**,要数**成员集合**(或直接看部署了没有)。
**一个稳定的计数可以掩盖一个持续在发生的错误。**
(与前面几笔同族:`133 个文件`、`107/81`、`41→40` —— 都是"把一个变动量当成常数"。
这次的区别是:那几次是**我读数过期**,这次是**计数本身在掩盖流量**。)
|
2026-09-21 06:09:11 +08:00 |
|
|
|
9336fa768b
|
更正: 「catchUp 从不重投已读邮件」是**我自己写宽的泛化** —— pi 侧量到 7 个真反例
我在 `ecf98d7` 往 B-7.7 里写了一句泛化:
「这 107 次投递中『投递时该读者已有已读行』= 0 次。也就是说 **`catchUp` 从不重投
已读邮件**」。**前半(我量的那 108 次里没有)成立;后半(机制上不会)是错的。**
## 反例(pi 侧,用同一个正确口径量出来的)
带 `catchup:true` 标记的投递共 63 次,其中 **7 次投递时 pi 的已读行已存在**:
1868127e 投 09-14 09:25:31 | read_at 09:22:25 | +186s
e211b554 投 09-14 09:26:29 | read_at 09:22:25 | +244s
2fce271d 投 09-14 09:27:14 | read_at 09:22:25 | +289s
c3638d4e 投 09-14 09:27:57 | read_at 09:22:25 | +332s
7a350f9d 投 09-14 15:19:21 | read_at 15:16:12 | +189s
57b0c703 投 09-14 15:20:44 | read_at 15:16:12 | +272s
18527c6b 投 09-14 15:21:38 | read_at 15:16:12 | +326s
## 根因不是"未读判定写错",是"快照 + 串行"
`catchUp` 先取一份 `status=unread` **清单快照**,再**逐封串行**投(每封起一轮模型)。
这 7 封同属一批快照,而模型投完第 1 封后调了一次 `read_inbox`(`01:22:25`/`07:16:12`)
**把剩下几封一次标成已读** —— 快照早已取好,后面的照投不误。
⇒ **判据"投递时是否已有已读行"测不出这条**:它测"当下快照对不对",
而这里快照**当时是对的**,只是**投的时候过期了**。
**"判据没答 ≠ 判据答错了"** —— 0/108 没答错,它答的是另一个问题。
⇒ 这让"每天重投"多出**第三条**独立成因(前两条:`read_mail` 不标 / `markReadFor` 错位)。
也说明"修好 (b) 重投就会停"是一个**新的假绿期待**。
## 顺带:两个数会一直涨,别当阈值
107/81 → 同一条命令当天下午就是 **108/82**(每来一封新信 +1)。
已改成钉**关系**(重投次数 ≥ 该会话真邮件数;时刻聚在 04:00 整点),
并注明别当验收阈值 —— 与我删掉 `check-deploy-drift.mjs` 里"133 个文件"是同一条教训。
★ 纪律提醒也补了一句:那条"别只看当前 `mail_reads`"的注意事项
**只说明会漏判/误判,不等于不存在真反例** —— 同一口径既排除了 11 个假反例,
也捞出了这 7 个真反例。
|
2026-09-21 05:59:20 +08:00 |
|
|
|
ecf98d7d36
|
文档: 记下 B-7.7 那条债的**实测实例** —— 每日 04:00 的 dsh 重启让重投每天复发
与契约里 2026-09-04 那个事故**同形**,只是换成了"宿主被定时重启"。链条完整量到:
1. root crontab `0 4 * * * /usr/bin/systemctl restart dsh.service`(第 6 行)
2. 每天 04:00:03 `dsh.service` Stop/Start(journalctl 逐日可见)
3. 新进程 ⇒ `deliveredMails` 空(index.ts:469,内存 BoundedSet)
4. `caughtUp` 重置 ⇒ 首个心跳后 catchUp 跑一次(:470 / :524)
5. 拉 `/mail/inbox?status=unread&limit=20`(:481)⇒ 整个未读积压当天重投一遍
实测(以 `mails` 表为判据):真邮件投递 **107 次 / 81 封**不同邮件,
04 点整点占 09-18:4 / 09-19:3 / 09-20:3 / 09-21:7;投递时刻落在
04:00:23 / 04:01:23 / 04:03:23… 的 ≤5 封批次节奏上(B-7.2)。
`session/end-seed` 也逐日落在 04:00。
★ 关键判别用**时序**:这 107 次里「投递时该读者已有已读行」= **0 次**
⇒ catchUp 从不重投已读邮件,它投的是"当时确实未读"的。"未读"有两个独立成因:
(a) `read_mail` 不标已读(T-12 对已读状态沉默);(b) `markReadFor` 占位符错位
(2026-09-20 修)。两者都让"读过了"没变成"已读"。
⚠️ 我把这里算错过两次,都写进文里当纪律:
① `agent/inbox/spliced` 里混着**非邮件**注入(后台作业完成通知、续跑提示),
按"事件数"统计会当成邮件(我一度报 09-20 有 18 次,真值 **3**);
⇒ 要拿 id 去问 `mails` 表在不在,才算真邮件。**同一事件类型 ≠ 同一种载荷。**
② 判断"有没有重投已读的信"**不能看当前 `mail_reads`**:11 封现在是"已读+曾重投",
看着像反例,其实是**先被重投、后来才补标**的。必须拿**每次投递的时刻**比 `read_at`
—— `mail_reads` 是当前状态,"投递时是否已读"是历史事实,两者不是一回事。
dsh 桥去重仍是内存态 ⇒ B-7.7 要求的落盘账本这条债**仍未销**(已在文里写明)。
|
2026-09-21 04:45:52 +08:00 |
|
|
|
63d430f423
|
文档: API.md 记一条 —— **"计数"对了不代表"账记上了"**(已读权威列静默少行)
接在「已读按调用者记录」那节后面(同一段语义迁移的下游后果)。
`markReadFor` 把 reader 前置成 `$1`,而调用方 `where` 早把 `$1` 用成 recipient
⇒ 绑定表右移一格 ⇒ 最后一封永远插不进(单封一封都不插)。
`marked` 与 `mails.status` 都对,只有权威列 `mail_reads` 静默少行
⇒ 邮件"看起来已读、实际仍算未读" ⇒ 重启补投时被当新信重投。
**为什么 7 天零痕迹**:`marked` 来自**另一条 UPDATE**,而当时那 4 个测试断言的
是冗余列 `mails.status` —— **判据守错了列**。
教训(写成可复用的):**一个值的"计数"来自 A 语句、"记账"来自 B 语句时,
A 对不代表 B 对;判据必须断言决定行为的那个列。**
修在 `17908c1`,回归判据 `internal/repo/markread_authcolumn_test.go`。
|
2026-09-21 04:26:49 +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 |
|
|
|
176c90272b
|
补充: 「差集」有**两个**成员(不是 §18.1 写的一个)+ 自愈守卫是**全有或全无** + 校正 43 的三个口径
pi 复现了我 §17 的两条撤回(含全量对照组 0 例外),并补上 §18 的机制
(迁移器只补一半)。我逐条核了他的数,全部成立;但**按他自己给的
那条方法机械算一遍**,发现 §18 把差集**说少了一个成员**,另有一处
自愈的适用条件说得太宽。本提交是他那封的**同一条方法的下一次应用**。
## 一、§18.1.1 差集是 2 个成员,不是一个
方法 = 「校验器要求 id」−「迁移器自愈 id」。机械枚举:
VALIDATED: agent/inbox/spliced, assistant/message,
session/title-llm-request, tool/result, user/message
HEALED : assistant/message, tool/result, user/message
⇒ 差集 = { agent/inbox/spliced, **session/title-llm-request** }
第二个成员同样"校验要 id、迁移器不补"(messageValue 经 exactRecord+
nonEmptyString(id);normalizeLegacyMessage 无此分支)。实测只剥它的 id:
transformed → refuses ... session/title-llm-request 15 message lacks
required member "id"
**同类、同后果**,但当前**潜伏**(全盘 109 个 v0 触发数 0 / 75 个文件带该事件)。
⇒ §18.2「只补 inserted 就够了」在当前数据上**仍然成立**,但成立的理由
**比 §18.1 写的窄**:不是差集只有一个成员,而是第二个恰好没被触发。
repair 脚本覆盖的是差集的 1/2 —— 若哪天 title-llm-request 丢 id,
**报错一模一样而脚本覆盖不到**。脚本头注释已写明该边界。
## 二、§18.1.2 自愈的前提是「全无」,不是「缺 id」
index.js:2179 的守卫是**全有或全无**:id/role/message 三个都不在才补。
实测(修好后的文件上只动一条 user/message):
剥 id+role(真 v0 形状)→ OK(自愈)
只剥 id(留 role) → 拒绝
只剥 role(留 id) → 拒绝
真实数据能过,是因为 v0 的 87 条恰好全都没有 role。
⇒ 准确说法是"迁移器会给**完整的 v0 形状**补 id",半成品不在自愈范围。
## 三、校正「43」的三个口径(§18.2 表 + §12)
· 被修的 v0 artifact : 43 = 40 mail-* + 3 非邮件
· --prefix mail- 验收候选 : 43 = mail-* 会话文件(含 v3)
· mail-* 目录名 : 41
前两个都等于 43 **但不是同一个集合**(被修集合的 3 个非邮件,在验收
集合里换成 v3-only 的 mail-f8f9a840)。数字相同 ≠ 集合相同。
另核:.bak 共 80 个 v0(37 文件 ×2 + 6 ×1 = 80),去重后 43 —— 与 pi 一致。
## 四、我独立复现的最强对照(与 pi 一致)
从未修过的 v0 共 66 个:两档都 OK 37 / 两档都 FAIL 29(全是
subagent/descriptor v2)/**transformed OK & current FAIL 0**。
⇒ 那个组合确系测量产物。§18.3(id 只查 nonEmptyString)我读码确认。
未改动 pi 的 §18 正文;新增 18.1.1 / 18.1.2 与三处口径标注。
|
2026-09-19 12:47:32 +08:00 |
|
|
|
652316674a
|
文档: 补上「为什么只补 inserted」的真正机制 —— 迁移器只补了一半(§18)
§3/§4 一直说「只改一处」,但从没说清为什么顶层 user/message 缺 id 就不用管。
早先给的理由(补了会 seq gap)已被 §17 作废,那句话一度**没有理由**。
真正的机制(可推广,不只是这一个 repair 的解释):
- 补 id 的 normalizeLegacyMessage() 的 switch 只有 3 个分支
(user/message、assistant/message、tool/result),**没有 agent/inbox/spliced**;
顶层消息缺 id 迁移器自己会补(legacy-message:<sid>:<seq>)。
- 校验 id 的 messageValue() 对 spliced.inserted[] 严格要求 id+role。
⇒ 顶层缺 id 能自愈;inserted[] 缺 id 直接拒绝整条会话。不是取舍,是只补了一半。
实测 43 个文件零例外:其中 43 个文件的顶层 user/message 也缺 id(共 270 条),
只补 inserted(故意不碰 user/message)后,产物里 user/message 仍缺 id = 0。
另:id 的约束是 nonEmptyString(无格式校验),从代码层面解释了 §17.1。
给下一位的一句话:判断「该补哪些字段」要同时读校验器与迁移器 ——
校验器管拒绝什么,迁移器管自愈什么,需要手工补的是两者之差。
|
2026-09-19 12:22:36 +08:00 |
|
|
|
f236ef44ca
|
更正: 我自己的两条风险是**测量方法**造成的假象 —— 校验会原地改写入参,换个对象就消失(§17)
pi 指出 §10 那个坑还有下半段。我据此重测了自己的两条结论,**两条都推翻**:
1. §5「伪造 id 会污染严格校验」—— 假象。§5 引用的 seq gap 来自
「先 transformed(在**原对象**上,验证器已把 dt 写回)→ 再拿同一批对象跑 current」。
深拷贝重测:strict-first OK、transformed(clone)+strict(clone) OK;
同进程 3 次 × 3 个 OS 进程全 OK ⇒ 不是不确定性,是入参被上一次校验改了。
**从没修过的 37 个对照组会话 strict 也全 OK** ⇒ 与伪造 id 无关。
2. §4/§8「补 user/message 会引入 seq gap」—— 同样假象。深拷贝重测:40/40
补与不补都 strict-ok。且迁移器**本来就会**给 user/message 合成 id
(原始 v0 迁出 386 条带 id / 0 条缺 id)⇒ pi 上封担心的
两个口径各要一份不同东西并不成立。
结论比原先更好:修后会话 **transformed 与 current 两档都可读**,
伪造 id 换成真 randomUUID() 结论不变(id 取值形式不影响)。
真正该记的是:**校验会改写入参 ⇒ 校验与落盘必须分对象**。它已制造
§10(dry-run 40 vs apply 3)与本节(假 seq gap)两次假结论。
另独立复核了数据完整性(40/40):.bak 齐全、事件数一致(80001=80001)、
剥掉注入的 id/role 后逐行语义差 0、87 个注入 id 全唯一。
|
2026-09-19 12:15:45 +08:00 |
|
|
|
fe0cfe626c
|
验收口径: --prefix 严格前缀 —— 修正 --only mail- 误匹配 agentmail- 造成的 3 个假失败
第一版验收用 --only mail- 得到「47 可读 / 3 不可读」,但那 3 个根本不是邮件会话:
目录名是普通 UUID,只是父目录 --home-program-agentmail-- 里含子串 mail-。
它们的错因是另一个独立缺陷(subagent/descriptor v2)。
换严格前缀后的真实数字:真 mail-* 会话 43/43 可读,0 不可读。
脚本现在同时提供 --only(子串)与 --prefix(目录名严格前缀)。
|
2026-09-19 12:04:43 +08:00 |
|
|
|
b4a8f74ae5
|
修复: dsh 邮件通道全断的**两侧**根因(桥侧不产 message id 是真正在写的那一处)
现象:dsh 的邮件通道全断。老会话读不出来 ⇒ 桥报 SessionQueryError ⇒ 按"不在磁盘"
处理 ⇒ 再 create 撞 `already exists`。修好读路径之后又立刻暴露下一层
`message "undefined" is already pending`。
根因一(历史数据,dsh 侧):v0 会话的 `agent/inbox/spliced.inserted[]` 缺 `id`/`role`,
v0→v1 迁移第一步就拒绝。40 个真 mail-* 会话全部命中。
根因二(**仍在写**,本仓侧):`plugins/dsh-mail-bridge/lib/message.js` 的
`userMessage()` 只产出 `{content, source}`。DSH 0.1.5 的 inbox 按 `message.id` 去重
(`dsh-agent-loop` 的投影 apply() 与 mutate() 各维护一个 Set),id 全是 undefined
⇒ **第二条消息必挂**。日志里最早的同类记录在 2026-09-07,累计 50+ 次。
官方形状在 `@deepseek-ai/dsh-llm` 的 `createMessage()`({id, role, content, source}),
同一份 dsh 里其它插件都用官方的 createUserMessage(),只有这个桥手搓。
以前没炸是因为读路径先坏,根本走不到 followup。
本次改动
- message.js/.d.ts: userMessage() 补 id: randomUUID() 与 role:'user'
- test/message.test.mjs: 钉住「id 非空」「两条消息 id 必须不同」,用官方 inbox
去重逻辑逐字复刻验证(修复前 message "undefined" is already pending,修复后 20 封全唯一)
- scripts/: repair-legacy-spliced-ids.mjs(v0,默认 dry-run)、
repair-v3-usermessage-ids.mjs(v3)、verify-mail-sessions-readable.mjs
(走生产真读路径 JsonlSessionPersistence.open,而非解码器口径)、两个 apply driver
- docs/DSH-0.1.5-MAIL-CHANNEL-ROOTCAUSE.md: 补执行结果与两处新事实
执行与验收(详见文档 §9-§15)
- v0 修 40 个、v3 修 2 个;逐文件解压后与备份 `cmp` **逐字节相等**,事件数 40/40 一致,
零丢失(25.2MB→12.5MB 是单帧改 500 行/帧的重压缩,不是丢数据)
- 真 mail-* 会话最终 **41/41 可读**
- journal 里同一会话从 `already exists` 变为 `resume 续谈`,且持续增长
(22647→22685 事件),最新 user/message 带真实 UUID;修复上线后 already pending 计数为 0
- 已在生产部署(deploy/redeploy-plugin.sh dsh,快照+原子软链+重启+后置验证全绿)
两个必须记住的坑
1. **校验与落盘不能共用同一批对象**:createRestore().decodeRow() 会原地改写入参
(补全 dt 数组),污染后写出去会报 `released Session row N has seq gap`。
这曾让 dry-run 说"40 个可修"、apply 只说"3 个"。
2. **判定磁盘健康只认 open()**:readSession() 走 SessionCorpus.load,命中有 live 会话时
直接返回内存快照、不校验磁盘;open() 才走 validateStoredEvents。两条路径结论相反
是设计使然,不是矛盾。
|
2026-09-19 12:03:34 +08:00 |
|
|
|
1832937016
|
docs: 日历那两处"未做"清单早已过期(写侧早就做完了)
`CalendarPage` 的文件头写着「没做:新建/编辑/删除事件(**写侧**)、……**ics 导入导出**」,
而写侧当时**早就做完了**(`createEvent`/`updateEvent` 已接线、表单在 765 行起)。
`docs/HARMONY-ALIGN-PLAN.md` 的 P6 行同样标着 ⬜ 未做。
注释把已完成的说成未做,比漏写更糟:下一个读的人会去"实现"一个已经存在的东西。
本仓反复在消的"说的与做的不一致"这次落在文档/注释上(判据看不见注释,
只有人读的时候才会发现 —— 所以更该在每次真做完时顺手改)。
- `CalendarPage` 文件头:补上月/周/日三档、农历、写侧、.ics,并写明为什么还没有滑动翻页。
- `HARMONY-ALIGN-PLAN.md` P6:⬜ → ✅(滑动翻页除外),带三笔提交号(fbe7879 / 2da38bb / bcd7e7f)
与"鸿蒙无下载目录概念、DocumentViewPicker 是唯一路径"这条平台差异。
|
2026-09-18 13:19:06 +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 |
|
|
|
30886271ea
|
跨端: docs(引用锚): 把日期锚换成邮件 ID(pi 邮件 b1e23663:日期也是锚,写错一天下一个人找不到那封信)
pi 指出的锚错:`PushContract.ts` 里那段线上形状是 pi **09-15** 发的(邮件 `004983bb`),我写成了 09-14。
照他的推理("引用别人的工作状态时,哈希和'谁在读'一样会漂")往下再走一步:
**日期本身就是会漂的锚 —— 邮件 ID 不会。** 所以不是把 09-14 改成 09-15 就完事,
而是把这一族引用改成**可检索的标识**:
- `PushContract.ts`:契约来源 → `1f9ff3b4`;线上形状 → `004983bb`;
- `harmony-push.test.mjs`:同上两处;
- `Calendar.ts`:表头共用分叉、"今天"的调用侧 → `f60521de`;
- `ALIGN-REFS.json`:参照物版本登记 → `6e14b410`;圆角策略追认 → `90372ca1`;AGC 包名实测 → `1f9ff3b4`。
共 10 处。判据不依赖这些注释文本,改完 `harmony-push` 12/12、`harmony-calendar` 10/10、`align-refs` 3/3 仍绿;
`ALIGN-REFS.json` 仍是合法 JSON。
**没改的 4 处**(`NavItems.ts` 1 处、`Wallpaper.ts` 3 处):它们写于 09-14 那批往来,日期本身没被指出错,
而那几封的邮件 ID 我这边没有(跨过一次上下文压缩)——**宁可留着有争议的日期,也不编一个 ID**。
|
2026-09-15 11:51:39 +08:00 |
|
|
|
46fa7fa729
|
feat(push): 可选、配置式、多厂商的推送通道(HMS 为首个实现)
用户要求:推送密钥必须是可选项(自部署后端不能写死推送方式),且要支持
多厂商配置式接入 —— 每个用户各自部署服务器、自己选厂商、自己配凭证。
所以落地成:
· internal/push:通道抽象 + 工厂表(RegisterType),加厂商不改配置层与端点形状;
HMS 只是第一个实现(internal/push/hms.go)
· 配置在 PUSH_CONFIG(默认 <AGENTMAIL_DATA_DIR>/push.json),一项一个厂商,
凭证走文件(app_secret_file / files.*,建议 600);环境变量只是可选覆盖
· 没配 = 整条推送路径连一次查库都不发生(shouldDispatch 早退);
单项配错(未知类型/密钥读不到/enabled:false)只跳过那一条,不影响启动
· push_tokens 表带 provider 维度 + 三个 /me/devices/push-token 端点;
没配推送时端点照存并回 enabled:false(登记成功 != 服务端开了推送)
· notify.Recipients 末尾异步挂钩:收件人名单直接用 SSE 那份 seen(两条通道
共用同一份"谁该收到"的判据);失败只记日志,绝不拖住收信
HMS 的形状是拿真凭证打线上接口问出来的(v1 + message.token[] + testMessage;
payload/target 形状 v1 不认、v2 要服务账号 JWT)。未上架应用必须 test_message=true,
单批 ≤10 token(MaxTokensPerRequest 声明)、每日 1000 条兜底(项目级额度)。
实测:App ID + App Secret 能换到 access_token(3600s);形状被线上服务接受。
判据:repo 6 条 + push 12 条 + handler 3 组,全部做过**变异验证** ——
过程中抓出两条假判据(异步分发与 t.Cleanup 赛跑而假绿;密钥文件优先级没被覆盖)
并补掉。Go 全量测试与 go vet 干净。
★ 未验:端到端真机送达(需要真机 token + 客户端按 com.jianf.agentmail 重编并签名,
签名指纹还要在 AGC 登记)—— 从未真正发出过一条能到达设备的推送。
详见 docs/HMS-PUSH-PLAN.md 的「实现状态」一节。
|
2026-09-15 11:21:00 +08:00 |
|
|
|
b806a05bfa
|
跨端: harmony 管理页(用户管理)+ P4c 壁纸上传入口 —— 「功能做全再交付」的两块
pi 的交付清单里缺的两块(`docs/GUI-PLAN-HARMONY.md` 原先把管理后台划在首版之外,
用户明确要求「功能做全再给我」之后收进来)。
标 `跨端:` 是因为判据落在 `client/electron/test/`(鸿蒙的判据目录一向量在那里),
代码本体全在 `client/harmony/`。
## 管理页(用户管理)
- `pages/AdminUsersPage.ets`:新建 / 编辑(显示名·角色·白名单)/ 启停 / 重置密码。
排布照 `AdminUsersPage.tsx`,包括「受限」徽标口径(普通用户且白名单非空才显示)、
最后登录缺席与空串都显示「从未登录」、管理员对白名单两项忽略。
- 入口在设置页底部,**仅管理员可见**(`role === 'admin'` 严格相等,与 `App.tsx` 同口径)。
读不到身份时**不**显示也不报错(乐观放行会让每个普通用户看到点进去 403 的入口)。
- `api/AdminApi.ets` + `model/AdminUsers.ts`(纯逻辑,零 import ⇒ 判据能真跑)。
- 启停**只发 status 一个字段** —— 服务端是部分更新,多发字段会把显示名与白名单一起改掉。
- `model/Models.ets` 补管理端 DTO;`main_pages.json` 注册路由。
## P4c 壁纸上传
- `model/ImagePrep.ts`:阈值与两档策略(2560/0.85 → 1280/0.78,入口 20MB,压后上限 3.5MiB)。
**一处有意不对齐 WebUI** 并写明理由:WebUI 卡 data-URL 长度(含 base64 膨胀),
鸿蒙内存直传 ArrayBuffer,卡的是字节数。
- `common/BackgroundPicker.ets`:不设 / 预设 / 自定义图片 + 浓度与模糊滑杆。
上传链:picker → 判可不可以 → 逐档按 desiredSize 解码压缩 → 上传 → **请页面以服务端为准重新同步**。
失败**必带原因**(服务端 415/413 文案原样透出)。用户取消选图**不算失败**。
- `ApiClient.uploadBytes`:MultiFormData.data 收 ArrayBuffer(核了 SDK,since 11;本工程 23)
⇒ 内存直传,不需要 base64 也不需要临时文件。
## 两处真 bug(变异测试逼出来的,不是"新写坏的")
1. **压缩循环的第二档此前是死代码**:循环里的 break 与循环外那句 shouldRetryWithActual
互相抵消 —— 把循环里那处改成 `if (true)`(永远只压一档)整套判据照样全绿。
收成一处判定(overLimit),循环外只读结论,并加结构性判据(该函数在这条链上只许调用一次)。
2. **壁纸的模糊档一直是「只写不读」**(计划文档 §7.12 登记过):滑杆能拖、值能存、
blurStyleFor 也写了,就是**没有调用点**,壁纸一点没糊。
本次补上的调用点分两层:壁纸层 `.blur(px)` = **图片内容模糊**
(与 WebUI 的 `filter: blur(var(--bg-blur))` 同一个量、同一个数,所以不需要映射表);
而那张**材质档**映射表 `blurStyleFor` 也终于有了调用点(`navMaterialFor` 内部复用它)。
`docs/HARMONY-ALIGN-PLAN.md` 的 §7.12 两行(材质 / 壁纸模糊度)已一并改准、不再互相矛盾。
## pi 复核后**改回来的**(这一笔里我自己犯的两处,都由 pi 抓出)
1. **导航条材质一度绑定到 `bg_blur`,`bg_blur=0` 时整个消失。**
我把 `NavBar` 从固定档改成 `blurStyleFor(bgPlan.blurPx)`,而滑杆 `min: 0` 可达、
`blurStyleFor(0) === 'NONE'` ⇒ 用户把壁纸调清晰时**导航条一点材质都没有**。
而且它与本笔自己的论证**相反**:刚论证完"图片内容模糊"与"面板材质"是两个物理量,
转头把面板材质接到壁纸模糊这个输入上。
现在**分层**:`blurStyleFor` 是通用映射(**允许** NONE —— "0 px 不模糊"是它的正确语义);
`navMaterialFor` 是**导航条专用、有下限**的入口(0 px ⇒ 最薄档)。
判据钉**可达性**(滑杆 0..40 每个整数 + 界外值都不许 NONE,且三档都要出现 ——
否则"恒定最薄档"会让滑杆成为死控件)。
2. **`Theme.navMaterial` 被我弄成了死令牌**,而看着它的判据**照样绿**
(那条只断言"声明存在且不是 NONE" —— 守的是声明,坏的是活的调用路径)。
现在导航条真的用它;并把同文件里**只覆盖 `Theme.overlay` 一个令牌**的死令牌规则
**铺到 Theme 的全部 35 个令牌**(量**外部引用数**:只被 Theme 内部方法读、
而那个方法自己有外部调用点 ⇒ 不算死 —— `chipSpentBg` 是这种;`navMaterial` 当时
唯一的消费者是一张可整体删掉的局部表,所以必须被抓)。
## pi 复核后**补上的**(这一笔漏掉的接线,都是我造成的)
- **`test/run-all.mjs` 的 SUITE 没接两个新判据文件** ⇒ HEAD 上 `npm test`
**一条判据都不跑、直接 exit 1**(套件自检 2 就是为这件事写的)。已接入,
并把两条登记进 `STATIC_ONLY`(`.ets` 要设备 ⇒ 静态欠账)。
- **`debt-visibility` 是我自伤**:那两个新文件里有 5 处"边界声明"但一次都没登记。
我当时报"2 条失败是改动前就红" —— **只对一半**:这条在父提交上是**绿的**。
我那次 `git stash push -u -- client/harmony` 的对照是**无效对照**
(`-- client/harmony` 把 `client/electron/test/` 整个排除在外,新判据文件根本没被 stash),
所以两次跑都红、看着像"既有"。已按 pi 的建议改用 `git worktree` 到父提交做对照。
现在两处都登记进 `docs/DEBTS.json`(含 `static-criteria` 5→7,Go 侧同一份登记同步改)。
## 一并修正的旧判据(都是"太宽/太窄/钉错东西",不是放宽标准)
- 「模糊归属」:原文「壁纸层不许有**任何**模糊调用」把**图片内容模糊**与**面板材质**
混为一谈(WebUI 侧核实:`.app-backdrop` 的 filter 与它之上那层的 backdrop-filter
是两个不同的量)⇒ 改成按两种模糊分别钉。
- 「bgBlur 只写不读,消费侧必须为 0」:值不再成立,**形状保留**(逐文件登记 + 计数 + 理由),
标题与断言里的假话一并改掉。
- isDarkMode 那条 `/dark\s*\)/` 断的是**参数顺序**(加一个入参就误红)⇒ 改成"dark 在实参里"。
- 三条钉 `backgroundBlurStyle` **整条字面表达式**的断言 ⇒ 改成钉语义
("用系统材质 + 材质有下限"),不再匹配那一行的字符。
## 判据
新增 `harmony-admin.test.mjs`(22 条)、`harmony-imageprep.test.mjs`(29 条);
`harmony-presets.test.mjs` 加 1 条(模糊档搬运与归一,含 `-0` 那个洞:
`Math.round(-0.4)` 是 `-0` 而 `-0 < 0` 为 false ⇒ 改成判 `!(r > 0)`)。
**`node test/run-all.mjs`:22 个判据文件全部跑起来**,红的只有 1 个:
`build-stamp`(`dist` 是 `a5fc86b` 上构建的,`gitRev` 对不上当前 HEAD)。
这条**不是我的代码造成的**(可证:`a5fc86b..HEAD` 之间,`srcHash` 覆盖的那批文件
——`client/electron/src` 等——**一个都没动过**,所以 `srcHash` 没变,差的是 `gitRev`),
但也**不是"改动前就红"**:任何推进 HEAD 的提交都会让它变红,正确修法是重构建。
## 未验(如实标注)
- **本机无设备/无模拟器 ⇒ 全部观感未验**:管理页排版与卡片观感、滑杆手感、
模糊在真机上的实际档位观感、系统材质在自绘悬浮条上的实际效果。代码齐 ≠ 真机验过。
- 预设档**没有**上模糊(壁纸在预设档下是一叠自绘矩形,系统材质对它不生效)——
这是我**主动收的范围**,不是漏,真机看一眼再决定要不要补。
- **Go 侧的 `debt_registry_test.go` 我没能跑**(沙箱里没有 Go 模块缓存,`go test` 起不来),
只做了 `gofmt` 校验;那处改动是一行 `Count: 5 → 7`。
|
2026-09-15 11:17:23 +08:00 |
|
|
|
474cadaf54
|
跨端: harmony 管理页(用户管理)+ P4c 壁纸上传入口 —— 「功能做全再交付」的两块
pi 的交付清单里缺的两块(`docs/GUI-PLAN-HARMONY.md` 原先把管理后台划在首版之外,
用户明确要求「功能做全再给我」之后收进来)。
标 `跨端:` 是因为本次的判据落在 `client/electron/test/`(鸿蒙的判据目录一向量在那里),
代码本体全在 `client/harmony/`。
## 管理页(用户管理)
- `pages/AdminUsersPage.ets`:新建 / 编辑(显示名·角色·白名单)/ 启停 / 重置密码。
排布照 `AdminUsersPage.tsx`,包括「受限」徽标的口径(普通用户且白名单非空才显示)、
最后登录缺席与空串都显示「从未登录」、管理员对白名单两项忽略。
- 入口在设置页底部,**仅管理员可见**(`role === 'admin'` 严格相等,与 `App.tsx` 同口径)。
读不到身份时**不**显示也不报错(乐观放行会让每个普通用户看到一个点进去 403 的入口)。
- `api/AdminApi.ets` + `model/AdminUsers.ts`(纯逻辑,零 import ⇒ 判据能真跑)。
- 启停**只发 status 一个字段** —— 服务端是部分更新,多发字段会把显示名与白名单一起改掉。
- `model/Models.ets` 补管理端 DTO;`main_pages.json` 注册路由。
## P4c 壁纸上传
- `model/ImagePrep.ts`:阈值与两档策略(2560/0.85 → 1280/0.78,入口 20MB,压后上限 3.5MiB)。
**一处有意不对齐 WebUI** 并写明理由:WebUI 卡 data-URL 长度(含 base64 膨胀),
鸿蒙内存直传 ArrayBuffer,卡的是字节数。
- `common/BackgroundPicker.ets`:不设 / 预设 / 自定义图片 + 浓度与模糊滑杆。
上传链:picker → 判可不可以 → 逐档按 desiredSize 解码压缩 → 上传 → **请页面以服务端为准重新同步**。
失败**必带原因**(服务端 415/413 文案原样透出)。
- `ApiClient.uploadBytes`:MultiFormData.data 收 ArrayBuffer(核了 SDK,since 11;本工程 23)
⇒ 内存直传,不需要 base64、也不需要临时文件。
- 用户取消选图**不算失败**,什么都不说。
## 顺带修掉的两处真问题(都是变异测试逼出来的)
1. **压缩循环的第二档此前是死代码**:循环里的 break 与循环外那句 shouldRetryWithActual
互相抵消 —— 把循环里那处改成 `if (true)`(永远只压一档)整套判据照样全绿。
收成一处判定(overLimit),循环外只读结论。
2. **壁纸的模糊档一直是「只写不读」**(计划文档 §7.12 登记过):滑杆能拖、值能存、
blurStyleFor 也写了,就是**没有调用点**,壁纸一点没糊。本次补上调用点
(壁纸层 .blur(px) = 图片内容模糊;导航条材质由 blurStyleFor 映射)。
同时按 §7.12 的原承诺更新了那一行。
## 一并修正的旧判据(都是"太宽/太窄",不是放宽标准)
- 「模糊归属」:原文「壁纸层不许有**任何**模糊调用」把**图片内容模糊**与**面板材质**
混为一谈(WebUI 侧核实:.app-backdrop 的 filter 与它之上那层的 backdrop-filter
是两个不同的量)⇒ 改成按两种模糊分别钉。
- 「bgBlur 只写不读,消费侧必须为 0」:值不再成立,**形状保留**(逐文件登记 + 计数 + 理由),
标题与断言里的假话一并改掉。
- isDarkMode 那条 `/dark\s*\)/` 断的是**参数顺序**(加一个入参就误红)⇒ 改成"dark 在实参里"。
- 导航材质三处断言原本钉 `Theme.navMaterial` 字面量 ⇒ 改成钉新的映射写法。
## 判据
新增 `harmony-admin.test.mjs`(22 条)、`harmony-imageprep.test.mjs`(29 条);
`harmony-presets.test.mjs` 加 1 条(模糊档搬运与归一,含 -0 那个洞)。
全量 203 条:**201 通过**,2 条失败为**改动前就红**的既有项
(BUILD_INFO 比对、词表↔余额)—— 用 stash 对照验证过。
两个新判据文件上跑了 **48 个变异体,全部被抓**(含"接线"类:删掉「受限」徽标、
组件自己宣布成功、release 不 await、按原图尺寸解码…),
其中 2 个变异体**红不了**,因此又补了 5 条判据(纯逻辑接线、退档判定只有一处、
两档都超限必拒、解码尺寸用的是目标尺寸而非原图尺寸、模糊档搬运)。
(数字口径:按 runner 的真实条件"锚点恰好命中 1 次才算跑过"统计;
另有 4 条锚点不命中、根本没跑,不算在这 48 里。我第一次写的是"40"——
凭记忆累加的,错了,已更正。)
**未验**:本机无设备/无模拟器 ⇒ 全部观感未验(管理页排版、滑杆手感、模糊在真机上的
实际档位观感)。代码齐 ≠ 真机验过。
|
2026-09-15 11:03:22 +08:00 |
|
|
|
f14f2d6fa7
|
chore: 动画盘点判据接入套件 + 让其合规 + 对齐参照物重新登记(清工程收尾)
用户:「清理一下tmp和工程吧」。
- 新判据 test/animation-audit.test.mjs 接入 test/run-all.mjs:原先**写了却不会跑**
(套件自带的那条闸门当场报「这些判据文件没接进套件」)。
- 该文件改用 test/lib/read.mjs 的具名入口(code/prose/bytes),不再裸用 readFileSync ——
criteria-hygiene 抓到:读原文判代码会被解释性注释骗,今天已踩过两次。
- docs/ALIGN-REFS.json:CalendarView 的对齐登记按规矩**重新核对后再登记**
(差异只有动画类 rise-in → pane-rise,骨架/布局/圆角来源未变;鸿蒙侧本无日历动效
⇒ 不产生新的对齐义务),不是抄一处新哈希。
|
2026-09-15 09:16:05 +08:00 |
|
|
|
f6cecf7867
|
docs(align-refs): CalendarView 重新核对后更新登记(差异只有根节点 rise-in;鸿蒙侧无日历动效,不产生新对齐义务)
|
2026-09-15 07:57:10 +08:00 |
|
|
|
f8fe14d124
|
docs(debts): 登记 pi-bridge-adopt-cwd-mismatch —— 沙箱 rw 与 worker cwd 的第三个来源(接管路径)未对齐,且这类错位没有任何一层提示
|
2026-09-15 06:47:24 +08:00 |
|