test(market): U-verify D0-fork 端到端真验收(真 PG+真 RAGFlow、第四单元)

D0-fork 最后单元:真 PG17+真 RAGFlow 端到端验收、2 IT 真绿(独立复跑确认 57s)。P1rMarketKbForkMaterializationIT(muse-server、跨 knowledge+market 两 BC 须同 Spring 上下文):①fork→安装→物化 installed_ref→检索命中(no_dataset 消除、副本与活 dataset 物理分离);②发布者私有不泄露真证(发布者上架后往活 dataset 真传私有 magic、安装者检索副本拿 magic 当 query 仍只返公开 chunk 不含 magic→d0ForkIsolationProven、U0 spike 反向基线);③幂等+多安装者共享1副本;④AFTER_COMMIT 时序(committedAssetForked/rolledBackAssetForked false:提交才 fork 回滚不 fork);⑤_test 库铁律(muse_p1r_fork_mat_test 绝未指 muse_slice_live)。

market-install-kb-retrieval.spec.ts(studio e2e):检索命中+私有不泄露无独立 UI 入口由 IT 证;安装物化 e2e 标 test.fixme 待 global-setup 补 fork 就绪资产(不伪绿、骨架就位 tsc 过)。召回回滚(D9)如实标开放项:recallAsset 仍 writeSourceStatusBlocked、knowledge 无召回消费者→不自动停用 installed_ref;需新增 market→knowledge 召回传播才闭环(不伪造、不引用不存在的 muse_source_propagation_target)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-26 11:58:56 -07:00
parent d4309ab0e9
commit 8b3258e22f
2 changed files with 1392 additions and 0 deletions

View File

