diff --git a/packages/cli/src/context-build-view.test.ts b/packages/cli/src/context-build-view.test.ts index c232d432..53287794 100644 --- a/packages/cli/src/context-build-view.test.ts +++ b/packages/cli/src/context-build-view.test.ts @@ -625,6 +625,54 @@ describe('runContextBuild', () => { expect(io.stdout()).not.toContain('historic-sql'); }); + it('passes schema-first notices from the plan into foreground output', async () => { + const io = makeIo(); + const project = { + ...projectWithConnections({ + warehouse: { driver: 'postgres', context: { depth: 'deep' } }, + }), + config: { + ...projectWithConnections({ warehouse: { driver: 'postgres' } }).config, + connections: { + warehouse: { driver: 'postgres', context: { depth: 'deep' } }, + }, + llm: { + provider: { backend: 'gateway', gateway: { api_key: 'env:KTX_GATEWAY_API_KEY' } }, + models: { default: 'gpt-test' }, + }, + scan: { + ...projectWithConnections({ warehouse: { driver: 'postgres' } }).config.scan, + enrichment: { + mode: 'llm', + embeddings: { + backend: 'openai', + model: 'text-embedding-3-small', + dimensions: 1536, + }, + }, + }, + }, + }; + const executeTarget = vi.fn(async (target) => successResult(target.connectionId, target.driver, target.operation)); + + await expect( + runContextBuild( + project, + { + projectDir: '/tmp/project', + inputMode: 'disabled', + targetConnectionId: 'warehouse', + all: false, + queryHistory: 'enabled', + }, + io.io, + { executeTarget, now: () => 1000 }, + ), + ).resolves.toMatchObject({ exitCode: 0 }); + + expect(io.stdout()).toContain('Schema ingest runs before query history for warehouse.'); + }); + it('renders final view for non-TTY output', async () => { const io = makeIo(); const project = projectWithConnections({ diff --git a/packages/cli/src/public-ingest.test.ts b/packages/cli/src/public-ingest.test.ts index 5e0fb25e..3dd1174c 100644 --- a/packages/cli/src/public-ingest.test.ts +++ b/packages/cli/src/public-ingest.test.ts @@ -223,6 +223,21 @@ describe('buildPublicIngestPlan', () => { }); }); + it('adds a schema-first notice when query history is explicitly enabled', () => { + const project = deepReadyProject({ + warehouse: { driver: 'postgres', context: { depth: 'deep' } }, + }); + + expect( + buildPublicIngestPlan(project, { + projectDir: '/tmp/project', + targetConnectionId: 'warehouse', + all: false, + queryHistory: 'enabled', + }).notices, + ).toEqual(['Schema ingest runs before query history for warehouse.']); + }); + it('warns and skips query-history window override for unsupported database drivers', () => { const plan = buildPublicIngestPlan( projectWithConnections({ @@ -373,6 +388,33 @@ describe('runKtxPublicIngest', () => { ); }); + it('prints the schema-first notice for explicit query-history runs', async () => { + const io = makeIo(); + const project = deepReadyProject({ + warehouse: { driver: 'postgres', context: { depth: 'deep' } }, + }); + const runScan = vi.fn(async () => 0); + const runIngest = vi.fn(async () => 0); + + await expect( + runKtxPublicIngest( + { + command: 'run', + projectDir: '/tmp/project', + targetConnectionId: 'warehouse', + all: false, + json: false, + inputMode: 'disabled', + queryHistory: 'enabled', + }, + io.io, + { loadProject: vi.fn(async () => project), runScan, runIngest }, + ), + ).resolves.toBe(0); + + expect(io.stdout()).toContain('Schema ingest runs before query history for warehouse.'); + }); + it('suppresses internal scan output for public database ingest summaries', async () => { const io = makeIo(); const project = projectWithConnections({ warehouse: { driver: 'postgres' } }); diff --git a/packages/cli/src/public-ingest.ts b/packages/cli/src/public-ingest.ts index 7a47d13f..f2119edf 100644 --- a/packages/cli/src/public-ingest.ts +++ b/packages/cli/src/public-ingest.ts @@ -67,6 +67,7 @@ export interface KtxPublicIngestPlan { projectDir: string; targets: KtxPublicIngestPlanTarget[]; warnings: string[]; + notices?: string[]; } export interface KtxPublicIngestTargetResult { @@ -173,6 +174,23 @@ function finalizeWarnings( return warnings; } +function schemaFirstQueryHistoryNotice( + targets: KtxPublicIngestPlanTarget[], + args: { queryHistory?: KtxPublicIngestQueryHistoryFlag }, +): string | null { + if (args.queryHistory !== 'enabled') { + return null; + } + const queryHistoryTargets = targets.filter((target) => target.queryHistory?.enabled === true); + if (queryHistoryTargets.length === 0) { + return null; + } + if (queryHistoryTargets.length === 1) { + return `Schema ingest runs before query history for ${queryHistoryTargets[0].connectionId}.`; + } + return `Schema ingest runs before query history for ${queryHistoryTargets.length} database connections.`; +} + function storedQueryHistory(connection: KtxProjectConnectionConfig): Record { const context = connection.context; const contextRecord = @@ -356,13 +374,16 @@ export function buildPublicIngestPlan( const targets = selected.map(([connectionId, connection]) => targetForConnection(connectionId, connection, project.config, args, warnings), ); + const orderedTargets = [ + ...targets.filter((t) => t.operation === 'database-ingest'), + ...targets.filter((t) => t.operation === 'source-ingest'), + ]; + const notice = schemaFirstQueryHistoryNotice(orderedTargets, args); return { projectDir: args.projectDir, - targets: [ - ...targets.filter((t) => t.operation === 'database-ingest'), - ...targets.filter((t) => t.operation === 'source-ingest'), - ], + targets: orderedTargets, warnings: finalizeWarnings(warnings, args), + ...(notice ? { notices: [notice] } : {}), }; } @@ -646,7 +667,10 @@ export async function runKtxPublicIngest( const plan = buildPublicIngestPlan(project, args); const results: KtxPublicIngestTargetResult[] = []; - if (!args.json && plan.warnings.length > 0) { + if (!args.json) { + for (const notice of plan.notices ?? []) { + io.stdout.write(`${notice}\n`); + } for (const warning of plan.warnings) { io.stderr.write(`Warning: ${warning}\n`); }