test(admin-e2e): governance config 全页巡航转真连——拆 6 子系统真连+灌系统级种子(admin e2e 0 skip 全收口)

承用户选 B(补 admin config 断层)。前提纠正:原 skip 注释"切片库根本没有 muse_function_chain/muse_protection_node 表"误判——psql 实证真表名带 meta_ 前缀=muse_meta_function_chain/muse_meta_protection_node(V3 schema、都在 muse_slice_live、行数 0),真断层是缺系统级公共治理对象种子、非缺表/缺功能。

每锚点真表+真后端过滤契约(curl 实证):story.draft 元结构=muse_meta_schema(列表无 status 过滤);故事链路=muse_meta_function_chain(listQuery 只放行 status=active、displayName 取 chain_snapshot.displayName);事实校验保护节点=muse_meta_protection_node(status=active+policy_snapshot->>chainKey 绑定链路、irreplaceable);候选生成 Prompt=muse_prompt(非 muse_ai_prompt);系统故事智能体=muse_agent(非 muse_ai_agent、agent_type=system、scope=all 不过滤);系统世界观库=muse_knowledge_base(kb_type=global、owner_user_id=0)。

global-setup 加 resetMetaGovernanceFixture(7 幂等 upsert ON CONFLICT、tenant_id=1、照 resetMarketFixture/resetJobsFixture 范式)。spec 删 :456 skip→拆 6 条细粒度真连用例(元结构/功能编排/AI/全局知识/市场/New-API),修 2 处 strict-mode 多命中。New-API 锚点契约纠正(mock 期 user-e2e/usage-e2e 伪造值):真后端绑定行是用户数字 id 字符串、用量 recordId 是聚合派生 {userId}:{period},改对真锚点(活体用户+1:month)、无后端缺口。

独立核实:连跑全量 e2e x2 各 12 passed/0 skipped(account/market/jobs 既有 6 真连无回归、幂等可复现);psql 复核 6 锚点种子真灌(function_chain/protection_node active、muse_prompt/muse_agent system/global KB 各 1 行真实契约)。至此 admin governance e2e 从全 page.route mock 全转真后端:12 真连用例 0 skip。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-25 13:05:37 -07:00
parent 2d06ec5f31
commit 6bd90301a5
2 changed files with 242 additions and 22 deletions

View File

