98294b9583
build / build (push) Failing after 24s
why:审计 19 项(12 P1 / 7 P2)来自同一批根因,逐点试错会把协议撕成十几份:
· 意图与执行混为一谈(F01/F15):`library.sync:<scope>:<快照>` 是终身幂等键,
A→B→A 的第三次发布拿到第一次的历史成功作业,什么都不做;
· 版本行既是实体又是事件(F02/F09):主键只由 (kind,scope,快照) 决定、写入是
upsert,第二个作业覆盖 `source_job_id`/`evidence`/`created_at`;
· 接管没有代次(F13):两个 worker 都能认领同一 pending 作业,失租的旧执行还能
写业务状态、释放别人的维护锁;
· 早退与终态没有统一政策(F03/F04/F05/F06):半写窗口的前置检查失败会重新开门、
服务起不来也照样开入口、journal 终态后收尾不可重建、恢复成功不回写 current;
· 数据身份与准入各写一套(F07/F08/F14/F17):接管丢了复用语义、全库搬运不改
location_hash、通用入口可造 library.* 作业、目标模型从没被问过;
· 存储/容量/UI/CI 判据各自为政(F10/F11/F12/F16/F18/F19)。
what:按批准的一套协议集中实施(详见 handoffs/release-consolidated-implementation/
current-contract.md):
· operation(一次明确意图)/ attempt(epoch 代次)/ 不可变版本事件 / current 指针;
治理库迁移 76,只动治理库,不碰 WeKnora Schema/源码/镜像;
· 所有业务状态、阶段、版本事件、维护锁写入带 epoch 守卫(失租代次写不进去);
心跳失租立刻取消 handler;
· 半写窗口早退保持关闭;服务未全部健康绝不开入口;journal 终态可从持久事实重建
收尾;恢复成功登记 recovery 事件并推进 current;
· worker 在动维护前核对业务行/kind/scope/队列关联;通用入口拒绝 library.*;
· 全库搬运重算 location_hash 并与文档级导入共用实现;接管消费写集合的复用决定;
发布前核目标实时模型契约、容量门禁前置;
· 存储保护闭包改为全量依赖、类型只认显式登记、保护未知则不出可删候选;
· 页面核 HTTP 状态与结构、恢复纳入轮询、Wiki 差异覆盖标题/摘要/父级。
how:`make verify` rc=0(16 项必需检查全绿,含 go test 全量与前端 485 例);
`go vet ./...` rc=0;`npx tsc -p tsconfig.json --noEmit` rc=0;逐项负例见各包测试
(F03/F04/F08/F12/F13/F14/F16/F18/F19 都有先红后绿记录)。
未执行:治理库迁移到任何已有库、真实 PG/ES/S3/WeKnora 集成、故障注入、部署。
38 lines
1.5 KiB
Go
38 lines
1.5 KiB
Go
package librarydiff
|
||
|
||
import "testing"
|
||
|
||
// F18:只改 title / summary / parent_slug 也必须在发布前差异里出现。
|
||
// 旧比较结构根本没读 summary/parent_slug,title 读了却不比较——三种改动都被漏报,
|
||
// 于是"预览无变化"可以对应一次真实的元数据变更。
|
||
func TestWikiMetadataChangeMustAppear(t *testing.T) {
|
||
for _, field := range []string{"title", "summary", "parent_slug"} {
|
||
t.Run(field, func(t *testing.T) {
|
||
before := map[string]any{"slug": "page-a", "title": "old", "summary": "old",
|
||
"parent_slug": "old", "content": "unchanged"}
|
||
after := map[string]any{"slug": "page-a", "title": "old", "summary": "old",
|
||
"parent_slug": "old", "content": "unchanged"}
|
||
after[field] = "new"
|
||
result, err := CompareWiki([]map[string]any{before}, []map[string]any{after})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if result.Updated != 1 {
|
||
t.Fatalf("只改 %s 必须记成 updated=1:%+v", field, result)
|
||
}
|
||
if len(result.Pages) != 1 || len(result.Pages[0].Changed) == 0 {
|
||
t.Fatalf("差异项必须列出改了哪些字段:%+v", result.Pages)
|
||
}
|
||
})
|
||
}
|
||
// 反例:完全没有变化时不报差异(避免把"无变化"也渲染成更新)。
|
||
same := map[string]any{"slug": "page-b", "title": "t", "summary": "s", "parent_slug": "p", "content": "c"}
|
||
result, err := CompareWiki([]map[string]any{same}, []map[string]any{same})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if result.Updated != 0 || len(result.Pages) != 0 {
|
||
t.Fatalf("无变化不应产生差异:%+v", result)
|
||
}
|
||
}
|