# P8 目标知识库可选(源库 → 目标库映射)开发与验收 ## 缺口 发布链路把「测试库」与「生产库」绑成同一个 KB ID,全链路 fail-closed: - `contract_inspector` 用 `route.knowledge_base_id` 读目标契约,且要求它等于发布项的测试库 ID; - `manifest.Validate` / `sidecar_publisher` / `verifier` 断言 `KnowledgeBaseID == item.TestKnowledgeBaseID`; - `postgres_import.prepareSnapshot` 逐行断言 `knowledge_base_id == identity.KnowledgeBaseID`; - `search_import.searchHitMatches` 断言文档 `knowledge_base_id == identity.KnowledgeBaseID`。 因此只能发布到与测试库同 ID 的「镜像库」,无法选择目标端已有真实库(如「对内知识库-wiki」)。 ## 目标 1. 系统设置新增「生产知识库绑定(对内 / 对外)」,从**目标(生产)WeKnora 实际列表**里选目标库,保存时真校验。 2. 发布执行时按绑定把 KB 身份从源库**改写**到目标库,三通道(PostgreSQL / Elasticsearch / 对象)与官方 API 验收一致收敛。 3. 保持 fail-closed 语义:映射关系是**显式声明并被冻结**的,未绑定时退回同 ID 语义(目标库不存在则在契约检查阶段即失败)。 ## 契约变更(唯一一处放宽) 原来比较 `manifest.Contract.KnowledgeBaseID == target.KnowledgeBaseID`; 现在比较**声明的映射对**(`releasebundle.KnowledgeBaseMapping`): ``` manifest.Contract.KnowledgeBaseID == mapping.SourceKnowledgeBaseID (源:测试库) target.KnowledgeBaseID == mapping.TargetKnowledgeBaseID (目标:生产库) 其余字段(版本 / migration / tenant / 检索引擎 / 模型 / 维度 / ES mapping SHA)继续必须相等 ``` 映射来源:`release_item_executions.target_knowledge_base_id`(执行开始时按 `production_kb_bindings` 解析并落库冻结),审计可见、重试复用。已在生产发布过的条目保留原目标(重试/回滚回到同一处),未发布过的按当前绑定。 ## 落点 | 层 | 改动 | |---|---| | 迁移 000044 | `production_kb_bindings`(单行 active,对内/对外目标库)+ `release_item_executions.target_knowledge_base_id` | | 系统设置 | 新增「生产知识库绑定(对内 / 对外)」,从**生产 WeKnora 实际列表**选库;保存时真校验(不存在→400 拒收;生产端不可达→只提示并如实回报 `production_connected=false`) | | API | `GET/PUT /api/v1/production-kb-bindings`、`GET /api/v1/production-kb-bindings/options` | | 执行计划 | `StartExecution` 解析映射并冻结进 plan 与 `release_item_executions`;空目标直接拒收 | | 快照改写 | `DocumentBundle.RemapKnowledgeBase`:5 张带 KB 列的表 + ES 文档 `knowledge_base_id` 改写为目标库,重算表/文档摘要并重新封装(`Seal`);其余字段按原字节透传(嵌入向量不动) | | 目标身份 | `targetIdentities` / 契约检查器 / 验证器 / `PlanItem.TargetBase()` 全部走映射后的目标库 | | 对象通道 | 不改:对象 key = `weknora///`,无 KB 段(实测穷举) | | Compose | `compose.release-sim.yaml`(数据面接线,无门禁)+ `compose.release-gates.yaml`(两道门禁,须人工授权叠加) | ## 改写面(实测穷举) 源库行里 KB ID 只出现在 `knowledge_base_id` 这一个字段(每行 1 处): | 表 / 位置 | 是否有 KB 列 | 处理 | |---|---|---| | `knowledges` / `chunks` / `knowledge_tags` / `wiki_pages` / `wiki_folders` | 有(各 1 处) | 改写为目标库 ID,并重算表摘要 | | `resources` / `resource_bindings` / `knowledge_tag_relations` | 无 | 不改(按 knowledge/handle 关联) | | Elasticsearch `_source.knowledge_base_id` | 有 | 改写为目标库 ID,并重算文档摘要 | | 对象存储 locator `storage:///oss:///weknora///` | 无 KB 段 | 不改(仅按目标 backend/bucket 重写) | ## 验收点 1. `go test ./...`、`go vet`、前端 `tsc` + `vitest` 全绿;迁移 44 up/down 往返可用。 2. 单元/集成:映射生效后 `knowledges/chunks/wiki_pages` 的 KB 列、ES 文档字段均为目标库;摘要随改写重算;未绑定退回同 ID。 3. 界面:系统设置可选目标库(列真实库名),保存时对目标 WeKnora 真校验(无效 ID 拒绝);发布管理显示本次目标库。 4. 真实跨环境:1 份文档发布到目标库 → 三通道读回一致 → 官方 API 检索命中 → 撤权后零命中 → 回滚恢复。 5. **wiki 归属门禁(2026-10-08 起)**:目标库存在引用包外知识的页时,发布自动降级为"只替换包内页集", 外来页 / agent 手写页 / 目标库自己的 `index`-`log` 一行不动;包内会写入的页与外来页同 slug 时拒绝发布 且事务不留任何改动。真库集成用例:`TestWikiReplaceOnSharedKnowledgeBaseKeepsForeignPages`、 `TestWikiReplaceRefusesWhenPackageSlugBelongsToForeignPage`、`TestWikiReplaceOnFaithfulMirrorReplacesWholeLibrary` (`TEST_WEKNORA_V071_TARGET_DATABASE_URL` 指向 v0.7.1 库时运行,只使用带随机后缀的临时知识库并自清理)。 6. 跨库对发布的前置条件:**测试库与生产库承载同一批文档**(镜像)时 wiki 才能保持"测试=生产"; 只做单文档发布时,共享页正文不保证两边一致(见 P6「wiki 通道的归属门禁」)。