Files
knowledge-governance/internal/releaseadapter/v071/contract_inspector.go
T
姚章浩 6440e3f679 feat(release): 发布目标知识库可选(源库 → 目标库映射)
- 迁移 000044:production_kb_bindings + release_item_executions.target_knowledge_base_id
- 系统设置新增「生产知识库绑定(对内/对外)」:从生产 WeKnora 实际列表选库,
  保存时真校验(不存在拒收;生产端不可达只提示并如实回报),仅管理员可改
- 执行计划按绑定解析目标库并冻结进 release_item_executions;空目标直接拒收;
  已发布过的条目保留原目标(重试/回滚回到同一处)
- DocumentBundle.RemapKnowledgeBase:knowledges/chunks/knowledge_tags/wiki_pages/
  wiki_folders 的 knowledge_base_id 与 ES 文档字段改写为目标库,重算表/文档摘要并重新封装
- 契约比对改为「声明的映射对」(KnowledgeBaseMapping),其余字段继续 fail-closed
- 契约检查器 / 目标身份 / 验证器全部走映射后的目标库;对象通道无需改写
- Compose 拆分:数据面接线 compose.release-sim.yaml + 两道门禁 compose.release-gates.yaml
- 测试:RemapKnowledgeBase 全通道/无操作/fail-closed、映射契约、绑定接口、RBAC、真实 PG 集成
2026-10-08 15:41:13 +08:00

109 lines
4.1 KiB
Go

package v071
import (
"context"
"errors"
"strings"
"github.com/jackc/pgx/v5"
"knowledge-governance/internal/releasebundle"
releaseworkflow "knowledge-governance/internal/workflows/release"
)
type targetContractDB interface {
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
type elasticsearchMappingInspector interface {
MappingSHA256(ctx context.Context) (string, error)
}
type TargetContractInspectorConfig struct {
DB targetContractDB
Mapping elasticsearchMappingInspector
KnowledgeBaseID string
TenantID uint64
IndexName string
}
type TargetContractInspector struct {
config TargetContractInspectorConfig
}
const targetContractSQL = `
SELECT migration.version,
migration.dirty,
knowledge_base.tenant_id,
model.name,
COALESCE(NULLIF(model.parameters->'embedding_parameters'->>'dimension', '')::INTEGER, 0),
COALESCE(vector_store.engine_type, ''),
COALESCE(vector_store.index_config->>'index_name', '')
FROM knowledge_bases AS knowledge_base
JOIN models AS model
ON model.id = knowledge_base.embedding_model_id
AND model.deleted_at IS NULL
JOIN schema_migrations AS migration ON TRUE
LEFT JOIN vector_stores AS vector_store
ON vector_store.id = knowledge_base.vector_store_id
AND vector_store.deleted_at IS NULL
WHERE knowledge_base.id = $1
AND knowledge_base.deleted_at IS NULL
`
func NewTargetContractInspector(config TargetContractInspectorConfig) (*TargetContractInspector, error) {
config.KnowledgeBaseID = strings.TrimSpace(config.KnowledgeBaseID)
config.IndexName = strings.TrimSpace(config.IndexName)
if config.DB == nil || config.Mapping == nil || config.TenantID == 0 || !validSearchIdentifier(config.KnowledgeBaseID) || !validSearchIdentifier(config.IndexName) {
return nil, errors.New("invalid target contract inspector configuration")
}
return &TargetContractInspector{config: config}, nil
}
func (inspector *TargetContractInspector) Contract(ctx context.Context, item releaseworkflow.PlanItem) (releasebundle.TargetContract, error) {
if item.TestKnowledgeBaseID != inspector.config.KnowledgeBaseID {
return releasebundle.TargetContract{}, errors.New("release plan knowledge base does not match target contract")
}
// The route is keyed by the TESTED base (where the content was verified);
// the contract is read for the base this release actually writes into —
// the 源库 → 目标库 mapping when the item carries one, else the same base.
targetKnowledgeBaseID := item.TargetBase()
if !validSearchIdentifier(targetKnowledgeBaseID) {
return releasebundle.TargetContract{}, errors.New("invalid release target knowledge base")
}
var (
migrationVersion int
dirty bool
tenantID uint64
embeddingModel string
dimension int
engineType string
indexName string
)
if err := inspector.config.DB.QueryRow(ctx, targetContractSQL, targetKnowledgeBaseID).Scan(
&migrationVersion, &dirty, &tenantID, &embeddingModel, &dimension, &engineType, &indexName,
); err != nil {
return releasebundle.TargetContract{}, errors.New("read target WeKnora contract")
}
if migrationVersion != releasebundle.WeKnoraMigrationV071 || dirty || tenantID != inspector.config.TenantID ||
!validSearchIdentifier(embeddingModel) || dimension < 1 ||
(engineType != "" && engineType != "elasticsearch") ||
(indexName != "" && indexName != inspector.config.IndexName) {
return releasebundle.TargetContract{}, errors.New("target WeKnora contract mismatch")
}
mappingSHA256, err := inspector.config.Mapping.MappingSHA256(ctx)
if err != nil || !validLowerSHA256(mappingSHA256) {
return releasebundle.TargetContract{}, errors.New("read target Elasticsearch mapping contract")
}
return releasebundle.TargetContract{
WeKnoraVersion: releasebundle.WeKnoraVersionV071,
MigrationVersion: releasebundle.WeKnoraMigrationV071,
TenantID: tenantID,
KnowledgeBaseID: targetKnowledgeBaseID,
RetrieverEngine: "elasticsearch_v8",
EmbeddingModel: embeddingModel,
EmbeddingDimension: dimension,
ElasticsearchMappingSHA256: mappingSHA256,
}, nil
}