test(mails.status): "正当"落成可判约束(只许比 archived);欠账补一条"还清即转绿"的判据
pi 2026-09-14 的三点接续。 1. **§1「正当」不能只是标注,要是可判的约束**:按文件+计数登记挡得住"新增一处", 挡不住"**已登记的那一处改变性质**"。已判出来:**任何与 m.status 的比较只允许比 'archived'**(全局属性),与 'read'/'unread' 比较 ⇒ 红(per-reader 属性必须走 mail_reads)。 第一次跑就抓到第三处:`migrate.go` 的 `WHERE m.status = 'read'` ×2 —— 它是**回填**, 职责就是读那个遗留值,所以按文件登记**窄豁免**(allowedCompareExempt,带理由), 而不是"凡是迁移都放行"。 2. **§2 欠账要有"还清即转绿"的判据**:新增 `mail_status_derived_test.go`,三态形状 (与套件探针同族)—— 欠账未清 ⇒ `t.Skip` 并写明还欠什么(**不假红也不假绿**); 还清 ⇒ 开始实跑并断言"详情路径的 status == 按读者派生的值",**转绿即可测事件**; 迁一半(只改 GetMailByID 漏 GetThread)⇒ 红。 实测当前状态:`--- SKIP ... 详情路径返回行级 status="read",而按读者派生说 "unread"`。 **没写成"现在就红"**:恒红的门 = 挂在错误相位的门(CRITERIA.md §11 已为它付过学费)。 另按他的更正把 `migrate.go` 的理由写准:不是"跑过即结束",而是 **"全新安装/空库路径必须存在"**(措辞会影响下一个人敢不敢删它)。 3. **§3 (b) 的语义契约补归属**:归属 = dsh(鸿蒙侧实现者),WebUI 侧不需改动; 落点 = 跨端对齐判据那一族(形状与"预设清单逐项一致"同构,比语义不比数值); **如实标注当前未建**及原因(鸿蒙侧还没有日历页/手势代码 ⇒ 此时"两端逐项相同"只有一端存在, 建出来就是假判据);到期前提 = P6 第 3 步出现手势代码时立即建,此前是**登记的欠账**。
This commit is contained in:
@ -43,8 +43,27 @@ func TestMailsStatusReadersAreRegistered(t *testing.T) {
|
||||
reasons := map[string]string{
|
||||
"repo.go": "unreadFor/readStateFor 用 m.status 判 archived(正当);其余是查询带出行级列(欠账:应按读者派生)",
|
||||
"thread.go": "GetThread 带出 m.status(同一处欠账)",
|
||||
"migrate.go": "一次性回填 mail_reads(历史 'read' → 按读者记账),跑过即结束",
|
||||
"migrate.go": "**全新安装/空库路径必须存在**:把历史 'read' 折进 mail_reads(措辞按 pi 2026-09-14 更正 —— 不是「跑过即结束」,否则下一个人会以为可以删)",
|
||||
}
|
||||
/*
|
||||
* 「正当」必须是**可判的约束**,不能是一句标注(pi 2026-09-14 裁定 §1)。
|
||||
*
|
||||
* 按文件+计数登记挡得住"新增一处",挡不住"**已登记的那一处改变性质**":
|
||||
* `unreadFor`/`readStateFor` 之所以正当,是因为它们比的字面量是 `'archived'`
|
||||
* (**全局属性**:归档对所有人都归档),而不是 `'read'`(**per-reader 属性**)。
|
||||
* 可这个"正当"原先只活在人的判断里 —— 明天有人让同一处也看 `status='read'`,
|
||||
* 计数不变、清册全绿,而语义塌回行级。
|
||||
*
|
||||
* 所以这里把它判出来:**任何与 m.status 的比较,只允许比 'archived'**;
|
||||
* 出现与 'read'(或任何别的字面量)的比较 ⇒ 红。
|
||||
*/
|
||||
compareRe := regexp.MustCompile(`\bm\.status\s*(?:=|<>|!=)\s*'([a-z_]+)'`)
|
||||
allowedLiteral := "archived"
|
||||
// 按文件登记的豁免(带理由);不在这张表里的文件与 'archived' 之外的字面量比较 ⇒ 红
|
||||
allowedCompareExempt := map[string]string{
|
||||
"migrate.go": "迁移回填:把历史 'read' 折进 mail_reads —— 它**必须**读那个遗留值,这是它的职责",
|
||||
}
|
||||
|
||||
writeRe := regexp.MustCompile(`UPDATE\s+mails\s+SET\s+status`)
|
||||
readRe := regexp.MustCompile(`\bm\.status\b|mails\.status`)
|
||||
|
||||
@ -53,6 +72,7 @@ func TestMailsStatusReadersAreRegistered(t *testing.T) {
|
||||
root := filepath.Join("..", "..", "internal")
|
||||
found := map[string]int{}
|
||||
var writes []string
|
||||
var badCompare []string
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
||||
return nil
|
||||
@ -66,6 +86,24 @@ func TestMailsStatusReadersAreRegistered(t *testing.T) {
|
||||
if n := len(readRe.FindAllString(src, -1)); n > 0 {
|
||||
found[base] = n
|
||||
}
|
||||
for i, line := range strings.Split(src, "\n") {
|
||||
for _, m := range compareRe.FindAllStringSubmatch(line, -1) {
|
||||
if m[1] == allowedLiteral {
|
||||
continue
|
||||
}
|
||||
/*
|
||||
* 唯一豁免:**迁移回填**。它的职责就是读那个遗留值(把历史 'read' 折进
|
||||
* mail_reads),所以这里比 'read' 不但正当,而且是必须的。
|
||||
* 豁免也做成**可判的**:按文件登记 + 写明理由(不是"凡是迁移都放行")。
|
||||
*/
|
||||
if why, ok := allowedCompareExempt[base]; ok {
|
||||
_ = why
|
||||
continue
|
||||
}
|
||||
badCompare = append(badCompare,
|
||||
fmt.Sprintf("%s:%d 把 m.status 与 '%s' 比较 —— %s", base, i+1, m[1], strings.TrimSpace(line)))
|
||||
}
|
||||
}
|
||||
if readRe.MatchString(src) || writeRe.MatchString(src) {
|
||||
for i, line := range strings.Split(src, "\n") {
|
||||
if writeRe.MatchString(line) {
|
||||
@ -108,6 +146,14 @@ func TestMailsStatusReadersAreRegistered(t *testing.T) {
|
||||
strings.Join(problems, "\n "))
|
||||
}
|
||||
|
||||
if len(badCompare) > 0 {
|
||||
t.Fatalf("`m.status` 被拿去和 `'%s'` 以外的东西比较了(行级语义会因此塌回邮件级):\n %s\n\n"+
|
||||
"`archived` 是**全局**属性(对所有人都归档),可以留在行级;`read`/`unread` 是**按读者**的,\n"+
|
||||
"必须用 mail_reads 派生(见 readStateFor/unreadFor)。\n"+
|
||||
"(唯一豁免是迁移回填,按文件登记在 allowedCompareExempt 里,且要写理由。)",
|
||||
allowedLiteral, strings.Join(badCompare, "\n "))
|
||||
}
|
||||
|
||||
/*
|
||||
* 写入点:**登记清册**(不是"允许它存在",而是"多一处就红")。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user