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 干净
138 lines
5.3 KiB
Go
138 lines
5.3 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"knowledge-governance/internal/governance"
|
|
)
|
|
|
|
func (s *Store) StartTestPublications(ctx context.Context, request governance.StartTestPublicationsRequest) (governance.StartTestPublicationsResult, error) {
|
|
result := governance.StartTestPublicationsResult{Selected: len(request.PublicationIDs)}
|
|
operator := strings.TrimSpace(request.OperatorRef)
|
|
reason := strings.TrimSpace(request.Reason)
|
|
if len(request.PublicationIDs) == 0 || len(request.PublicationIDs) > 100 || operator == "" || len(operator) > 128 || reason == "" || len(reason) > 500 || request.Now.IsZero() {
|
|
return governance.StartTestPublicationsResult{}, errors.New("invalid processing confirmation")
|
|
}
|
|
seen := make(map[string]bool, len(request.PublicationIDs))
|
|
for _, publicationID := range request.PublicationIDs {
|
|
if !validRouteID(publicationID) || seen[publicationID] {
|
|
return governance.StartTestPublicationsResult{}, errors.New("invalid processing publication IDs")
|
|
}
|
|
seen[publicationID] = true
|
|
}
|
|
|
|
var tx pgx.Tx
|
|
var err error
|
|
ownsTransaction := false
|
|
if existing, ok := s.db.(pgx.Tx); ok {
|
|
tx = existing
|
|
} else {
|
|
beginner, ok := s.db.(interface {
|
|
Begin(context.Context) (pgx.Tx, error)
|
|
})
|
|
if !ok {
|
|
return governance.StartTestPublicationsResult{}, errors.New("processing confirmation transactions are unavailable")
|
|
}
|
|
var err error
|
|
tx, err = beginner.Begin(ctx)
|
|
if err != nil {
|
|
return governance.StartTestPublicationsResult{}, fmt.Errorf("begin processing confirmation: %w", err)
|
|
}
|
|
ownsTransaction = true
|
|
defer tx.Rollback(ctx)
|
|
}
|
|
|
|
for _, publicationID := range request.PublicationIDs {
|
|
var ready, confirmed, uploadJobExists bool
|
|
err = tx.QueryRow(ctx, `
|
|
SELECT p.ingest_state='pending'
|
|
AND p.processing_state='pending'
|
|
AND p.retrieval_state='unpublished'
|
|
AND p.knowledge_id=''
|
|
AND version.content_state='verified'
|
|
AND content.content_state='verified'
|
|
AND content.local_spool_ref<>''
|
|
AND content.size_bytes>0 AS ready,
|
|
EXISTS(SELECT 1 FROM processing_confirmations confirmation WHERE confirmation.publication_id=p.id),
|
|
EXISTS(SELECT 1 FROM jobs job WHERE job.type='test.upload' AND job.payload->>'publication_id'=p.id)
|
|
FROM test_publications p
|
|
JOIN source_item_versions version ON version.id=p.source_item_version_id
|
|
JOIN content_objects content ON content.id=version.content_object_id
|
|
WHERE p.id=$1
|
|
FOR UPDATE OF p`, publicationID).Scan(&ready, &confirmed, &uploadJobExists)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
result.Unavailable++
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return governance.StartTestPublicationsResult{}, fmt.Errorf("inspect processing plan: %w", err)
|
|
}
|
|
if confirmed || uploadJobExists {
|
|
result.AlreadyStarted++
|
|
continue
|
|
}
|
|
if !ready {
|
|
result.Unavailable++
|
|
continue
|
|
}
|
|
confirmationID, err := newOpaqueID("pc-")
|
|
if err != nil {
|
|
return governance.StartTestPublicationsResult{}, err
|
|
}
|
|
jobID, err := newOpaqueID("job-")
|
|
if err != nil {
|
|
return governance.StartTestPublicationsResult{}, err
|
|
}
|
|
payload, err := json.Marshal(map[string]string{"publication_id": publicationID})
|
|
if err != nil {
|
|
return governance.StartTestPublicationsResult{}, errors.New("encode processing upload job")
|
|
}
|
|
if _, err := tx.Exec(ctx, `INSERT INTO processing_confirmations(id,publication_id,operator_ref,reason,confirmed_at,created_at) VALUES($1,$2,$3,$4,$5,$5)`, confirmationID, publicationID, operator, reason, request.Now); err != nil {
|
|
return governance.StartTestPublicationsResult{}, fmt.Errorf("record processing confirmation: %w", err)
|
|
}
|
|
if _, err := tx.Exec(ctx, `INSERT INTO jobs(id,type,payload,state,priority,available_at,max_attempts,idempotency_key,created_at,updated_at)
|
|
VALUES($1,'test.upload',$2::jsonb,'pending',10,$3,5,$4,$3,$3)`, jobID, payload, request.Now, "test-start:"+publicationID); err != nil {
|
|
return governance.StartTestPublicationsResult{}, fmt.Errorf("enqueue confirmed processing: %w", err)
|
|
}
|
|
result.Started++
|
|
}
|
|
if ownsTransaction {
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return governance.StartTestPublicationsResult{}, fmt.Errorf("commit processing confirmation: %w", err)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Store) StartTestPublicationsWithOperation(ctx context.Context, request governance.StartTestPublicationsRequest, operationID string) (governance.StartTestPublicationsResult, bool, error) {
|
|
fingerprint, err := operationFingerprint(struct {
|
|
PublicationIDs []string `json:"publication_ids"`
|
|
OperatorRef string `json:"operator_ref"`
|
|
Reason string `json:"reason"`
|
|
}{request.PublicationIDs, request.OperatorRef, request.Reason})
|
|
if err != nil {
|
|
return governance.StartTestPublicationsResult{}, false, err
|
|
}
|
|
raw, replayed, err := s.executeOperation(ctx, "test.publications.start", operationID, fingerprint, request.Now, func(tx pgx.Tx) (json.RawMessage, error) {
|
|
result, err := NewStore(tx).StartTestPublications(ctx, request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(result)
|
|
})
|
|
if err != nil {
|
|
return governance.StartTestPublicationsResult{}, false, err
|
|
}
|
|
var result governance.StartTestPublicationsResult
|
|
if err := json.Unmarshal(raw, &result); err != nil {
|
|
return governance.StartTestPublicationsResult{}, false, fmt.Errorf("decode stored processing result: %w", err)
|
|
}
|
|
return result, replayed, nil
|
|
}
|