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"(还清是可测事件,这正是那条判据存在的意义)。
This commit is contained in:
2026-09-14 17:16:22 +08:00
parent f5c4f56682
commit d25770ea2f
7 changed files with 273 additions and 43 deletions

View File

@ -0,0 +1,152 @@
package repo
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"testing"
)
/*
欠账的**单一余额**(pi 2026-09-14 裁定 §3)。
背景:到这轮为止已经有三笔不同类型、各自表达方式的欠账 ——
鸿蒙静态判据(`RESULT static=5`,有余额、有到期探针)、
`mails.status` 行级派生化(`t.Skip`,**原先无余额**)、
手势语义契约(登记的到期前提,**原先无余额**)。
三笔都"可判",但**没有一处能一眼看全**;而"欠账不显形就等于没有"。
所以:**登记在一处、可打印**。Go 这一侧打印在 TestMain 收尾,
跨端的净值同时登记在 `docs/DEBTS.json`(两端都能读,见该文件)。
*/
type debt struct {
ID string
Count int
Due string // 到期前提(什么时候该还清)
Where string // 判据在哪
}
var debts = map[string]debt{
"mails-status-derived": {
ID: "mails-status-derived",
Count: 0, // 由 mail_status_derived_test.go **实测**后标 1(不许手写:常量余额 = 永久 Skip 的死法)
Due: "详情/线程改为按读者派生(readStateFor)之后 —— 那时 mail_status_derived_test.go 从 Skip 转实跑",
Where: "server/internal/repo/mail_status_derived_test.go",
},
"static-criteria": {
ID: "static-criteria",
Count: 5,
Due: "本工作区能装、能点设备(探针三值转 true 时自动变红)",
Where: "client/electron/test/run-all.mjs(STATIC_ONLY)",
},
"gesture-semantics": {
ID: "gesture-semantics",
Count: 1,
Due: "P6 第 3 步:鸿蒙侧出现滑动手势代码时立即建(此前建 = 只有一端存在的假判据)",
Where: "docs/HARMONY-ALIGN-PLAN.md P6 段",
},
}
// debtMark 由各条欠账的判据在"确认未清"时调用(余额来自**测量**,不是常量)。
func debtMark(id string) {
d := debts[id]
d.Count = 1
debts[id] = d
}
type debtLedger struct {
Debts []struct {
ID string `json:"id"`
Count int `json:"count"`
Due string `json:"due"`
Where string `json:"where"`
} `json:"debts"`
}
// loadDebts 读**那一份**登记(docs/DEBTS.json):两端的余额必须来自同一处。
func loadDebts(t *testing.T) debtLedger {
t.Helper()
b, err := os.ReadFile(filepath.Join("..", "..", "..", "docs", "DEBTS.json"))
if err != nil {
t.Fatalf("读欠账登记 docs/DEBTS.json 失败:%v(登记丢了 = 欠账不显形)", err)
}
var l debtLedger
if err := json.Unmarshal(b, &l); err != nil {
t.Fatalf("欠账登记不是合法 JSON:%v", err)
}
return l
}
/*
★ 登记与**实测**必须一致(pi 2026-09-14 裁定 §1:Skip 的条件要是测量结果,"跳过"要进余额)。
这条判据挡的是两种死法:
① 条件被写死:余额不是从测量来的,而是某人抄的数字 ⇒ 这里用 debtMark 的实测值比对;
② 跳过不可数:`go test` 对 Skip 是退出码 0、`--- SKIP` 只是一行输出
(而且**跑得通时 go test 根本不打印包的输出** —— 我第一次就把余额打在 TestMain 里,
结果常态运行时一个字都看不见,正是"不显形")。所以可见的那份在
electron 套件的 RESULT 行(它读同一个文件),这里保证两处**同源**。
*/
func TestDebtLedgerMatchesMeasurement(t *testing.T) {
l := loadDebts(t)
seen := map[string]bool{}
for _, d := range l.Debts {
seen[d.ID] = true
if d.Due == "" || d.Where == "" {
t.Fatalf("欠账 %s 必须写明到期前提与判据位置(否则它只是「存在」,不是「欠账」)", d.ID)
}
}
for _, id := range []string{"static-criteria", "mails-status-derived", "gesture-semantics"} {
if !seen[id] {
t.Fatalf("欠账登记里缺 %s —— 三笔必须同处登记,否则审计只会找到一处就当全部", id)
}
}
// 本包能实测的那一笔:**自己测**(不依赖别的测试先跑过 —— 排序依赖是隐蔽的假绿)
var want int
for _, d := range l.Debts {
if d.ID == "mails-status-derived" {
want = d.Count
}
}
outstanding, detail, derived := measureMailStatusDebt(t)
got := 0
if outstanding {
got = 1
}
if got != want {
if got == 0 {
t.Fatalf("**欠账已还清**(详情 %q 已等于按读者派生 %q),但 docs/DEBTS.json 还记着 %d —— "+
"还清是可测事件,登记要跟着清(这正是这条判据存在的意义)", detail, derived, want)
}
t.Fatalf("mails-status-derived 的余额:登记说 %d,实测说 %d —— 登记与测量分叉了", want, got)
}
}
// debtSummary 供 TestMain 打印:一处能看全的余额 + 到期前提。
func debtSummary() string {
ids := make([]string, 0, len(debts))
total := 0
for id, d := range debts {
if d.Count > 0 {
ids = append(ids, id)
total += d.Count
}
}
if len(ids) == 0 {
return " debts=0(全部还清)"
}
sort.Strings(ids)
var b strings.Builder
fmt.Fprintf(&b, "\n======= 欠账余额 debts=%d =======", total)
for _, id := range ids {
d := debts[id]
fmt.Fprintf(&b, "\n · %s ×%d\n 判据:%s\n 到期:%s", d.ID, d.Count, d.Where, d.Due)
}
fmt.Fprintf(&b, "\n(三笔合在一处看全:分散登记时,审计只会找到一处就当全部)")
return b.String()
}