@ -0,0 +1,144 @@
import { expect, test, type Page } from '@playwright/test';
import { Client } from 'pg';
/**
* D0-fork-04 U-verify installed_ref studio e2eMSW off
*
* spec
* D0-fork
* UI installed_ref KB + binding
* dataset market_kb installed_ref
* spec
* + UI retrieveForWork AI DefaultKnowledgeRetrievalFacade
* muse HTTP/UI RAGFlow
* studio e2e PG + RAGFlow IT
* muse-cloud/muse-server/.../P1rMarketKbForkMaterializationIT绿publicChunksHit=3privateMagicLeaked=false
* d0ForkIsolationProven=true e2e UI AI SSE flaky
*
*
* spec global-setup fork market_kb
* - listed knowledge_base + fork forkStatus=readymuse_market_asset.tags
* publicForkDatasetId/forkStatus=ready muse-server + RAGFlow fork
* global-setup#16 seed agent #6 seed
* - handoff tokenprecheckbind
* spec test.fixme 绿 fixme
* P1rMarketKbForkMaterializationIT installerALocalKbId/installerBLocalKbId
* FE / market-install.spec.ts + knowledge-bindings.spec.ts
*
* e2e vite VITE_API_MOCK=false muse-server 48080muse_slice_live
* PG MUSE_POSTGRES_* ~/.config/muse-repo/infra.env
* `set -a; . ~/.config/muse-repo/infra.env; set +a`
*/
const TOKEN = 'test1';
const API = 'http://localhost:48080/app-api/muse';
const AUTH = { Authorization: 'Bearer test1', 'tenant-id': '1', 'X-API-Version': '1' };
/** global-setup 待补fork 已就绪的可安装 market_kb 资产 marker 名(与 global-setup 约定,前提补齐时一并固化)。 */
const FORK_READY_KB_ASSET_NAME = '活体市场知识库·fork安装检索e2e';
function pgClient(): Client {
return new Client({
host: process.env.MUSE_POSTGRES_HOST,
port: Number(process.env.MUSE_POSTGRES_PORT ?? '5433'),
database: process.env.MUSE_POSTGRES_DATABASE ?? 'muse_slice_live',
user: process.env.MUSE_POSTGRES_USERNAME ?? 'root',
password: process.env.MUSE_POSTGRES_PASSWORD,
ssl: false,
});
}
async function seedToken(page: Page): Promise<void> {
await page.addInitScript((t) => {
window.localStorage.setItem('accessToken', t);
window.localStorage.setItem('tenantId', '1');
}, TOKEN);
}
/** 读 fork-ready market_kb 资产 id前提补齐后由 global-setup seed未 seed 则该断言会指引去补 global-setup。 */
async function readForkReadyAssetId(): Promise<number> {
const c = pgClient();
await c.connect();
try {
const r = await c.query(
"SELECT id FROM muse_market_asset WHERE tenant_id=1 AND name=$1 AND asset_type='knowledge_base' AND deleted=false",
[FORK_READY_KB_ASSET_NAME]
);
expect(
r.rowCount,
`需 global-setup 预置 fork-ready market_kb 资产「${FORK_READY_KB_ASSET_NAME}」(见本 spec 头部「运行前提」)`
).toBeGreaterThan(0);
return Number(r.rows[0].id);
} finally {
await c.end();
}
}
/** 读资产 tags 里的 forkStatusD0-fork仅 ready 才放行物化绑定)。 */
async function readForkStatus(assetId: number): Promise<string | null> {
const c = pgClient();
await c.connect();
try {
const r = await c.query(
"SELECT tags ->> 'forkStatus' AS fork_status FROM muse_market_asset WHERE tenant_id=1 AND id=$1",
[assetId]
);
return r.rowCount ? (r.rows[0].fork_status as string | null) : null;
} finally {
await c.end();
}
}
/** 读某作品下 installed_refsource_market_asset_id=assetId物化出的本地 KB 行数 + 其 binding 指向的 dataset。 */
async function readInstalledRefDataset(assetId: number, ownerUserId: number): Promise<string | null> {
const c = pgClient();
await c.connect();
try {
const r = await c.query(
`SELECT b.ragflow_dataset_id AS dataset_id
FROM muse_knowledge_base kb
JOIN muse_knowledge_ragflow_binding b
ON b.tenant_id = kb.tenant_id AND b.kb_id = kb.id
AND b.status = 'active' AND b.document_id IS NULL AND b.deleted = false
WHERE kb.tenant_id = 1 AND kb.kb_type = 'installed_ref'
AND kb.source_market_asset_id = $1 AND kb.owner_user_id = $2 AND kb.deleted = false
ORDER BY kb.id DESC LIMIT 1`,
[assetId, ownerUserId]
);
return r.rowCount ? (r.rows[0].dataset_id as string) : null;
} finally {
await c.end();
}
}
// 运行前提fork-ready 资产 + handoff token seed未就绪前标 fixme避免伪绿前提补齐后去掉 fixme 真跑。
test.fixme(
'D0-fork:安装 fork-ready 市场知识库 → 物化 installed_ref(binding 指向公开副本)→ 来源面板可见(真实后端)',
async ({ page, request }) => {
const assetId = await readForkReadyAssetId();
// 1) 前提核验:资产的公开副本 fork 必须 ready(D6 fail-closed:非 ready 不放行物化绑定)。
expect(await readForkStatus(assetId), 'D0-fork 安装前置:公开副本 forkStatus 必须 ready').toBe('ready');
// 2) 经真实安装 + 绑定链(purchase → handoff → precheck → bind)兑现物化(由 UI 或 page.request 真打,前提补齐时按
// market-handoff-precheck.spec.ts 同范式补全 token 兑现步骤)。
const acquire = await request.post(`${API}/marketplace/assets/${assetId}/purchase`, {
headers: AUTH,
data: { commandId: `e2e-fork-acq-${Date.now()}` },
});
expect(acquire.status(), 'purchase HTTP').toBe(200);
expect((await acquire.json()).code, 'purchase code').toBe(0);
// 3) 物化结果(DB 真证):安装者本地建出 installed_ref KB,其 dataset binding 指向公开副本(非发布者活库)。
const datasetId = await readInstalledRefDataset(assetId, 1);
expect(datasetId, '安装绑定后应物化出 installed_ref KB + 指向公开副本的 dataset binding').toBeTruthy();
// 4) 用户可见证据:进作品知识来源面板,看到该 market_kb 来源(installed_ref 物化成功的可观测结果)。
await seedToken(page);
await page.goto('/knowledge/1');
await expect(page.getByText('作品已绑定的知识来源').first()).toBeVisible({ timeout: 15_000 });
await expect(page.getByText('市场知识库').first()).toBeVisible({ timeout: 15_000 });
// 注:检索命中 + 发布者私有不泄露(②)不在此断言——无独立 UI 检索面,且物理隔离须真 RAGFlow,已由
// P1rMarketKbForkMaterializationIT 真证(privateMagicLeaked=false)。
}
);