@ -75,6 +75,9 @@ async function globalSetup(): Promise<void> {
// 复位 jobs/source-event 写命令子域 fixture(详见函数注释)。
await resetJobsFixture(client);
// 灌核心治理配置只读巡航子域 fixture(元结构/功能编排/AI/全局知识/New-API 用量,详见函数注释)。
await resetMetaGovernanceFixture(client);
} finally {
await client.end();
}
@ -188,6 +191,33 @@ async function resetMarketFixture(client: Client): Promise<void> {
const JOB_RETRY_FIXTURE_ID = 90_001;
const JOB_CANCEL_FIXTURE_ID = 90_002;
const SOURCE_EVENT_FIXTURE_ID = 90_003;
/**
* (//AI//New-API) e2e
*
* ****(),( total=0,),
* market/jobs ,"核心治理配置全页巡航"
* id(901xx),tenant_id=1,ON CONFLICT ; `.e2e` ,
*
* ( curl surface,):
* - story.draft(muse_meta_schema): status ; activeVersion=null
* - (muse_meta_function_chain): listQuery status='active';displayName chain_snapshot.displayName
* - (muse_meta_protection_node): status='active' + policy_snapshot->>'chainKey' , policy_snapshot chainKey
* - AI Prompt Prompt(muse_prompt): status ,name
* - (muse_agent):scope=all ,tab agent_type=system ;name
* - (muse_knowledge_base): kb_type='global' (owner_user_id=0);name
* - New-API 锚点不需独立种子:网关绑定行由 account (userId='1'/'活体用户'),
* (muse_member_usage_record) user1 recordId '1:month'
*/
const META_SCHEMA_FIXTURE_ID = 90_101;
const FUNCTION_CHAIN_FIXTURE_ID = 90_102;
const PROTECTION_NODE_FIXTURE_ID = 90_103;
const PROMPT_FIXTURE_ID = 90_104;
const SYSTEM_AGENT_FIXTURE_ID = 90_105;
const GLOBAL_KB_FIXTURE_ID = 90_106;
const USAGE_RECORD_FIXTURE_ID = 90_107;
/** 功能链路与保护节点的关联键:保护节点 policy_snapshot.chainKey 指向它,前端按 chainKey 拉保护节点列表。 */
const FUNCTION_CHAIN_FIXTURE_KEY = 'story.pipeline.e2e';
/** 重试 fixture 的 retry group;重试命令派生子 job 会复用同一 group,reset 据此清理上一轮派生行。 */
const JOB_RETRY_FIXTURE_GROUP = 'retry-group-e2e-fixture-retry';
/** 来源事件 fixture 的业务事件 id;来源重试派生的传播 job 在 input_summary.sourceEventId 里带它,reset 据此清理。 */
@ -293,4 +323,129 @@ async function resetJobsFixture(client: Client): Promise<void> {
);
}
/**
* e2e fixture(tenant_id=1 )
*
* 这些都是只读端点的展示锚点:本前置只** upsert**,"清上一轮"(),
* ON CONFLICT deleted=false / status / ,
* ( status=active policy_snapshot.chainKey KB kb_type=global ),
* "伪造糊弄断言"curl + New-API (account )
*
* best-effort 硬前置失败语义的反面:这些表本就空(),"灌入","复位已有行";
* upsert rowCount=0(,ON CONFLICT DO UPDATE ),,
*
* id GENERATED ALWAYS IDENTITY, id OVERRIDING SYSTEM VALUE
*/
async function resetMetaGovernanceFixture(client: Client): Promise<void> {
// 1) 元结构 story.draft:列表端点不按 status 过滤,draft 也可见;详情页 activeVersion=null 能正常渲染「草稿 JSON 编辑」。
// uk 是 (tenant_id, domain, scope, target_type, schema_key),冲突即复位展示名/状态。
const metaSchema = await client.query(
`INSERT INTO muse_meta_schema
(id, schema_key, domain, scope, target_type, display_name, status, projection_version,
creator, updater, deleted, tenant_id)
OVERRIDING SYSTEM VALUE
VALUES ($1, 'story.draft', 'content', 'work', 'novel_work', '故事草稿元结构', 'draft', 1,
'e2e-fixture', 'e2e-fixture', false, 1)
ON CONFLICT (tenant_id, domain, scope, target_type, schema_key)
DO UPDATE SET display_name = EXCLUDED.display_name, status = EXCLUDED.status, deleted = false`,
[META_SCHEMA_FIXTURE_ID],
);
// 2) 功能链路「故事链路」:列表 listQuery 只放行 status='active';displayName 取 chain_snapshot.displayName,
// activeVersion 取 active_version 字符串(无版本行时也能解析成 1)。uk 是 (tenant_id, chain_key)。
const functionChain = await client.query(
`INSERT INTO muse_meta_function_chain
(id, chain_key, active_version, chain_snapshot, status, creator, updater, deleted, tenant_id)
OVERRIDING SYSTEM VALUE
VALUES ($1, $2, '1',
'{"displayName": "故事链路", "description": "e2e fixture 故事生成功能链路"}'::jsonb,
'active', 'e2e-fixture', 'e2e-fixture', false, 1)
ON CONFLICT (tenant_id, chain_key)
DO UPDATE SET chain_snapshot = EXCLUDED.chain_snapshot, active_version = '1',
status = 'active', deleted = false`,
[FUNCTION_CHAIN_FIXTURE_ID, FUNCTION_CHAIN_FIXTURE_KEY],
);
// 3) 保护节点「事实校验」:列表按 status='active' + policy_snapshot->>'chainKey' 过滤,故 policy_snapshot 必须带
// chainKey 绑定到故事链路;replaceable=false → 前端展示「不可替换」。uk 是 (tenant_id, node_key)。
const protectionNode = await client.query(
`INSERT INTO muse_meta_protection_node
(id, node_key, display_name, owner_module, replaceable, policy_snapshot, status, node_type,
irreplaceable_reason, creator, updater, deleted, tenant_id)
OVERRIDING SYSTEM VALUE
VALUES ($1, 'protection.fact_check.e2e', '事实校验', 'meta', false,
('{"chainKey": "' || $2 || '", "chainDisplayName": "故事链路", "auditSummary": "e2e 事实校验保护节点"}')::jsonb,
'active', 'static_check', '事实校验为不可替换保护节点', 'e2e-fixture', 'e2e-fixture', false, 1)
ON CONFLICT (tenant_id, node_key)
DO UPDATE SET display_name = EXCLUDED.display_name, policy_snapshot = EXCLUDED.policy_snapshot,
status = 'active', deleted = false`,
[PROTECTION_NODE_FIXTURE_ID, FUNCTION_CHAIN_FIXTURE_KEY],
);
// 4) AI Prompt「候选生成 Prompt」:列表无 status 过滤;name 即前端展示名(默认「Prompt 模板」tab 可见)。
// uk 是 (tenant_id, prompt_key)。
const prompt = await client.query(
`INSERT INTO muse_prompt
(id, prompt_key, name, description, content, active_version, status, category,
creator, updater, deleted, tenant_id)
OVERRIDING SYSTEM VALUE
VALUES ($1, 'system.candidate.gen.e2e', '候选生成 Prompt', 'e2e fixture 候选生成 prompt',
'生成候选内容', '1', 'active', 'generation', 'e2e-fixture', 'e2e-fixture', false, 1)
ON CONFLICT (tenant_id, prompt_key)
DO UPDATE SET name = EXCLUDED.name, status = 'active', deleted = false`,
[PROMPT_FIXTURE_ID],
);
// 5) 系统智能体「系统故事智能体」:scope=all 不过滤,前端「系统智能体」tab 按 agent_type=system 展示;
// name 即展示名。uk 是 (tenant_id, agent_key)。
const systemAgent = await client.query(
`INSERT INTO muse_agent
(id, agent_key, name, description, agent_type, status, creator, updater, deleted, tenant_id)
OVERRIDING SYSTEM VALUE
VALUES ($1, 'system:story.e2e', '系统故事智能体', 'e2e fixture 系统故事智能体', 'system', 'active',
'e2e-fixture', 'e2e-fixture', false, 1)
ON CONFLICT (tenant_id, agent_key)
DO UPDATE SET name = EXCLUDED.name, agent_type = 'system', status = 'active', deleted = false`,
[SYSTEM_AGENT_FIXTURE_ID],
);
// 6) 全局知识库「系统世界观库」:列表按 kb_type='global' 过滤(系统级 owner_user_id=0);name 即展示名。
// 无业务 uk(uk 仅在 command_id 非空时生效),按主键 id 幂等。
const globalKb = await client.query(
`INSERT INTO muse_knowledge_base
(id, name, description, kb_type, owner_user_id, status, active_version, revision,
creator, updater, deleted, tenant_id)
OVERRIDING SYSTEM VALUE
VALUES ($1, '系统世界观库', 'e2e fixture 系统级世界观知识库', 'global', 0, 'active', 1, 1,
'e2e-fixture', 'e2e-fixture', false, 1)
ON CONFLICT (id)
DO UPDATE SET name = EXCLUDED.name, kb_type = 'global', status = 'active', deleted = false`,
[GLOBAL_KB_FIXTURE_ID],
);
// 7) New-API 调用归属用量记录:account usage 端点把同用户同周期的用量聚合成一行,recordId='{userId}:{period}',
// 前端默认 period=month。灌 user1 一行 completed 用量(create_time=now 落在本月,month periodStart 过滤可见),
// 真后端聚合出 recordId='1:month' → New-API「调用归属」tab 可断言。create_time 复位为 now 保证永远落在当月。
const usageRecord = await client.query(
`INSERT INTO muse_member_usage_record
(id, account_user_id, usage_type, token_input, token_output, cost_reference, correlation_id,
attribution_snapshot, status, creator, updater, deleted, tenant_id, create_time, update_time)
OVERRIDING SYSTEM VALUE
VALUES ($1, 1, 'ai_generation', 1200, 800, 'cost-e2e', 'corr-usage-e2e',
'{"attributedUserId": "1", "source": "e2e fixture"}'::jsonb, 'completed',
'e2e-fixture', 'e2e-fixture', false, 1, now(), now())
ON CONFLICT (id)
DO UPDATE SET token_input = EXCLUDED.token_input, token_output = EXCLUDED.token_output,
status = 'completed', deleted = false, create_time = now(), update_time = now()`,
[USAGE_RECORD_FIXTURE_ID],
);
console.log(
`[admin e2e globalSetup] reset meta-governance fixture: metaSchema=${metaSchema.rowCount} ` +
`functionChain=${functionChain.rowCount} protectionNode=${protectionNode.rowCount} ` +
`prompt=${prompt.rowCount} systemAgent=${systemAgent.rowCount} globalKb=${globalKb.rowCount} ` +
`usageRecord=${usageRecord.rowCount}(均为 upsert 写入条数)`,
);
}
export default globalSetup;

