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 干净
133 lines
6.0 KiB
Go
133 lines
6.0 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"knowledge-governance/internal/governance"
|
|
)
|
|
|
|
const listReleasePlanSQL = `
|
|
WITH ` + releaseGateCTEs + `,
|
|
source_truth AS (
|
|
SELECT source.id AS source_item_id, source.title, source.revision, source.source_change,
|
|
COALESCE(item_rule.visibility, directory_rule.visibility, source.visibility) AS desired_policy
|
|
FROM source_items source
|
|
LEFT JOIN source_item_scope_overrides item_rule ON item_rule.source_item_id=source.id
|
|
LEFT JOIN LATERAL (
|
|
SELECT rule.visibility
|
|
FROM directory_scope_rules rule
|
|
WHERE rule.source_connection_id=source.source_connection_id
|
|
AND (rule.directory_path='' OR source.normalized_path=rule.directory_path OR source.normalized_path LIKE rule.directory_path||'/%')
|
|
ORDER BY LENGTH(rule.directory_path) DESC
|
|
LIMIT 1
|
|
) directory_rule ON TRUE
|
|
),
|
|
desired AS (
|
|
SELECT truth.source_item_id, truth.title, scope.target_scope
|
|
FROM source_truth truth
|
|
CROSS JOIN LATERAL unnest(
|
|
CASE
|
|
WHEN truth.source_change='deleted' OR truth.desired_policy='blocked' THEN ARRAY[]::TEXT[]
|
|
WHEN truth.desired_policy='both' THEN ARRAY['internal','external']::TEXT[]
|
|
ELSE ARRAY[truth.desired_policy]::TEXT[]
|
|
END
|
|
) scope(target_scope)
|
|
),
|
|
test_ready AS (
|
|
SELECT DISTINCT ON (version.source_item_id, publication.target_scope)
|
|
version.source_item_id, publication.target_scope, publication.id AS publication_id,
|
|
ready.review_id AS release_revision
|
|
FROM release_ready ready
|
|
JOIN test_publications publication ON publication.id=ready.publication_id
|
|
JOIN source_item_versions version ON version.id=publication.source_item_version_id
|
|
ORDER BY version.source_item_id, publication.target_scope, publication.updated_at DESC, publication.id DESC
|
|
),
|
|
latest_production AS (
|
|
SELECT DISTINCT ON (version.source_item_id, publication.target_scope)
|
|
version.source_item_id, publication.target_scope,
|
|
publication.id AS publication_id, item.production_revision
|
|
FROM release_item_executions item
|
|
JOIN release_executions execution ON execution.id=item.release_execution_id
|
|
JOIN test_publications publication ON publication.id=item.publication_id
|
|
JOIN source_item_versions version ON version.id=publication.source_item_version_id
|
|
WHERE execution.state IN ('completed','rolled_back')
|
|
AND item.state IN ('verified','rolled_back')
|
|
ORDER BY version.source_item_id, publication.target_scope, execution.completed_at DESC NULLS LAST, execution.id DESC
|
|
),
|
|
production AS (
|
|
SELECT * FROM latest_production WHERE production_revision<>'' AND production_revision<>'absent'
|
|
),
|
|
all_scopes AS (
|
|
SELECT source_item_id,target_scope FROM desired
|
|
UNION
|
|
SELECT source_item_id,target_scope FROM production
|
|
),
|
|
plan AS (
|
|
SELECT COALESCE(test.publication_id, production.publication_id, '') AS publication_id,
|
|
truth.title,
|
|
truth.source_change,
|
|
scope.target_scope,
|
|
CASE
|
|
WHEN truth.source_change='unavailable' THEN 'blocked'
|
|
WHEN desired.source_item_id IS NULL AND production.source_item_id IS NOT NULL THEN 'delete'
|
|
WHEN desired.source_item_id IS NOT NULL AND test.publication_id IS NULL THEN 'blocked'
|
|
WHEN production.source_item_id IS NULL THEN 'add'
|
|
WHEN production.production_revision<>test.release_revision THEN 'update'
|
|
ELSE 'unchanged'
|
|
END AS action,
|
|
CASE WHEN desired.source_item_id IS NULL THEN 'not_required' WHEN test.publication_id IS NULL THEN 'not_ready' ELSE 'ready' END AS test_state,
|
|
CASE WHEN production.source_item_id IS NULL THEN 'absent' ELSE 'present' END AS production_state
|
|
FROM all_scopes scope
|
|
JOIN source_truth truth ON truth.source_item_id=scope.source_item_id
|
|
LEFT JOIN desired ON desired.source_item_id=scope.source_item_id AND desired.target_scope=scope.target_scope
|
|
LEFT JOIN test_ready test ON test.source_item_id=scope.source_item_id AND test.target_scope=scope.target_scope
|
|
LEFT JOIN production ON production.source_item_id=scope.source_item_id AND production.target_scope=scope.target_scope
|
|
)
|
|
SELECT publication_id,title,target_scope,action,test_state,production_state,
|
|
action IN ('add', 'update', 'delete') AS can_release,
|
|
CASE WHEN action='blocked' AND source_change='unavailable' THEN 'source_unavailable'
|
|
WHEN action='blocked' THEN 'test_not_ready'
|
|
ELSE '' END AS blocker
|
|
FROM plan
|
|
ORDER BY CASE action WHEN 'delete' THEN 0 WHEN 'blocked' THEN 1 WHEN 'update' THEN 2 WHEN 'add' THEN 3 ELSE 4 END, title, target_scope
|
|
`
|
|
|
|
func (s *Store) ListReleasePlan(ctx context.Context) (governance.ReleasePlan, error) {
|
|
rows, err := s.db.Query(ctx, listReleasePlanSQL)
|
|
if err != nil {
|
|
return governance.ReleasePlan{}, fmt.Errorf("list release plan: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
plan := governance.ReleasePlan{Items: []governance.ReleasePlanItem{}}
|
|
for rows.Next() {
|
|
var item governance.ReleasePlanItem
|
|
if err := rows.Scan(&item.PublicationID, &item.Title, &item.TargetScope, &item.Action, &item.TestState, &item.ProductionState, &item.CanRelease, &item.Blocker); err != nil {
|
|
return governance.ReleasePlan{}, fmt.Errorf("scan release plan: %w", err)
|
|
}
|
|
if (item.TargetScope != "internal" && item.TargetScope != "external") || (item.Action != "add" && item.Action != "update" && item.Action != "delete" && item.Action != "blocked" && item.Action != "unchanged") {
|
|
return governance.ReleasePlan{}, errors.New("invalid release plan row")
|
|
}
|
|
switch item.Action {
|
|
case "add":
|
|
plan.Summary.Add++
|
|
case "update":
|
|
plan.Summary.Update++
|
|
case "delete":
|
|
plan.Summary.Delete++
|
|
case "unchanged":
|
|
plan.Summary.Unchanged++
|
|
continue
|
|
}
|
|
if !item.CanRelease {
|
|
plan.Summary.Blocked++
|
|
}
|
|
plan.Items = append(plan.Items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return governance.ReleasePlan{}, fmt.Errorf("iterate release plan: %w", err)
|
|
}
|
|
return plan, nil
|
|
}
|