Files
knowledge-governance/internal/platform/postgres/pp_import.go
T
zheromiku dcdc8d1ddd feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环):

- 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口)
- 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入
  parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截
- 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过):
  * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id)
  * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配
  * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数
  * 回滚 job payload 支持 prior_execution_id(strict 解码)
- 三通道发布+回滚演练实测:publish 4/4 verified,检索命中,
  rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false
- 前端 A·Data-console 风格全站统一,120 用例通过
- 门禁:go vet / go test 全包通过,gofmt 干净
2026-09-30 10:54:06 +08:00

174 lines
5.0 KiB
Go

package postgres
import (
"context"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
ppconnector "knowledge-governance/internal/connectors/pp"
)
type LegacyImportReport struct {
Connections int
SourceItems int
ContentObjects int
Versions int
Publications int
}
func LoadPPCurrentItems(ctx context.Context, pool *pgxpool.Pool, connectionID string) (map[string]ppconnector.CurrentItem, error) {
rows, err := pool.Query(ctx, `
SELECT stable_external_id, revision
FROM source_items
WHERE source_connection_id = $1
ORDER BY stable_external_id`, connectionID)
if err != nil {
return nil, fmt.Errorf("query current PP items: %w", err)
}
defer rows.Close()
items := make(map[string]ppconnector.CurrentItem)
for rows.Next() {
var sourceHash string
var item ppconnector.CurrentItem
if err := rows.Scan(&sourceHash, &item.Revision); err != nil {
return nil, fmt.Errorf("scan current PP item: %w", err)
}
items[sourceHash] = item
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate current PP items: %w", err)
}
return items, nil
}
func ImportPPLegacy(ctx context.Context, pool *pgxpool.Pool, plan ppconnector.ImportPlan) (LegacyImportReport, error) {
tx, err := pool.Begin(ctx)
if err != nil {
return LegacyImportReport{}, fmt.Errorf("begin PP legacy import: %w", err)
}
defer func() { _ = tx.Rollback(ctx) }()
report := LegacyImportReport{}
tag, err := tx.Exec(ctx, `INSERT INTO source_connections (
id, source_type, name, base_url, secret_ref, enabled, schedule, last_health
) VALUES ($1, $2, $3, $4, $5, $6, '{}'::jsonb, '{}'::jsonb)
ON CONFLICT DO NOTHING`,
plan.Connection.ID,
plan.Connection.Type,
plan.Connection.Name,
plan.Connection.BaseURL,
plan.Connection.SecretRef,
plan.Connection.Enabled,
)
if err != nil {
return LegacyImportReport{}, fmt.Errorf("insert PP source connection: %w", err)
}
report.Connections = int(tag.RowsAffected())
for _, content := range plan.ContentObjects {
tag, err = tx.Exec(ctx, `INSERT INTO content_objects (
id, sha256, size_bytes, mime, validated_format, local_spool_ref, content_state
) VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT DO NOTHING`,
content.ID,
content.SHA256,
content.SizeBytes,
content.MIME,
content.ValidatedFormat,
content.LocalSpoolRef,
content.State,
)
if err != nil {
return LegacyImportReport{}, fmt.Errorf("insert PP content object: %w", err)
}
report.ContentObjects += int(tag.RowsAffected())
}
for _, item := range plan.SourceItems {
tag, err = tx.Exec(ctx, `INSERT INTO source_items (
id, source_connection_id, stable_external_id, canonical_uri_hash,
parent_external_id, normalized_path, title, item_type, size_bytes,
mime, revision, visibility, visibility_origin, last_seen_scan_id,
source_change, absence_streak, identity_confidence
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9,
$10, $11, $12, $13, $14, $15, $16, 'stable_id'
)
ON CONFLICT DO NOTHING`,
item.ID,
item.SourceConnectionID,
item.StableExternalID,
item.CanonicalURIHash,
item.ParentExternalID,
item.NormalizedPath,
item.Title,
item.ItemType,
item.SizeBytes,
item.MIME,
item.Revision,
item.Visibility,
item.VisibilityOrigin,
item.LastSeenScanID,
item.SourceChange,
item.AbsenceStreak,
)
if err != nil {
return LegacyImportReport{}, fmt.Errorf("insert PP source item: %w", err)
}
report.SourceItems += int(tag.RowsAffected())
}
for _, version := range plan.Versions {
tag, err = tx.Exec(ctx, `INSERT INTO source_item_versions (
id, source_item_id, revision, content_object_id, content_state,
download_error, raw_record_summary, source_updated_at
) VALUES ($1, $2, $3, $4, $5, $6, CAST($7 AS JSONB), $8)
ON CONFLICT DO NOTHING`,
version.ID,
version.SourceItemID,
version.Revision,
version.ContentObjectID,
version.ContentState,
version.DownloadError,
string(version.RawRecordSummary),
version.SourceUpdatedAt,
)
if err != nil {
return LegacyImportReport{}, fmt.Errorf("insert PP source item version: %w", err)
}
report.Versions += int(tag.RowsAffected())
}
for _, publication := range plan.Publications {
tag, err = tx.Exec(ctx, `INSERT INTO test_publications (
id, source_item_version_id, test_kb_id, knowledge_id,
ingest_state, processing_state, retrieval_state,
parser_generation, index_generation, last_error, retry_count
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
ON CONFLICT DO NOTHING`,
publication.ID,
publication.SourceItemVersionID,
publication.TestKBID,
publication.KnowledgeID,
publication.IngestState,
publication.ProcessingState,
publication.RetrievalState,
publication.ParserGeneration,
publication.IndexGeneration,
publication.LastError,
publication.RetryCount,
)
if err != nil {
return LegacyImportReport{}, fmt.Errorf("insert PP test publication: %w", err)
}
report.Publications += int(tag.RowsAffected())
}
if err := tx.Commit(ctx); err != nil {
return LegacyImportReport{}, fmt.Errorf("commit PP legacy import: %w", err)
}
return report, nil
}