8febe12d7d
真实本机 PG/ES/MinIO:v071 目录镜像 10 模式、结构引用/游标 15 模式、 8 表×2 外部范围身份矩阵、13 模式自然键(含软删除与合法同 parent 正例)、 备份期间三注入、收尾故障与授权撤销两个真实治理 PG 用例。 需隔离服务的用例在 test-scope.json 显式声明:普通 CI 跳过不算通过。
153 lines
6.6 KiB
Go
153 lines
6.6 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"knowledge-governance/internal/governance"
|
|
"knowledge-governance/internal/libraryagent"
|
|
"knowledge-governance/internal/librarysnapshot"
|
|
)
|
|
|
|
func legacySourceFixture(t *testing.T, store *Store) (string, string, governance.LibrarySourceIdentity) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
root := t.TempDir()
|
|
if e := os.Chmod(root, 0700); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
archive, e := librarysnapshot.Open(root, 1024*1024)
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
defer archive.Close()
|
|
m := librarysnapshot.Manifest{ValidationPolicy: librarysnapshot.ArtifactAcceptancePolicy, Format: librarysnapshot.Format, Scope: "internal", TenantID: 10000, KnowledgeBaseID: "kb-legacy", CreatedAt: time.Now().UTC(), Compatibility: librarysnapshot.Compatibility{WeKnoraVersion: "v0.7.1", SchemaVersion: 74, RetrieverEngine: "elasticsearch_v8", EmbeddingModel: "text-embedding-v4", EmbeddingDimension: 1024, IndexMappingSHA256: strings.Repeat("a", 64)}}
|
|
for _, entry := range []string{"database/knowledges", "database/chunks", "database/resources", "database/resource_bindings", "database/knowledge_tags", "database/knowledge_tag_relations", "wiki/wiki_pages", "wiki/wiki_folders", "wiki/wiki_page_issues", "wiki/wiki_log_entries", "index/chunks"} {
|
|
p, e := archive.Put(strings.NewReader("[]"))
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
x := strings.Split(entry, "/")
|
|
p.Kind = x[0]
|
|
p.Name = x[1]
|
|
m.Parts = append(m.Parts, p)
|
|
}
|
|
snapshot, e := archive.Seal(m)
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
source := governance.LibrarySourceIdentity{Project: "historical-project", App: "historical-app", Scope: m.Scope, TenantID: m.TenantID, KnowledgeBaseID: m.KnowledgeBaseID}
|
|
exp := libraryagent.NewExpectation(source.Project, source.App, "/original/archive")
|
|
e = store.RecordLibraryVersion(ctx, governance.LibraryVersion{ID: governance.LibraryVersionID(governance.LibraryVersionTestSealed, m.Scope, snapshot), Scope: m.Scope, Kind: governance.LibraryVersionTestSealed, SnapshotID: snapshot, SourceJobID: "legacy-capture", CreatedAt: m.CreatedAt, Evidence: map[string]any{"expectation_fingerprint": exp.Fingerprint, "capture_job": "legacy", "agent_result": map[string]any{"snapshot_id": snapshot, "agent_identity": map[string]any{"project": source.Project, "source_app": source.App, "archive_root": "/original/archive", "expectation_fingerprint": exp.Fingerprint}}}})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
return root, snapshot, source
|
|
}
|
|
|
|
func TestContractLegacySourceAssociationPreservesOriginalEvidence(t *testing.T) {
|
|
store, _ := newIdentityStore(t)
|
|
ctx := context.Background()
|
|
root, snapshot, source := legacySourceFixture(t, store)
|
|
binder, ok := any(store).(interface {
|
|
BindLegacyLibrarySource(context.Context, string, string, governance.LibrarySourceIdentity, string, time.Time) error
|
|
})
|
|
if !ok {
|
|
t.Fatal("verified legacy source association unavailable")
|
|
}
|
|
original, e := os.ReadFile(filepath.Join(root, "versions", snapshot+".json"))
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if _, err := store.db.Exec(ctx, `UPDATE library_versions SET evidence=evidence || '{"large_proof":9223372036854775807}'::jsonb`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var originalEvidence string
|
|
if err := store.db.QueryRow(ctx, `SELECT evidence::text FROM library_versions`).Scan(&originalEvidence); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
id, _ := source.ID()
|
|
if _, found, e := store.LibrarySourceCandidate(ctx, source.Scope, id); e != nil || found {
|
|
t.Fatal("legacy unbound candidate adopted without explicit association", e)
|
|
}
|
|
now := time.Now().UTC()
|
|
if e = binder.BindLegacyLibrarySource(ctx, snapshot, root, source, "admin", now); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
candidate, found, e := store.LibrarySourceCandidate(ctx, source.Scope, id)
|
|
if e != nil || !found || candidate.SnapshotID != snapshot || candidate.SourceJobID != "legacy-capture" {
|
|
t.Fatal("association not readable", e)
|
|
}
|
|
if candidate.Evidence["capture_job"] != "legacy" {
|
|
t.Fatal("original evidence overwritten")
|
|
}
|
|
if e = binder.BindLegacyLibrarySource(ctx, snapshot, root, source, "another-admin", now.Add(time.Second)); e != nil {
|
|
t.Fatal("idempotent association", e)
|
|
}
|
|
var preserved bool
|
|
if err := store.db.QueryRow(ctx, `SELECT (evidence - 'source_id' - 'source_identity' - 'legacy_source_binding')=$1::jsonb FROM library_versions`, originalEvidence).Scan(&preserved); err != nil || !preserved {
|
|
t.Fatal("historical evidence changed, including bigint", err)
|
|
}
|
|
var bindingActor string
|
|
if err := store.db.QueryRow(ctx, `SELECT evidence->'legacy_source_binding'->>'actor' FROM library_versions`).Scan(&bindingActor); err != nil || bindingActor != "admin" {
|
|
t.Fatal("association replay rewrote audit actor", err)
|
|
}
|
|
changed := source
|
|
changed.App = "other-app"
|
|
if e = binder.BindLegacyLibrarySource(ctx, snapshot, root, changed, "admin", now); e == nil {
|
|
t.Fatal("source rebound")
|
|
}
|
|
after, e := os.ReadFile(filepath.Join(root, "versions", snapshot+".json"))
|
|
if e != nil || string(original) != string(after) {
|
|
t.Fatal("historical manifest changed", e)
|
|
}
|
|
}
|
|
|
|
func TestContractLegacySourceAssociationRejectsInsufficientProof(t *testing.T) {
|
|
for _, scenario := range []string{"project", "tenant", "fingerprint", "missing-origin", "snapshot", "partial-binding"} {
|
|
t.Run(scenario, func(t *testing.T) {
|
|
store, _ := newIdentityStore(t)
|
|
ctx := context.Background()
|
|
root, snapshot, source := legacySourceFixture(t, store)
|
|
var mutation string
|
|
switch scenario {
|
|
case "project":
|
|
source.Project = "other-project"
|
|
case "tenant":
|
|
source.TenantID++
|
|
case "fingerprint":
|
|
mutation = `UPDATE library_versions SET evidence=jsonb_set(evidence,'{expectation_fingerprint}','"wrong"'::jsonb)`
|
|
case "missing-origin":
|
|
mutation = `UPDATE library_versions SET evidence=evidence-'agent_result'`
|
|
case "snapshot":
|
|
mutation = `UPDATE library_versions SET evidence=jsonb_set(evidence,'{agent_result,snapshot_id}','"different"'::jsonb)`
|
|
case "partial-binding":
|
|
mutation = `UPDATE library_versions SET evidence=evidence || '{"source_identity":{}}'::jsonb`
|
|
}
|
|
if mutation != "" {
|
|
if _, e := store.db.Exec(ctx, mutation); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
}
|
|
var before string
|
|
if e := store.db.QueryRow(ctx, `SELECT evidence::text FROM library_versions`).Scan(&before); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if e := store.BindLegacyLibrarySource(ctx, snapshot, root, source, "admin", time.Now()); e == nil {
|
|
t.Fatal("unproven source accepted")
|
|
}
|
|
var after string
|
|
if e := store.db.QueryRow(ctx, `SELECT evidence::text FROM library_versions`).Scan(&after); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if before != after {
|
|
t.Fatal("rejected association changed historical evidence")
|
|
}
|
|
})
|
|
}
|
|
}
|