diff --git a/packages/context/src/connections/local-warehouse-descriptor.test.ts b/packages/context/src/connections/local-warehouse-descriptor.test.ts index 5864c833..0eee9f34 100644 --- a/packages/context/src/connections/local-warehouse-descriptor.test.ts +++ b/packages/context/src/connections/local-warehouse-descriptor.test.ts @@ -36,7 +36,13 @@ describe('localConnectionToWarehouseDescriptor', () => { }); it('returns null for non-warehouse adapters', () => { - expect(localConnectionToWarehouseDescriptor('looker', { driver: 'looker' })).toBeNull(); + expect( + localConnectionToWarehouseDescriptor('looker', { + driver: 'looker', + base_url: 'https://looker.example.com', + client_id: 'client', + }), + ).toBeNull(); }); }); @@ -48,7 +54,9 @@ describe('local connection info helpers', () => { }); it('keeps non-warehouse adapter labels for display-only local connection surfaces', () => { - expect(localConnectionTypeForConfig('prod-metabase', { driver: 'metabase' })).toBe('metabase'); + expect(localConnectionTypeForConfig('prod-metabase', { driver: 'metabase', api_url: 'https://metabase.example.com' })).toBe( + 'metabase', + ); expect(localConnectionTypeForConfig('missing-driver', {} as never)).toBe('unknown'); }); diff --git a/packages/context/src/connections/notion-config.ts b/packages/context/src/connections/notion-config.ts index c7068512..24dd5d4b 100644 --- a/packages/context/src/connections/notion-config.ts +++ b/packages/context/src/connections/notion-config.ts @@ -13,7 +13,20 @@ export const KTX_NOTION_ORG_KNOWLEDGE_WARNING = type KtxNotionCrawlMode = 'all_accessible' | 'selected_roots'; -export interface KtxNotionConnectionConfig extends KtxProjectConnectionConfig { +type RawKtxNotionConnectionConfig = Extract; + +export type KtxNotionConnectionConfig = Omit< + RawKtxNotionConnectionConfig, + | 'auth_token' + | 'auth_token_ref' + | 'crawl_mode' + | 'root_page_ids' + | 'root_database_ids' + | 'root_data_source_ids' + | 'max_pages_per_run' + | 'max_knowledge_creates_per_run' + | 'max_knowledge_updates_per_run' +> & { driver: 'notion'; auth_token: string | null; auth_token_ref: string | null; @@ -24,7 +37,7 @@ export interface KtxNotionConnectionConfig extends KtxProjectConnectionConfig { max_pages_per_run: number; max_knowledge_creates_per_run: number; max_knowledge_updates_per_run: number; -} +}; export interface RedactedKtxNotionConnectionConfig { driver: 'notion'; diff --git a/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts b/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts index 1139ea4d..b337fc66 100644 --- a/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts +++ b/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts @@ -3,6 +3,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { buildDefaultKtxProjectConfig } from '../../../project/index.js'; +import { connectionConfigSchema } from '../../../project/driver-schemas.js'; import { KtxYamlMetabaseSourceStateReader, LocalMetabaseDiscoveryCache } from './local-source-state-store.js'; describe('Metabase YAML source state and discovery cache', () => { @@ -23,10 +24,11 @@ describe('Metabase YAML source state and discovery cache', () => { config: { ...buildDefaultKtxProjectConfig('metabase-cache-test'), connections: { - 'prod-metabase': { + 'prod-metabase': connectionConfigSchema.parse({ driver: 'metabase', + api_url: 'https://metabase.example.com', mappings, - }, + }), }, }, }; diff --git a/packages/context/src/ingest/local-mapping-reconcile.test.ts b/packages/context/src/ingest/local-mapping-reconcile.test.ts index 4a5d2740..903ef43f 100644 --- a/packages/context/src/ingest/local-mapping-reconcile.test.ts +++ b/packages/context/src/ingest/local-mapping-reconcile.test.ts @@ -27,11 +27,12 @@ describe('local mapping yaml reconciliation bridge', () => { const project = projectWithConnections({ 'prod-metabase': { driver: 'metabase', + api_url: 'https://metabase.example.com', mappings: { databaseMappings: { '1': 'prod-warehouse' }, syncEnabled: { '1': true }, syncMode: 'ONLY', - selections: { collections: [12] }, + selections: { collections: [12], items: [] }, defaultTagNames: ['ktx'], }, }, @@ -46,6 +47,8 @@ describe('local mapping yaml reconciliation bridge', () => { const project = projectWithConnections({ 'prod-looker': { driver: 'looker', + base_url: 'https://looker.example.com', + client_id: 'client', mappings: { connectionMappings: { analytics: 'prod-warehouse' } }, }, 'prod-warehouse': { driver: 'postgres', url: 'postgresql://readonly@db.test/analytics' }, diff --git a/packages/context/src/project/config.test.ts b/packages/context/src/project/config.test.ts index be6d5219..9ecbcb3e 100644 --- a/packages/context/src/project/config.test.ts +++ b/packages/context/src/project/config.test.ts @@ -531,4 +531,11 @@ describe('generateKtxProjectConfigJsonSchema', () => { const relationships = scan?.properties?.relationships as { properties?: Record }; expect(relationships?.properties?.acceptThreshold?.description).toMatch(/auto-accepted/); }); + + it('emits the mappings shapes under connections', () => { + const serialized = JSON.stringify(schema); + expect(serialized).toContain('databaseMappings'); + expect(serialized).toContain('connectionMappings'); + expect(serialized).toContain('expectedLookerConnectionName'); + }); }); diff --git a/packages/context/src/project/config.ts b/packages/context/src/project/config.ts index 55da5413..07c30891 100644 --- a/packages/context/src/project/config.ts +++ b/packages/context/src/project/config.ts @@ -1,6 +1,7 @@ import { KTX_MODEL_ROLES } from '@ktx/llm'; import YAML from 'yaml'; import * as z from 'zod'; +import { connectionConfigSchema } from './driver-schemas.js'; const KTX_LLM_BACKENDS = ['none', 'anthropic', 'vertex', 'gateway'] as const; const KTX_EMBEDDING_BACKENDS = ['none', 'deterministic', 'openai', 'sentence-transformers'] as const; @@ -206,12 +207,7 @@ const storageSchema = z }) .describe('Storage backends and commit policy for KTX state and search indexes.'); -const connectionSchema = z - .looseObject({ - driver: z.string().min(1).optional().describe('Connector driver identifier (e.g. "postgres", "bigquery", "snowflake").'), - url: z.string().optional().describe('Connection URL or DSN. Format depends on the driver; may contain environment-variable references.'), - }) - .describe('A single database/connector connection entry. Additional driver-specific fields are accepted and passed through.'); +const connectionSchema = connectionConfigSchema; const agentSchema = z .strictObject({ diff --git a/packages/context/src/project/mappings-yaml-schema.ts b/packages/context/src/project/mappings-yaml-schema.ts index c20b0e69..43ad6515 100644 --- a/packages/context/src/project/mappings-yaml-schema.ts +++ b/packages/context/src/project/mappings-yaml-schema.ts @@ -1,5 +1,4 @@ import * as z from 'zod'; -import type { KtxProjectConnectionConfig } from './config.js'; const metabaseSyncModeSchema = z.enum(['ALL', 'ONLY', 'EXCEPT']); const positiveIntegerValueSchema = z.number().int().positive(); @@ -78,6 +77,11 @@ export type LookmlMappingBootstrap = { export type ConnectionMappingBootstrap = MetabaseMappingBootstrap | LookerMappingBootstrap | LookmlMappingBootstrap; +type MappingConnectionInput = Record & { + driver?: unknown; + mappings?: unknown; +}; + function recordValue(value: unknown): Record { return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record) : {}; } @@ -90,13 +94,13 @@ function assertPositiveIntegerKeys(field: string, record: Record