View File

@ -518,68 +518,133 @@ test('市场申诉以 maintained 结论真打处理命令并落库', async ({ pa
expectNoUnhandledMuseAdminApis(page);
});
// 断层标注(诚实记录,保留 skip):这条"核心治理配置全页巡航"用例当前**不能**转真后端。
// 它的断言混了两类:① 系统边界静态文案(各页 chrome,与数据无关,真后端能渲染);② 一批 mock 期数据
// 锚点——story.draft 元结构、"故事链路"功能链路、"事实校验"保护节点、"候选生成 Prompt"、"系统故事智能体"、
// "系统世界观库"、new-api 的 user-e2e/usage-e2e。这些数据锚点在 muse_slice_live **不存在**,且其中:
// - 功能链路 / 保护节点:切片库**根本没有 muse_function_chain / muse_protection_node 表**
// (端点真返 total=0 仅因无后端支撑表),"故事链路""事实校验"断言无表可灌,无法真连;
// - AI Prompt(/ai/prompts total=0)、全局知识库(/knowledge/global-kbs total=0):切片库未灌对应种子;
// - 元结构现有 worldview/setting 而非 story.draft;系统智能体现有"活体测试智能体"而非"系统故事智能体"。
// 用例还做数据依赖的交互(点"查看详情"跳 story.draft、点"保护节点"看"事实校验"、切 tab 看"系统故事智能体"),
// 这些行在缺数据(且保护节点/功能链路缺表)时无法定位 → 无法硬转。faithfully 转需跨 6 个子系统伪造数据,
// 远超本批"3 个 skip 转真连"范围,且功能链路/保护节点连表都没有、本质是后端切片支撑断层。
// 故保留 skip。account/market/jobs/source-event 子域的系统边界已分别由各自真连用例覆盖;此页"全页巡航"
// 待后端补齐功能链路/保护节点表 + 切片灌全 AI Prompt/全局知识库种子后再转真连。下方 body 留作"完整覆盖应断言什么"的记录。
test.skip('管理员可进入核心治理配置页面并看到系统边界', async ({ page }) => {
// 核心治理配置巡航 —— 转真后端(MSW-off,真打 48080 + 真治理数据种子)。
//
// 前提纠正(对抗上一 agent 的"缺表"误判):此前把本用例标 skip 时误判"切片库根本没有
// muse_function_chain / muse_protection_node 表"。经 psql 实证,真表名带 meta_ 前缀=
// muse_meta_function_chain(V3)+ muse_meta_protection_node(V3)+ 版本/槽位/节点(V10),
// **表都在 muse_slice_live、只是行数=0**(上一 agent 查无前缀表名才漏判)。元结构 / AI Prompt /
// 全局知识库 / 系统智能体同理:表都在,只是缺系统级公共治理对象的种子。故真断层是"切片库缺数据种子",
// **不是缺表、更不是后端缺功能**。globalSetup.resetMetaGovernanceFixture 已按各端点实证过的真实过滤契约
// 幂等灌入这些系统级锚点(故事链路 status=active、保护节点 policy_snapshot.chainKey 绑定、全局 KB kb_type=global
// 等),八个端点 curl 复核均真返锚点,故把原"全页巡航"拆成各子系统独立真连用例(下面六条),按数据锚点定位真实行 +
// 做真实交互(查看详情跳转 / 点保护节点拉列表 / 切 tab),不再伪造数据。
//
// 残留诚实标注(New-API 锚点契约纠正,见对应用例):原 mock 期锚点 'user-e2e'(网关绑定 userId 单元格)、
// 'usage-e2e'(用量 recordId)在真后端契约下**不可能**出现——绑定行的 userId 单元格是用户数字 id 字符串
// (toAdminSummary 取 stringId(user.getId())),用量 recordId 是后端聚合派生 '{userId}:{period}'
// (toAdminUsageRecord 里 accountUserId + ":" + period)。故 New-API 子系统改对真实锚点断言
// (绑定行真实用户'活体用户' + 用量 recordId '1:month'),而非伪造一个数字 id 叫 user-e2e。
// 元结构治理:真后端列出 story.draft 元结构 + 查看详情跳转 + 新建草稿入口。
test('元结构治理页对真后端列出 story.draft 并支持详情/新建草稿', async ({ page }) => {
await page.goto('/muse/governance/meta-schemas');
await expect(page.getByText('元结构定义').first()).toBeVisible();
// story.draft 由 resetMetaGovernanceFixture 灌入(列表端点不按 status 过滤,draft 也可见)。
await expect(page.getByText('story.draft')).toBeVisible();
await page.getByRole('button', { name: /查看详情/ }).click();
// 查看详情跳转到 story.draft 详情页:详情端点 activeVersion=null 也能正常渲染「草稿 JSON 编辑」。
await page.getByRole('button', { name: /查看详情/ }).first().click();
await expect(page).toHaveURL(/\/muse\/governance\/meta-schemas\/story\.draft$/);
await expect(page.getByText('草稿 JSON 编辑')).toBeVisible();
// 新建草稿入口(纯前端路由,无需后端数据):跳 __create__ 创建模式。
await page.goto('/muse/governance/meta-schemas');
await page.getByRole('button', { name: /新建草稿/ }).click();
await expect(page).toHaveURL(/\/muse\/governance\/meta-schemas\/__create__\?mode=create$/);
await expect(page).toHaveURL(
/\/muse\/governance\/meta-schemas\/__create__\?mode=create$/,
);
await expect(page.getByPlaceholder('例如 work.story')).toBeVisible();
expectNoUnhandledMuseAdminApis(page);
});
// 功能编排治理:真后端列出「故事链路」+ 点「保护节点」拉出绑定的「事实校验」保护节点。
test('功能编排页对真后端列出故事链路并展开事实校验保护节点', async ({ page }) => {
await page.goto('/muse/governance/function-orchestration');
await expect(
page.getByText('开放槽位只表示可替换的非保护节点').first(),
).toBeVisible();
// 故事链路由 fixture 灌入(列表 listQuery 只放行 status='active';displayName 取 chain_snapshot.displayName)。
await expect(page.getByText('故事链路')).toBeVisible();
await page.getByRole('button', { name: '保护节点' }).first().click();
await expect(page.getByText('事实校验')).toBeVisible();
// 点「保护节点」→ 前端按该链路 chainKey 拉保护节点列表;事实校验 policy_snapshot.chainKey 绑定到故事链路。
// exact 收口:fixture 的不可替换原因/审计摘要列也含「事实校验」子串,精确匹配 nodeName 单元格避免多命中。
await page.getByRole('button', { name: '保护节点' }).first().click();
await expect(
page.getByRole('cell', { name: '事实校验', exact: true }),
).toBeVisible();
expectNoUnhandledMuseAdminApis(page);
});
// AI 配置治理:真后端展示「候选生成 Prompt」(默认 Prompt tab)+ 切「系统智能体」tab 看「系统故事智能体」。
test('AI 配置页对真后端展示候选生成 Prompt 与系统故事智能体', async ({ page }) => {
await page.goto('/muse/ai');
await expect(page.getByText('AI 配置域治理边界')).toBeVisible();
// 候选生成 Prompt 由 fixture 灌入(列表无 status 过滤,name 即展示名,默认「Prompt 模板」tab 可见)。
await expect(page.getByText('候选生成 Prompt')).toBeVisible();
// 切「系统智能体」tab:系统故事智能体 agent_type=system,scope=all 列表含它。
// 收口到卡片标题 heading:fixture 的智能体描述「e2e fixture 系统故事智能体」也含同名子串,按 heading 避免多命中。
await page.getByRole('tab', { name: '系统智能体' }).click();
await expect(page.getByText('用户私有 Agent 边界')).toBeVisible();
await expect(page.getByText('系统故事智能体')).toBeVisible();
await expect(
page.getByRole('heading', { name: '系统故事智能体' }),
).toBeVisible();
expectNoUnhandledMuseAdminApis(page);
});
// 全局知识治理:真后端展示「系统世界观库」全局知识库 + 系统边界文案。
test('全局知识页对真后端展示系统世界观库', async ({ page }) => {
await page.goto('/muse/knowledge');
await expect(page.getByRole('heading', { name: '全局知识管理' })).toBeVisible();
await expect(page.getByText('不读取用户私有正文')).toBeVisible();
// 系统世界观库由 fixture 灌入(列表按 kb_type='global' 过滤,owner_user_id=0,name 即展示名)。
await expect(page.getByText('系统世界观库').first()).toBeVisible();
expectNoUnhandledMuseAdminApis(page);
});
// 市场治理巡航:真后端市场治理页边界 + 三个 tab 的 chrome(数据锚点由 market 真连用例另行覆盖,这里只巡航 chrome)。
// market 子域已由 resetMarketFixture 灌真实资产/申诉,故资产名/申诉关联资产也能真断言。
test('市场治理页对真后端巡航三大工作台边界', async ({ page }) => {
await page.goto('/muse/market');
await expect(page.getByRole('heading', { name: '市场治理' })).toBeVisible();
// 审核队列(默认 active)tab 头文案。
await expect(page.getByText('待审资产').first()).toBeVisible();
await page.getByRole('tab', { name: '资产治理' }).click();
await expect(page.getByText('不改用户私有副本').first()).toBeVisible();
await expect(page.getByText('市场资产').first()).toBeVisible();
const assetPanel = page.getByRole('tabpanel', { name: '资产治理' });
await expect(assetPanel.getByText('活体市场资产·测试')).toBeVisible();
await page.getByRole('tab', { name: '申诉处理' }).click();
await expect(page.getByText('申诉人').first()).toBeVisible();
expectNoUnhandledMuseAdminApis(page);
});
// New-API 治理对账巡航:真后端绑定/用量摘要 + 系统边界(不展示凭据)。
//
// 锚点契约纠正(对抗 mock 假设):原用例断言单元格 'user-e2e' / 文案 'usage-e2e' 是 mock 期伪造值,真后端契约
// 下不可能出现——网关绑定 userId 单元格是用户数字 id 字符串(toAdminSummary 取 stringId(user.getId())),
// 用量 recordId 是后端聚合派生 '{userId}:{period}'(默认 period=month → '1:month')。故改对真实锚点断言:
// 绑定 tab 看真实用户'活体用户'(account 用户投影,稳定存在);调用归属 tab 看 fixture 用量真聚合出的 recordId '1:month'。
test('New-API 页对真后端展示绑定与调用归属对账摘要', async ({ page }) => {
await page.goto('/muse/new-api');
await expect(
page.getByText('New-API 页面只展示治理和对账摘要'),
).toBeVisible();
await expect(page.getByText('不展示凭据')).toBeVisible();
await expect(page.getByRole('cell', { name: 'user-e2e' })).toBeVisible();
// 网关绑定 tab(默认):真实用户'活体用户'的网关绑定行(userId 单元格是数字 id 字符串,昵称唯一可断言)。
await expect(page.getByRole('cell', { name: '活体用户' })).toBeVisible();
// 调用归属 tab:resetMetaGovernanceFixture 灌的 user1 用量记录被后端聚合成 recordId '1:month'。
await page.getByRole('tab', { name: '调用归属' }).click();
await expect(page.getByText('usage-e2e')).toBeVisible();
await expect(page.getByRole('cell', { name: '1:month' })).toBeVisible();
expectNoUnhandledMuseAdminApis(page);
});