diff --git a/CHANGELOG.md b/CHANGELOG.md index e66be62..2ae9628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ - worker `library.sync` 接执行编排:先闸口、再授权,写入阶段落库;生产目标适配器未配置时明确失败。 - 页面"生产保护与恢复"显示发布授权状态与有效期;系统设置→存储管理显示容量门禁、受保护版本与清理预览。 +### Added(Wiki 页面级差异) + +- `GET /library/overview` 的差异结果新增 `wiki`:两份快照 `wiki_pages` 的新增/更新/撤销页数与样例, + 明确标注页面级、不代表实体/边/引用重算;缺部件时如实 unknown。 + ### Changed - 启动同步的缺项包含"没有与目标快照匹配的有效发布授权"与容量预算;作业只有在编排返回成功时才标成功。 diff --git a/docs/library-acceptance-matrix.md b/docs/library-acceptance-matrix.md index 27f7c95..93415fb 100644 --- a/docs/library-acceptance-matrix.md +++ b/docs/library-acceptance-matrix.md @@ -19,7 +19,7 @@ A06(内容、问答、引用图片效果、性能报告)已被用户取消 | --- | --- | --- | --- | --- | | V01 | 只有对内/对外两个目标,身份来自配置映射 | `internal/httpapi/library_*.go`(scope 只接受 internal/external);`web/src/ReleaseTargets.tsx` | 已实现·已验证 | overview 对非法 scope 返回 400;真实归档里 internal/external 各自解析到唯一 KB(0c84a098… / 对外 KB) | | V02 | 全部解析/索引/Wiki 就绪,验收绑定本版 | `internal/releaseadapter/v071/library_source_settlement.go`;页面准备清单 | 已实现·未验证 | 结清检查(settlement)在封存前执行且缺项阻断;但对"实时测试库此刻就绪"仍需一次真实封存复核;页面已注明依据的是哪次快照 | -| V03 | 删除文档后 Wiki 共享正文/实体/边/引用真实变化 | — | 未实现 | 现在只能证明 Wiki 完整快照被保存;删除贡献的语义重算与证据未实现,界面不声称已满足 | +| V03 | 删除文档后 Wiki 共享正文/实体/边/引用真实变化 | `wikiDiffOf`(页面级差异) | 部分实现 | 真实归档验证:对内 vs 对外 = 新增 31 页 / 撤销 1 页,反向镜像一致。**页面级不等于**实体/概念/边/引用的语义重算,后者仍需 WeKnora 内部证据,未实现也不声称 | | V04 | 完整固定版本:文档集合+处理结果+资源+索引+完整 Wiki | `internal/librarysnapshot/*`(10 表 + 索引 + 资源 + Wiki 部件);`artifacts` 清单 | 已实现·已验证 | 真实快照 f992d64e…:4 文档/38 分块/122 索引/21 资源/31 Wiki 页/55,309,044 字节;部件摘要逐项校验 | | V05 | 导出中源变更则拒绝或隔离 | `internal/libraryfreeze/*`(持久 journal、源锁、暂停/恢复);`ProjectController` | 已实现·未验证 | 冻结窗口内会重新比对兼容契约;跨"项目外写入者"的证明仍缺,需一次带并发写入的隔离演练 | | V06 | 只同步知识数据,不含账号/会话/凭据 | 导出白名单(10 张知识表 + 知识域索引 + 资源 + Wiki) | 已实现·已验证 | 部件清单里没有 users/sessions/api_keys;真实归档部件名可逐项核对 | @@ -54,6 +54,7 @@ A06(内容、问答、引用图片效果、性能报告)已被用户取消 比较两份归档快照的 `knowledges` 行数组,输出新增/更新/撤销。真实归档上验证: 对内快照 4 篇对空集为"新增 4",反向为"撤销 4",缺失快照返回原因而不是空差异。 生产权威版本建立后即可直接给出生产→测试差异;在生产版本未知时页面保持"未接入"。 +Wiki 页面级差异单独返回(`diff.wiki`),明确标注"不代表实体/边/引用重算"。 ## 未通过项如何阻断 diff --git a/internal/httpapi/library_flow.go b/internal/httpapi/library_flow.go index 2205ee9..afabc67 100644 --- a/internal/httpapi/library_flow.go +++ b/internal/httpapi/library_flow.go @@ -54,6 +54,85 @@ type libraryDiffView struct { // 避免把某次历史差异当成"当前拟发布"。 BaseSnapshot string `json:"base_snapshot,omitempty"` TargetSnapshot string `json:"target_snapshot,omitempty"` + // Wiki 是**页面级**差异:只能证明页面被增/改/撤,不能证明共享正文、实体、 + // 概念或边被重新计算——后者需要 WeKnora 内部语义,本平台不做也不声称。 + Wiki *libraryWikiDiffView `json:"wiki,omitempty"` +} + +type libraryWikiDiffView struct { + State string `json:"state"` // computed | unknown + Reason string `json:"reason,omitempty"` + Added int `json:"added"` + Updated int `json:"updated"` + Removed int `json:"removed"` + // Sample 只列前若干页,避免把整库 Wiki 列表塞进接口。 + Sample []string `json:"sample"` + Note string `json:"note"` +} + +// wikiDiffOf 比较两份快照的 wiki_pages 行数组(页面级)。 +// 它不能替代"删除文档后共享正文/实体/边/引用如何变化"的语义证据。 +func wikiDiffOf(archive *librarysnapshot.Archive, baseID, targetID string) *libraryWikiDiffView { + unknown := func(reason string) *libraryWikiDiffView { + return &libraryWikiDiffView{State: "unknown", Reason: reason, Sample: []string{}, + Note: "页面级比对缺失时不能声称 Wiki 已同步"} + } + rows := func(id string) ([]map[string]any, string) { + manifest, err := archive.Load(id) + if err != nil { + return nil, "快照不可读" + } + for _, part := range manifest.Parts { + if part.Kind != "wiki" || part.Name != "wiki_pages" { + continue + } + reader, err := archive.OpenPart(part) + if err != nil { + return nil, "Wiki 正文不可读" + } + defer reader.Close() + parsed, err := librarydiff.ReadDocuments(reader) + if err != nil { + return nil, "Wiki 行格式不符" + } + return parsed, "" + } + return nil, "快照里没有 wiki_pages 部件" + } + base, reason := rows(baseID) + if reason != "" { + return unknown("生产版本:" + reason) + } + target, reason := rows(targetID) + if reason != "" { + return unknown("测试版本:" + reason) + } + result, err := librarydiff.Compare(base, target) + if err != nil { + return unknown("Wiki 页面比对失败:" + err.Error()) + } + view := &libraryWikiDiffView{State: "computed", Added: len(result.Added), Updated: len(result.Updated), + Removed: len(result.Removed), Sample: []string{}, + Note: "页面级差异:只说明页面被增/改/撤,不代表实体、概念、边或引用已重算"} + for _, item := range result.Added { + if len(view.Sample) >= 20 { + break + } + view.Sample = append(view.Sample, "新增 "+item.Title) + } + for _, item := range result.Updated { + if len(view.Sample) >= 20 { + break + } + view.Sample = append(view.Sample, "更新 "+item.Title) + } + for _, item := range result.Removed { + if len(view.Sample) >= 20 { + break + } + view.Sample = append(view.Sample, "撤销 "+item.Title) + } + return view } // computeLibraryDiff 用归档里的 knowledges 行数组比较两个快照: @@ -115,6 +194,7 @@ func computeLibraryDiff(root, baseID, targetID string) (libraryDiffView, bool) { } view := libraryDiffView{State: "computed", Added: []any{}, Updated: []any{}, Removed: []any{}, BaseSnapshot: baseID, TargetSnapshot: targetID} + view.Wiki = wikiDiffOf(archive, baseID, targetID) for _, item := range result.Added { view.Added = append(view.Added, item) } diff --git a/web/src/ReleaseTargets.test.tsx b/web/src/ReleaseTargets.test.tsx index af17dda..dd78ab5 100644 --- a/web/src/ReleaseTargets.test.tsx +++ b/web/src/ReleaseTargets.test.tsx @@ -209,6 +209,14 @@ describe('ReleaseTargetsView(确认稿布局)', () => { removed: [{ id: 'doc-del-0003', title: '撤销的说明', digest: 'c'.repeat(64) }], base_snapshot: 'prod00000000', target_snapshot: 'test00000000', + wiki: { + state: 'computed', + added: 2, + updated: 1, + removed: 3, + sample: [], + note: '页面级差异', + }, }, }, }) @@ -218,6 +226,8 @@ describe('ReleaseTargetsView(确认稿布局)', () => { expect(html).toContain('撤销的说明 · doc-del-') expect(html).toContain('生产 prod00000000 → 测试 test00000000') expect(html).not.toContain('变更清单来自生产与测试版本对比') + expect(html).toContain('Wiki 页面:新增 2 · 更新 1 · 撤销 3') + expect(html).toContain('不代表实体/边/引用重算') }) it('准备信息注明快照时间与范围,不声称实时测试库已就绪', () => { diff --git a/web/src/ReleaseTargets.tsx b/web/src/ReleaseTargets.tsx index ad1673c..edf2ddb 100644 --- a/web/src/ReleaseTargets.tsx +++ b/web/src/ReleaseTargets.tsx @@ -311,6 +311,12 @@ function ChangeTable({ diff }: { diff: LibraryOverview['diff'] }) { )} + {diff.wiki && diff.wiki.state === 'computed' ? ( +
+ Wiki 页面:新增 {diff.wiki.added} · 更新 {diff.wiki.updated} · 撤销 {diff.wiki.removed} + (页面级,不代表实体/边/引用重算) +
+ ) : null} ) } diff --git a/web/src/librarySnapshots.ts b/web/src/librarySnapshots.ts index f45eef7..cfe5916 100644 --- a/web/src/librarySnapshots.ts +++ b/web/src/librarySnapshots.ts @@ -250,6 +250,16 @@ export interface LibraryDiff { /** 这次比对用的两个整库版本(生产 → 测试),避免把历史差异当成当前。 */ base_snapshot?: string target_snapshot?: string + /** 页面级 Wiki 差异;不代表实体、概念、边或引用被重算。 */ + wiki?: { + state: 'computed' | 'unknown' + reason?: string + added: number + updated: number + removed: number + sample: string[] + note: string + } } export interface LibraryStageRow {