dcdc8d1ddd
全栈知识治理平台首次入库(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 干净
132 lines
4.2 KiB
Go
132 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
postgresstore "knowledge-governance/internal/platform/postgres"
|
|
"knowledge-governance/internal/platform/spool"
|
|
)
|
|
|
|
type manualSeeder interface {
|
|
SeedManualPublication(context.Context, postgresstore.ManualPublicationSeedRequest) (postgresstore.ManualPublicationSeedResult, error)
|
|
}
|
|
|
|
type seedConfig struct {
|
|
SpoolRoot string
|
|
SpoolRef string
|
|
Format string
|
|
Title string
|
|
TestKBID string
|
|
MaxBytes int64
|
|
Now time.Time
|
|
}
|
|
|
|
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, output io.Writer) error {
|
|
flags := flag.NewFlagSet("test-publication-seed", flag.ContinueOnError)
|
|
flags.SetOutput(io.Discard)
|
|
spoolRef := flags.String("spool-ref", "", "opaque relative spool reference")
|
|
format := flags.String("format", "md", "validated artifact format")
|
|
title := flags.String("title", "", "safe display title")
|
|
if err := flags.Parse(args); err != nil {
|
|
return errors.New("invalid seed arguments")
|
|
}
|
|
databaseURL := getenv("DATABASE_URL")
|
|
spoolRoot := getenv("GOVERNANCE_SPOOL_ROOT")
|
|
if spoolRoot == "" {
|
|
spoolRoot = "/var/lib/governance/spool"
|
|
}
|
|
testKBID := getenv("TEST_WEKNORA_KB_ID")
|
|
maxBytes := int64(50 * 1024 * 1024)
|
|
if raw := getenv("TEST_WEKNORA_MAX_UPLOAD_BYTES"); raw != "" {
|
|
parsed, err := strconv.ParseInt(raw, 10, 64)
|
|
if err != nil || parsed <= 0 || parsed > 50*1024*1024 {
|
|
return errors.New("invalid TEST_WEKNORA_MAX_UPLOAD_BYTES")
|
|
}
|
|
maxBytes = parsed
|
|
}
|
|
if databaseURL == "" || testKBID == "" || *spoolRef == "" || *title == "" {
|
|
return errors.New("DATABASE_URL, TEST_WEKNORA_KB_ID, --spool-ref and --title are required")
|
|
}
|
|
pool, err := pgxpool.New(ctx, databaseURL)
|
|
if err != nil {
|
|
return errors.New("open governance database")
|
|
}
|
|
defer pool.Close()
|
|
if err := pool.Ping(ctx); err != nil {
|
|
return errors.New("connect governance database")
|
|
}
|
|
if err := postgresstore.ApplyMigrations(ctx, pool); err != nil {
|
|
return errors.New("apply governance migrations")
|
|
}
|
|
result, err := seedFromSpool(ctx, postgresstore.NewStore(pool), seedConfig{
|
|
SpoolRoot: spoolRoot,
|
|
SpoolRef: *spoolRef,
|
|
Format: *format,
|
|
Title: *title,
|
|
TestKBID: testKBID,
|
|
MaxBytes: maxBytes,
|
|
Now: time.Now().UTC(),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.NewEncoder(output).Encode(result)
|
|
}
|
|
|
|
func seedFromSpool(ctx context.Context, store manualSeeder, config seedConfig) (postgresstore.ManualPublicationSeedResult, error) {
|
|
if store == nil {
|
|
return postgresstore.ManualPublicationSeedResult{}, errors.New("manual publication store is required")
|
|
}
|
|
expectation, err := spool.InspectVerified(config.SpoolRoot, config.SpoolRef, config.Format, config.MaxBytes)
|
|
if err != nil {
|
|
return postgresstore.ManualPublicationSeedResult{}, errors.New("inspect manual spool artifact")
|
|
}
|
|
mime, ok := mimeForFormat(config.Format)
|
|
if !ok {
|
|
return postgresstore.ManualPublicationSeedResult{}, errors.New("unsupported manual publication format")
|
|
}
|
|
result, err := store.SeedManualPublication(ctx, postgresstore.ManualPublicationSeedRequest{
|
|
TestKBID: config.TestKBID,
|
|
Title: config.Title,
|
|
SHA256: expectation.SHA256,
|
|
SizeBytes: expectation.SizeBytes,
|
|
MIME: mime,
|
|
ValidatedFormat: expectation.Format,
|
|
LocalSpoolRef: expectation.RelativeRef,
|
|
Now: config.Now,
|
|
})
|
|
if err != nil {
|
|
return postgresstore.ManualPublicationSeedResult{}, fmt.Errorf("seed manual publication: %w", err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func mimeForFormat(format string) (string, bool) {
|
|
mimes := map[string]string{
|
|
"md": "text/markdown", "txt": "text/plain", "csv": "text/csv", "json": "application/json",
|
|
"pdf": "application/pdf", "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
"doc": "application/msword", "xls": "application/vnd.ms-excel", "ppt": "application/vnd.ms-powerpoint",
|
|
}
|
|
value, ok := mimes[format]
|
|
return value, ok
|
|
}
|