Files
knowledge-governance/cmd/worker/library_versions.go
T
姚章浩 0088004cd6 fix(library): 当前生效版本改按精确查询
接管分类、基线判定、已知索引映射、同步闸口的创建判据与归档保留的保护标记,
原来都用 LibraryVersions(scope, 50/100) 这个分页窗口再在内存里筛。生效版本每
scope 只有一行、可能很久以前登记,历史一长就被挤出窗口 —— 于是"已生效"被读成
"没有生效":接管会把已经上线的目标重写一遍,基线入口会给已有权威版本的目标
再建一次基线。

现在统一走两条精确查询:LibraryCurrentVersion(kind='production_effective' AND
superseded_at IS NULL,每 scope 至多一行)与 LibraryVersionOfSnapshot(按快照
身份取回"已顶替"证据)。多行、自相矛盾、读不到一律 fail closed;同一含义只留
一份实现(knownIndexMapping 同步与恢复共用)。LibraryVersions 仍是 1..100 的
展示窗口,只用于列表。

验证:真实 PG 上先复现(窗口实现 ⇒ found=false),改后 7 条 store 契约用例与
worker 端到端判定用例全绿;本机 make verify 15 项 success(用例 1264 通过 /
0 失败 / 93 已声明可选跳过)。
2026-10-30 03:09:21 +08:00

84 lines
3.5 KiB
Go

package main
import (
"context"
"fmt"
"knowledge-governance/internal/governance"
)
// libraryVersionIndex 是"当前生效版本"与"某个快照自己的登记"的精确查询口。
//
// 为什么不是 LibraryVersions(scope, N):那是分页展示用的窗口,历史超过上限后
// "存在"会被读成"不存在"。接管/基线/同步闸口的判定都必须走这里,同一含义只有
// 一份实现(两个查询在 Store 里都是按条件精确命中,没有排序窗口)。
type libraryVersionIndex interface {
LibraryCurrentVersion(ctx context.Context, scope string) (governance.LibraryVersion, bool, error)
LibraryVersionOfSnapshot(ctx context.Context, scope string, kind governance.LibraryVersionKind,
snapshotID string) (governance.LibraryVersion, bool, error)
}
// libraryVersionReader 是"当前生效版本 + 展示窗口"的组合:已知索引映射只需要这两样。
type libraryVersionReader interface {
LibraryCurrentVersion(ctx context.Context, scope string) (governance.LibraryVersion, bool, error)
LibraryVersions(ctx context.Context, scope string, limit int) ([]governance.LibraryVersion, error)
}
// libraryVersionFacts 是接管判定需要的两个事实:
// - Current/HasCurrent:这个 scope 现在生效的是哪一份(每 scope 至多一行);
// - Snapshot/HasSnapshot:本次作业的目标快照自己有没有生效登记(含"已顶替")。
//
// Err 非空表示"读不到",调用方必须按未知处理,不许降级成"没有证据"。
type libraryVersionFacts struct {
Current governance.LibraryVersion
HasCurrent bool
Snapshot governance.LibraryVersion
HasSnapshot bool
Err error
}
// loadLibraryVersionFacts 用两次精确查询拼出判定事实;任一查询失败都返回 Err。
func loadLibraryVersionFacts(ctx context.Context, store libraryVersionIndex, scope, targetSnapshotID string) libraryVersionFacts {
facts := libraryVersionFacts{}
current, hasCurrent, err := store.LibraryCurrentVersion(ctx, scope)
if err != nil {
facts.Err = fmt.Errorf("读不到当前生效版本:%w", err)
return facts
}
facts.Current, facts.HasCurrent = current, hasCurrent
if targetSnapshotID == "" {
return facts
}
snapshot, hasSnapshot, err := store.LibraryVersionOfSnapshot(ctx, scope,
governance.LibraryVersionProductionEffective, targetSnapshotID)
if err != nil {
facts.Err = fmt.Errorf("读不到快照 %s 的生效登记:%w", targetSnapshotID, err)
return facts
}
facts.Snapshot, facts.HasSnapshot = snapshot, hasSnapshot
return facts
}
// knownIndexMapping 取"上一次登记过的索引映射",作为装配目标时的映射基线。
//
// 权威来源是**当前生效版本**的登记证据;只有在它没有记录映射时,才退回到最近
// 成功记录里的那一份(恢复会把旧版本顶替掉,这时仍要拿到生产实际用的映射)。
// 退化只会让"索引被改动"判定偏严,不会误放行覆盖。同步与恢复共用这一份实现。
func knownIndexMapping(ctx context.Context, store libraryVersionReader, scope string) string {
if current, found, err := store.LibraryCurrentVersion(ctx, scope); err == nil && found {
if raw, ok := current.Evidence["index_mapping_sha256"].(string); ok && raw != "" {
return raw
}
}
versions, err := store.LibraryVersions(ctx, scope, 50)
if err != nil {
return ""
}
for _, item := range versions {
if raw, ok := item.Evidence["index_mapping_sha256"].(string); ok && raw != "" {
return raw
}
}
return ""
}