Files
knowledge-governance/cmd/pp-ledger-import/main.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

154 lines
4.3 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
ppconnector "knowledge-governance/internal/connectors/pp"
postgresstore "knowledge-governance/internal/platform/postgres"
)
func main() {
if err := run(context.Background(), os.Args[1:], os.Getenv, os.Stdout); err != nil {
log.Fatal(err)
}
}
func run(ctx context.Context, args []string, getenv func(string) string, stdout io.Writer) error {
flags := flag.NewFlagSet("pp-ledger-import", flag.ContinueOnError)
flags.SetOutput(io.Discard)
ledgerPath := flags.String("ledger", "", "path to the legacy PP state JSONL")
manifestPath := flags.String("manifest", "", "path to the validated download manifest JSON")
dryRun := flags.Bool("dry-run", false, "validate and print aggregate counts without database access")
if err := flags.Parse(args); err != nil {
return fmt.Errorf("parse arguments: %w", err)
}
if *ledgerPath == "" || *manifestPath == "" {
return fmt.Errorf("-ledger and -manifest are required")
}
ledger, err := os.Open(*ledgerPath)
if err != nil {
return fmt.Errorf("open PP ledger: %w", err)
}
snapshot, foldErr := ppconnector.FoldLedger(ledger)
closeErr := ledger.Close()
if foldErr != nil {
return foldErr
}
if closeErr != nil {
return fmt.Errorf("close PP ledger: %w", closeErr)
}
manifest, err := os.Open(*manifestPath)
if err != nil {
return fmt.Errorf("open PP download manifest: %w", err)
}
recovered, recovery, recoveryErr := ppconnector.RecoverMissingSHA256(snapshot, manifest)
closeErr = manifest.Close()
if recoveryErr != nil {
return recoveryErr
}
if closeErr != nil {
return fmt.Errorf("close PP download manifest: %w", closeErr)
}
overLimitManifest, err := os.Open(*manifestPath)
if err != nil {
return fmt.Errorf("reopen PP download manifest: %w", err)
}
complete, overLimit, overLimitErr := ppconnector.MergeOverLimitManifest(recovered, overLimitManifest)
closeErr = overLimitManifest.Close()
if overLimitErr != nil {
return overLimitErr
}
if closeErr != nil {
return fmt.Errorf("close PP download manifest: %w", closeErr)
}
if err := ppconnector.ValidateSnapshot(complete); err != nil {
return fmt.Errorf("validate PP snapshot: %w", err)
}
plan, err := ppconnector.BuildImportPlan(complete)
if err != nil {
return fmt.Errorf("build PP import plan: %w", err)
}
completed := 0
aliases := 0
for _, item := range complete.Items {
switch item.Status {
case ppconnector.StatusCompleted:
completed++
case ppconnector.StatusContentAlias:
aliases++
}
}
_, _ = fmt.Fprintf(stdout,
"events=%d\nsource_items=%d\ncompleted=%d\ncontent_aliases=%d\nover_limit=%d\nrecovered_sha256=%d\nverified_bytes=%d\ncontent_objects=%d\nversions=%d\npublications=%d\n",
complete.EventCount,
len(plan.SourceItems),
completed,
aliases,
overLimit.Added,
recovery.Recovered,
recovery.VerifiedBytes,
len(plan.ContentObjects),
len(plan.Versions),
len(plan.Publications),
)
if *dryRun {
_, _ = fmt.Fprintln(stdout, "dry_run=true")
return nil
}
databaseURL := strings.TrimSpace(getenv("DATABASE_URL"))
if databaseURL == "" {
return fmt.Errorf("DATABASE_URL is required unless -dry-run is used")
}
pool, err := pgxpool.New(ctx, databaseURL)
if err != nil {
return fmt.Errorf("open governance database: %w", err)
}
defer pool.Close()
if err := pool.Ping(ctx); err != nil {
return fmt.Errorf("ping governance database: %w", err)
}
if err := postgresstore.ApplyMigrations(ctx, pool); err != nil {
return err
}
report, err := postgresstore.ImportPPLegacy(ctx, pool, plan)
if err != nil {
return err
}
_, _ = fmt.Fprintf(stdout,
"inserted_connections=%d\ninserted_source_items=%d\ninserted_content_objects=%d\ninserted_versions=%d\ninserted_publications=%d\n",
report.Connections,
report.SourceItems,
report.ContentObjects,
report.Versions,
report.Publications,
)
current, err := postgresstore.LoadPPCurrentItems(ctx, pool, plan.Connection.ID)
if err != nil {
return err
}
reconciliation, err := ppconnector.PlanReconciliation(complete, current)
if err != nil {
return err
}
_, _ = fmt.Fprintf(stdout,
"reconcile_unchanged=%d\nreconcile_adopted=%d\nreconcile_over_limit=%d\nplanned_uploads=%d\n",
reconciliation.Unchanged,
reconciliation.Adopted,
reconciliation.OverLimit,
reconciliation.UploadRequired,
)
return nil
}