数据目录 F3 / B1 界面:运行横幅、时间线和版本历史把 Agent 可能查询的表和直接引用分开说——直接引用写「有变化」,可能涉及写「Agent 可能查询的表有变化」并另起一组;版本历史按新的发布记录两组列出,发布之后才建目录的表也列;补发布记录两种写法的单测和三个检查脚本的断言

This commit is contained in:
ThearchyHelios
2026-10-04 01:24:50 +08:00
parent 52aaaaf945
commit 3ece89872b
9 changed files with 222 additions and 69 deletions
+20
View File
@@ -737,6 +737,26 @@ await section('正式运行:发布之后数据目录有变化(catalog.drift
&& row.detail.includes('「scenic」parks:发布时尚无目录,现为第 1 版'), row?.detail)
const one = mod.catalogDrift({ tables: [{ source: 's', table: 't', label: null, published: 1, current: 2 }] })
check('只有一张表:标题不写「等」', one?.title === '自发布以来,数据目录中「t」有变化', one?.title)
check('老事件的表没有 impact:按直接引用', one?.direct.length === 1 && one?.possible.length === 0 && one?.tables[0].impact === 'direct')
// Agent 可能查询的表(SQL 运行时才写):和直接引用分开说
const mixed = mod.catalogDrift({ tables: [
{ source: 'scenic', source_id: 'src-scenic', table: 'visits', label: '入园记录', published: 3, current: 5, impact: 'direct' },
{ source: 'scenic', source_id: 'src-scenic', table: 'orders', label: '订单', published: 2, current: 4, impact: 'possible' },
{ source: 'scenic', source_id: 'src-scenic', table: 'gates', label: null, published: 0, current: 1, impact: 'possible' },
] })
check('两组都有:标题说直接引用的,再补一句 Agent 可能查询的表有变化',
mixed?.title === '自发布以来,数据目录中「入园记录」有变化;另有 2 张 Agent 可能查询的表有变化', mixed?.title)
check('……展开:直接引用在前,可能涉及的在小标题下面', mixed?.detail === [
'「scenic」入园记录(visits):发布时第 3 版,现为第 5 版', 'Agent 可能查询的表:',
'「scenic」订单(orders):发布时第 2 版,现为第 4 版', '「scenic」gates:发布时尚无目录,现为第 1 版'].join('\n'), mixed?.detail)
const agentOnly = mod.catalogDrift({ tables: [
{ source: 'scenic', source_id: 'src-scenic', table: 'orders', label: '订单', published: 2, current: 4, impact: 'possible' },
] })
check('只有可能涉及的:标题直说「Agent 可能查询的表有变化」', agentOnly?.title === '自发布以来,Agent 可能查询的表有变化:「订单」', agentOnly?.title)
const run = mod.decodeRun([{ seq: 1, type: 'catalog.drift', node_id: null, ts: 1, data: { version: 2, count: 1, tables: [
{ source: 'scenic', source_id: 'src-scenic', table: 'orders', label: '订单', published: 2, current: 4, impact: 'possible' }] } }])
.find((s) => s.code === 'catalog_drift')
check('……时间线里那一行同样是警告', run?.level === 'warn' && run?.title === agentOnly?.title, run?.title)
check('形状不对不出行', mod.decodeRun([{ seq: 1, type: 'catalog.drift', node_id: null, ts: 1, data: { tables: 'x' } }])
.every((s) => s.code !== 'catalog_drift'))
})
+15 -4
View File
@@ -706,9 +706,11 @@ await section('正式运行:发布之后数据目录有变化,提醒但不
], edges: [{ source: 'a', target: 'q' }] }
const events = [
{ seq: 1, type: 'run.started', node_id: null, ts: t, data: { nodes: 2 } },
{ seq: 2, type: 'catalog.drift', node_id: null, ts: t + 0.01, data: { version: 3, count: 2, tables: [
{ source: 'scenic', source_id: 'src-scenic', table: 'visits', label: '入园记录', published: 3, current: 5 },
{ source: 'scenic', source_id: 'src-scenic', table: 'parks', label: null, published: 0, current: 1 },
{ seq: 2, type: 'catalog.drift', node_id: null, ts: t + 0.01, data: { version: 3, count: 3, tables: [
{ source: 'scenic', source_id: 'src-scenic', table: 'visits', label: '入园记录', published: 3, current: 5, impact: 'direct' },
{ source: 'scenic', source_id: 'src-scenic', table: 'parks', label: null, published: 0, current: 1, impact: 'direct' },
// Agent 绑定了这个源的查询工具:SQL 运行时才写,记的是源里有目录的表
{ source: 'scenic', source_id: 'src-scenic', table: 'orders', label: '订单', published: 2, current: 4, impact: 'possible' },
] } },
{ seq: 3, type: 'node.started', node_id: 'a', ts: t + 0.1, data: {} },
{ seq: 4, type: 'node.finished', node_id: 'a', ts: t + 0.2, data: { duration_ms: 100 } },
@@ -723,11 +725,20 @@ await section('正式运行:发布之后数据目录有变化,提醒但不
const banner = page.locator('[data-run-banner="catalog-drift"]')
check('详情显示目录变化的横幅', await banner.waitFor({ timeout: 5000 }).then(() => true, () => false))
const text = await banner.innerText().catch(() => '')
check('横幅标题:「入园记录」等 2 张表有变化', text.includes('自发布以来,数据目录中「入园记录」等 2 张表有变化'), text.split('\n')[0])
check('横幅标题:「入园记录」等 2 张表有变化,另有 1 张 Agent 可能查询的表有变化',
text.includes('自发布以来,数据目录中「入园记录」等 2 张表有变化;另有 1 张 Agent 可能查询的表有变化'), text.split('\n')[0])
check('写明运行未被拦截', text.includes('运行未被拦截'))
check('逐表写发布时和现在的版本,没有目录写「尚无目录」', text.includes('「scenic」入园记录(visits):发布时第 3 版,现为第 5 版')
&& text.includes('「scenic」parks:发布时尚无目录,现为第 1 版'), text.replace(/\s+/g, ' '))
check('每张表给到数据目录的入口', (await banner.locator('[data-drift-open="visits"]').getAttribute('href')) === '/data/catalog/src-scenic/visits')
const possible = banner.locator('[data-drift-possible]')
const possibleText = await possible.innerText().catch(() => '')
check('Agent 可能查询的表另起一组:小标题、说明、逐表一行', await possible.count() === 1
&& possibleText.includes('Agent 可能查询的表') && possibleText.includes('SQL 在运行时生成')
&& possibleText.includes('「scenic」订单(orders):发布时第 2 版,现为第 4 版'), possibleText.replace(/\s+/g, ' '))
check('……直接引用的表不在这一组里', await possible.locator('[data-drift-table="visits"]').count() === 0
&& (await banner.locator('[data-drift-table="orders"]').getAttribute('data-drift-impact')) === 'possible')
check('……也给到数据目录的入口', (await banner.locator('[data-drift-open="orders"]').getAttribute('href')) === '/data/catalog/src-scenic/orders')
check('运行照常完成:没有失败横幅', await page.locator('[data-run-banner=failed]').count() === 0)
const row = page.locator('[data-step-code="catalog_drift"]')
check('时间线里也有一行提醒', await row.count() === 1 && (await row.innerText()).includes('等 2 张表有变化'))
+22 -4
View File
@@ -263,11 +263,17 @@ async function open({ width = 1440, height = 900, path = '/studio/st-main', pref
if (sub === 'versions' && !v) return json(route, VERSIONS)
if (sub === 'versions' && v) {
const n = Number(v)
// 已发布的 v2 记着发布时的目录版本:入园记录之后改过(第 3 版 → 第 5 版),景区表没变
// 已发布的 v2 记着发布时的目录版本:入园记录之后改过(第 3 版 → 第 5 版),景区表没变;
// Agent 绑定了 shop 的查询工具,可能查询的表里订单改过、闸机是发布之后才建的目录,另一个源发布时还没有目录
return json(route, { ...VERSIONS.find((x) => x.version === n), workflow_id: id,
graph: n === 1 ? V1 : GRAPH, graph_hash: 'x', input_fields: [{ name: 'goal' }], published: n === 2,
catalog_versions: n === 2 ? { shop: { parks: 1, visits: 3 } } : null,
catalog_changes: n === 2 ? [{ source: 'shop', source_id: 'src-shop', table: 'visits', label: '入园记录', published: 3, current: 5 }] : [] })
catalog_versions: n === 2
? { direct: { shop: { parks: 1, visits: 3 } }, possible: { shop: { orders: 2, channels: 1 }, crm: {} } } : null,
catalog_changes: n === 2 ? [
{ source: 'shop', source_id: 'src-shop', table: 'visits', label: '入园记录', published: 3, current: 5, impact: 'direct' },
{ source: 'shop', source_id: 'src-shop', table: 'orders', label: '订单', published: 2, current: 4, impact: 'possible' },
{ source: 'shop', source_id: 'src-shop', table: 'gates', label: null, published: 0, current: 1, impact: 'possible' },
] : [] })
}
if (method === 'GET' && !sub) return state.deleted.has(id) ? json(route, { detail: '工作流不存在,可能已被删除' }, 404) : json(route, FAKES[id])
if (method === 'DELETE' && !sub) {
@@ -1011,8 +1017,20 @@ await section('版本历史:预览、恢复成一次可撤销的改动', async
const catText = await cat.innerText().catch(() => '')
check('已发布的版本写「发布时的目录版本」', catText.includes('发布时的目录版本') && catText.includes('parks') && catText.includes('第 1 版'), catText)
check('之后改过的表标出来', (await page.locator('[data-version-catalog-table="visits"]').getAttribute('data-changed')) === 'true'
&& catText.includes('之后有变化,现为第 5 版') && catText.includes('1 张表在发布之后有变化'), catText.replace(/\s+/g, ' '))
&& catText.includes('之后有变化,现为第 5 版') && catText.includes('3 张表在发布之后有变化'), catText.replace(/\s+/g, ' '))
check('没改过的表不标', (await page.locator('[data-version-catalog-table="parks"]').getAttribute('data-changed')) === null)
// Agent 可能查询的表:另起一组,和 SQL 里写着的表分开
const possible = cat.locator('[data-version-catalog-possible]')
const possibleText = await possible.innerText().catch(() => '')
check('Agent 可能查询的表另起一组', await possible.count() === 1 && possibleText.includes('Agent 可能查询的表')
&& await possible.locator('[data-version-catalog-table="visits"]').count() === 0, possibleText.replace(/\s+/g, ' '))
check('……改过的标出来,没改过的不标', (await possible.locator('[data-version-catalog-table="orders"]').getAttribute('data-changed')) === 'true'
&& (await possible.locator('[data-version-catalog-table="channels"]').getAttribute('data-changed')) === null
&& possibleText.includes('之后有变化,现为第 4 版'), possibleText.replace(/\s+/g, ' '))
check('……发布之后才建目录的表也列出来,发布时写「尚无目录」',
(await possible.locator('[data-version-catalog-table="gates"]').getAttribute('data-changed')) === 'true'
&& (await possible.locator('[data-version-catalog-table="gates"]').innerText()).includes('尚无目录'))
check('……发布时一张有目录的表都没有的源写一句', possibleText.includes('「crm」发布时还没有目录'), possibleText.replace(/\s+/g, ' '))
await page.locator('ol[aria-label="版本"] > li').nth(2).locator('button').first().click()
await page.waitForTimeout(300)
check('没发布过的版本没有目录版本这一栏', await count(page, '[data-version-catalog]') === 0)