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 干净
131 lines
4.1 KiB
Go
131 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
postgresstore "knowledge-governance/internal/platform/postgres"
|
|
"knowledge-governance/internal/platform/weknora"
|
|
"knowledge-governance/internal/workflows/testweknora"
|
|
)
|
|
|
|
type probeConfig struct {
|
|
DatabaseURL string
|
|
Concurrency int
|
|
Online bool
|
|
BaseURL string
|
|
APIKeyFile string
|
|
ChunkSampleEvery int
|
|
}
|
|
|
|
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 {
|
|
config, err := parseProbeConfig(args, getenv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pool, err := pgxpool.New(ctx, config.DatabaseURL)
|
|
if err != nil {
|
|
return fmt.Errorf("open database: %w", err)
|
|
}
|
|
defer pool.Close()
|
|
if err := pool.Ping(ctx); err != nil {
|
|
return fmt.Errorf("ping database: %w", err)
|
|
}
|
|
store := postgresstore.NewStore(pool)
|
|
legacy, err := store.ListLegacyVerificationCandidates(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
candidates := make([]testweknora.LegacyCandidate, 0, len(legacy))
|
|
for _, candidate := range legacy {
|
|
candidates = append(candidates, testweknora.LegacyCandidate{
|
|
ID: candidate.ID, TestKBID: candidate.TestKBID, KnowledgeID: candidate.KnowledgeID,
|
|
})
|
|
}
|
|
if !config.Online {
|
|
_, _ = io.WriteString(stdout, testweknora.FormatDryRunReport(candidates, config.Concurrency))
|
|
return nil
|
|
}
|
|
apiKey, err := weknora.ReadAPIKeyFile(config.APIKeyFile)
|
|
if err != nil {
|
|
return fmt.Errorf("read test WeKnora API key: %w", err)
|
|
}
|
|
client, err := weknora.NewClient(weknora.ClientConfig{
|
|
BaseURL: config.BaseURL,
|
|
APIKey: apiKey,
|
|
Timeout: 30 * time.Second,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("create test WeKnora client: %w", err)
|
|
}
|
|
report, err := testweknora.VerifyLegacyConcurrent(ctx, client, candidates, config.ChunkSampleEvery, config.Concurrency)
|
|
if err != nil {
|
|
return fmt.Errorf("verify historical test publications: %w", err)
|
|
}
|
|
_, _ = io.WriteString(stdout, testweknora.FormatLegacyVerificationReport(report))
|
|
return nil
|
|
}
|
|
|
|
func parseProbeConfig(args []string, getenv func(string) string) (probeConfig, error) {
|
|
flags := flag.NewFlagSet("test-weknora-probe", flag.ContinueOnError)
|
|
flags.SetOutput(io.Discard)
|
|
databaseURL := flags.String("database-url", "", "PostgreSQL connection URL (or DATABASE_URL)")
|
|
concurrency := flags.Int("concurrency", 4, "planned read-only verify concurrency for the online phase")
|
|
online := flags.Bool("online", false, "run authenticated read-only verification")
|
|
baseURL := flags.String("base-url", "", "HTTPS WeKnora base URL")
|
|
apiKeyFile := flags.String("api-key-file", "", "mode-0600 WeKnora API key file")
|
|
chunkSampleEvery := flags.Int("chunk-sample-every", 25, "check one completed Knowledge chunk count every N items; 0 disables")
|
|
if err := flags.Parse(args); err != nil {
|
|
return probeConfig{}, fmt.Errorf("parse arguments: %w", err)
|
|
}
|
|
if *databaseURL == "" {
|
|
*databaseURL = getenv("DATABASE_URL")
|
|
}
|
|
if *databaseURL == "" {
|
|
return probeConfig{}, fmt.Errorf("-database-url or DATABASE_URL is required")
|
|
}
|
|
if *concurrency < 1 || *concurrency > 16 || *chunkSampleEvery < 0 {
|
|
return probeConfig{}, fmt.Errorf("concurrency must be 1..16 and chunk-sample-every must be non-negative")
|
|
}
|
|
if *baseURL == "" {
|
|
*baseURL = getenv("TEST_WEKNORA_BASE_URL")
|
|
}
|
|
if *apiKeyFile == "" {
|
|
*apiKeyFile = getenv("TEST_WEKNORA_API_KEY_FILE")
|
|
}
|
|
if *online {
|
|
parsed, err := url.Parse(strings.TrimSpace(*baseURL))
|
|
if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil {
|
|
return probeConfig{}, fmt.Errorf("online verification requires an HTTPS base-url")
|
|
}
|
|
if !filepath.IsAbs(*apiKeyFile) {
|
|
return probeConfig{}, fmt.Errorf("online verification requires an absolute api-key-file")
|
|
}
|
|
}
|
|
return probeConfig{
|
|
DatabaseURL: *databaseURL,
|
|
Concurrency: *concurrency,
|
|
Online: *online,
|
|
BaseURL: strings.TrimRight(strings.TrimSpace(*baseURL), "/"),
|
|
APIKeyFile: *apiKeyFile,
|
|
ChunkSampleEvery: *chunkSampleEvery,
|
|
}, nil
|
|
}
|