diff --git a/muse-cloud/muse-module-knowledge/.agent b/muse-cloud/muse-module-knowledge/.agent index eef59925..cd26d63b 100644 --- a/muse-cloud/muse-module-knowledge/.agent +++ b/muse-cloud/muse-module-knowledge/.agent @@ -5,5 +5,5 @@ - **目标(owner 职责)**:Local KB / User KB / 全局知识处理 / Knowledge Draft / Knowledge Source Binding / 投影与索引状态。 - **边界(不可违反)**:**进 Local KB 唯一入口=用户显式确认草稿或手动修正**;全书解析章节确认只产/推 Draft,不写正式知识;来源优先级 Local KB > 用户绑定 > 市场/全局;依赖 RAGFlow GraphRAG(不可用时 Unavailable 优雅降级);守 BC 边界不碰他域 `.dal`([bc-boundaries](../../.agents/rules/bc-boundaries.md))。 - **out-of-scope**:正文写入(归 content)。 -- **现状**:只读评估 72%;后端最完整模块之一(59 端点对齐契约),RAGFlow 真 HTTP + SSRF 防护;installed-KB 删除记录回显 bug 已修(2026-06-19:`MuseKnowledgeBindingMapperTest` + `MuseInstalledKnowledgeBaseServiceTest` 10/10,删除时写 `deleted=true` 且列表排除 `binding_status=deleted`);impact preview 已按 `muse_knowledge_source_binding_projection.owner_user_id` 输出 installed/source-binding owner 审计计数(2026-06-19:`MuseKnowledgeBaseServiceTest` + `MuseKnowledgeSourceBindingProjectionMapperTest` 30/30);source binding projection 回填闭环已修(2026-06-19:绑定确认为权威回填点写读模型行、unbind 按 bindingId 作用域撤销不误伤同源跨作品投影、唯一键补 work_id 修跨作品复用误判冲突〔V24〕;`MuseKnowledgeBindingServiceTest` + `MuseKnowledgeSourceBindingProjectionMapperTest` 11/11、整套件 214/214 绿);**读回端到端坐实 + unbind 真软删修复(2026-06-19)**:嵌入式 DB(H2)往返证 bind 写投影→`selectActiveByWorkId` 读回该来源、unbind 后读回消失、同源他作品投影存活;并修 unbind 软删真 bug——`deleted` 是 `@TableLogic`,实体 `setDeleted(true)` 被 MP 普通 update 剥离成空操作,改 `setSql("deleted = true")` 才真软删(`MuseKnowledgeSourceBindingProjectionRoundTripTest` 2/2、整套件 216/216 绿;新增 knowledge 模块首套嵌入式 DB 测试基建)。**GET bindings 读端点已补(2026-06-19)**:`AppMuseKnowledgeBindingController` 加 `GET /muse/works/{workId}/knowledge-bindings`→`listKnowledgeBindings`(requireWorkOwner 防 IDOR + selectActiveByWorkId 读回投影,id→String;happy-path + 越权 fail-closed 单测,整套件 218/218 绿);**bindings 读回后端腿(写→读→端点)已闭环,仅余 FE hook**。**FE 读回面板已加(2026-06-19)**:studio `useKnowledgeBindings` hook + `KnowledgeBindingsPanel`(镜像 DraftPanel)接入 `KnowledgePage`,契约+组件测试经 vitest 验证(全 studio 50/50、tsc 干净);live playwright e2e 需全栈 app(env 受限)未跑。bindings 写→读→端点→FE 读回展示链(除 live e2e)全通。 +- **现状**:只读评估 72%;后端最完整模块之一(59 端点对齐契约),RAGFlow 真 HTTP + SSRF 防护;installed-KB 删除记录回显 bug 已修(2026-06-19:`MuseKnowledgeBindingMapperTest` + `MuseInstalledKnowledgeBaseServiceTest` 10/10,删除时写 `deleted=true` 且列表排除 `binding_status=deleted`);impact preview 已按 `muse_knowledge_source_binding_projection.owner_user_id` 输出 installed/source-binding owner 审计计数(2026-06-19:`MuseKnowledgeBaseServiceTest` + `MuseKnowledgeSourceBindingProjectionMapperTest` 30/30);source binding projection 回填闭环已修(2026-06-19:绑定确认为权威回填点写读模型行、unbind 按 bindingId 作用域撤销不误伤同源跨作品投影、唯一键补 work_id 修跨作品复用误判冲突〔V24〕;`MuseKnowledgeBindingServiceTest` + `MuseKnowledgeSourceBindingProjectionMapperTest` 11/11、整套件 214/214 绿);**读回端到端坐实 + unbind 真软删修复(2026-06-19)**:嵌入式 DB(H2)往返证 bind 写投影→`selectActiveByWorkId` 读回该来源、unbind 后读回消失、同源他作品投影存活;并修 unbind 软删真 bug——`deleted` 是 `@TableLogic`,实体 `setDeleted(true)` 被 MP 普通 update 剥离成空操作,改 `setSql("deleted = true")` 才真软删(`MuseKnowledgeSourceBindingProjectionRoundTripTest` 2/2、整套件 216/216 绿;新增 knowledge 模块首套嵌入式 DB 测试基建)。**GET bindings 读端点已补(2026-06-19)**:`AppMuseKnowledgeBindingController` 加 `GET /muse/works/{workId}/knowledge-bindings`→`listKnowledgeBindings`(requireWorkOwner 防 IDOR + selectActiveByWorkId 读回投影,id→String;happy-path + 越权 fail-closed 单测,整套件 218/218 绿);**bindings 读回后端腿(写→读→端点)已闭环,仅余 FE hook**。**FE 读回面板已加(2026-06-19)**:studio `useKnowledgeBindings` hook + `KnowledgeBindingsPanel`(镜像 DraftPanel)接入 `KnowledgePage`,契约+组件测试经 vitest 验证(全 studio 50/50、tsc 干净);live playwright e2e 需全栈 app(env 受限)未跑。bindings 写→读→端点→FE 读回展示链(除 live e2e)全通。**live e2e 已写 ready 未跑(2026-06-19)**:`muse-studio/e2e/knowledge-bindings.spec.ts`(镜像 graph spec,`playwright --list` 编译有效);活体受阻=远端 PG 宿主 100.64.0.8 离线(不在 tailnet)、无本地 PG 兜底,PG 宿主恢复后按 [.agents/knowledge §六] 起栈可跑。 - **关键风险 / TODO**:前端仅约 1/3 面(草稿确认/绑定/发布/图谱缺 hook);Task4 剩余 owner 缺口已收窄为 **document owner count / export_task_owner** 仍不能伪造,需后续 owner 事实或外部服务接入;recheck/worker 默认未生效。 diff --git a/muse-studio/e2e/knowledge-bindings.spec.ts b/muse-studio/e2e/knowledge-bindings.spec.ts new file mode 100644 index 00000000..7913b0b8 --- /dev/null +++ b/muse-studio/e2e/knowledge-bindings.spec.ts @@ -0,0 +1,52 @@ +import { expect, test, type Page } from '@playwright/test'; + +/** + * 作品已绑定知识来源读回视图 e2e(来源绑定读模型,真实后端,MSW off)。 + * + * 前置(同 knowledge-graph.spec.ts 的活体全栈口径): + * - vite 以 VITE_API_MOCK=false 运行、真实 muse-server 在 48080; + * - muse_slice_live 作品 1 下已种子一条<生效>的来源绑定读模型投影 + * (muse_knowledge_source_binding_projection:work_id=1、deleted=false、status=active、 + * source_type=market_kb、kb_id=4001;可由真实 bind(precheck→bind)产生,或等价 seed)。 + * 目的:rendered studio 的「作品已绑定的知识来源」面板经 GET /works/1/knowledge-bindings + * 读真实投影,把当前生效的来源绑定在 UI 渲染——证"绑定确认 → 读模型回填 → 读回展示"链路活体可见(打真后端,非 mock)。 + * + * ⚠️ 状态(2026-06-19):本 spec 已编写但<尚未>活体运行——远端 PG 宿主(100.64.0.8:5433)当时离线、 + * 全栈 app 无法启动(见 docs/mvp/进度总账.md bindings 条目"诚实边界")。PG 宿主恢复后按上述前置起栈即可运行。 + * 后端读端点 + FE hook/面板已在 vitest/单测/tsc/build 层验证(knowledge-server 218、studio 50 + build 绿)。 + */ +const TOKEN = 'test1'; + +async function seedToken(page: Page): Promise { + await page.addInitScript((t) => { + window.localStorage.setItem('accessToken', t); + window.localStorage.setItem('tenantId', '1'); + }, TOKEN); +} + +test('已绑定知识来源面板渲染真实投影读回(真实后端)', async ({ page }) => { + await seedToken(page); + + // 捕获真实来源绑定读回响应(打真后端,非 mock)。 + const [resp] = await Promise.all([ + page.waitForResponse( + (r) => /\/works\/1\/knowledge-bindings(\?|$)/.test(r.url()) && r.request().method() === 'GET', + { timeout: 15_000 } + ), + page.goto('/knowledge/1'), + ]); + + // Yudao 口径:成功为 HTTP 200 + CommonResult{code:0}。 + expect(resp.status()).toBe(200); + const body = await resp.json(); + expect(body.code).toBe(0); + // 真实后端返回该作品当前生效(未删)的来源绑定。 + const bindings = body.data?.bindings ?? []; + expect(bindings.length).toBeGreaterThan(0); + expect(bindings.some((b: { status: string }) => b.status === 'active')).toBe(true); + + // UI 渲染:面板标题 + 来源类型徽标(market_kb→市场知识库)+ 生效状态徽标。 + await expect(page.getByText('作品已绑定的知识来源').first()).toBeVisible({ timeout: 15_000 }); + await expect(page.getByText('市场知识库').first()).toBeVisible({ timeout: 15_000 }); + await expect(page.getByText('生效中').first()).toBeVisible({ timeout: 15_000 }); +});