|
|
63649031d8
|
后端: 销掉一份漂移的欠账副本 + 钉住 user_appearance.user_id 的语义陷阱
## 一、`user_appearance.user_id` 存的是**用户名**(不是 UUID)—— 陷阱显式化
审计时实测到的差异:
| 表 | `user_id` 存的是 |
|---|---|
| `user_keys` | **UUID**(`809967c5-…`) |
| `user_sessions` | **UUID** |
| `user_appearance` | **用户名**(`jianf`)← 与兄弟表不同 |
**功能上自洽**(本包读写都用 username,handler 一律传 `user.Username`),
所以**不是活跃 bug**。但它是个**不报错的陷阱**:按兄弟表的习惯写
LEFT JOIN user_appearance a ON u.user_id = a.user_id
会**静默匹配到 0 行**。我自己审计时就先踩了一次 —— 查出来全是空,
差点当成"这些账号从没设置过外观"(实际 jianf 有记录)。
**没有改列名**:表里有生产数据(壁纸本体在 blob 里、由 `image_sha256` 引用),
改列要迁移 + 回滚预案,收益只是"名字更好看"。选择**把语义钉住**:
文件头写清(**指名兄弟表**当锚、说清**失败长什么样**)+ 新判据
`appearance_semantics_test.go` 双向校验(repo 层不得混入 UUID 语义、
handler 层不得出现 `user.UserID`)。
★ 判据从源码读而不是运行时测:这里要钉的是**约定**,
而约定的存在形式就是注释与命名 —— 行为测试证明不了"下一个人不会踩"。
## 二、销掉一份**已经漂移**的欠账副本
`debt_registry_test.go` 里有一份**手写副本** `debts` map(给 `debtSummary()`
打印用),而**没有任何判据校验它与权威 `docs/DEBTS.json` 一致**。
实测漂移得很厉害:
- 副本 3 条 vs 权威 **17 条**;
- 里面还留着 **`gesture-semantics`** —— 那一笔已于 2026-09-19 还清并销账;
- `static-criteria` 的余额还是旧值 **7**(权威已是 5)。
后果很具体:**`TestMain` 打印的余额是错的**,而余额的全部意义就是"能看全"。
处理:**删掉副本**,让 `debtSummary()` 直接读权威 JSON(同一个事实不留两份),
并加判据 `TestDebtSummaryReadsAuthoritativeLedger` 双向钉住:
① 不许再引入手写副本;② 打出来的数字必须与 JSON 一致,
且**每一笔余额>0 的都要出现在打印里**(漏掉的欠账等于不存在)。
同时修了 `TestDebtLedgerMatchesMeasurement` 里一条**硬编码欠账清单**的断言
(它点名要求 `gesture-semantics` 必须存在 —— 还清了反而红)。
改成断言**形状**(跨端净值 + 本包可实测的那笔必须同处登记),不点名具体笔数。
★ 教训:硬编码欠账清单不可维护,漏改的后果是"还清了反而红"。
## 三、写这三条判据时连踩两次**自匹配**
`strings.Contains(src, "var debts = map[string]debt{")` —— 那串字**本身**
出现在断言的 `t.Fatal` 消息里(以及我留的说明注释里)⇒ 恒红。
(`run-all.mjs` 的自检锚点也栽过一次,那里改用 `lastIndexOf`。)
修法:锚点**拼出来**(`"var " + "debts" + " = map..."`)+ 注释措辞避开那个声明。
## 四、验证
`go test ./...` → **13 个包全过**。
新增:`appearance_semantics_test.go`(4 条断言)、
`TestDebtSummaryReadsAuthoritativeLedger`(2 条)。
|
2026-09-19 16:27:09 +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 |
|
|
|
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 |
|