@kaelio/ktx 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/{kaelio_ktx-0.10.0-py3-none-any.whl → kaelio_ktx-0.12.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin.js +1 -1
- package/dist/clack.d.ts +16 -0
- package/dist/clack.js +37 -6
- package/dist/claude-code-prompt-caching.js +1 -1
- package/dist/cli-program.js +7 -3
- package/dist/cli-runtime.d.ts +2 -0
- package/dist/cli-runtime.js +14 -8
- package/dist/commands/connection-commands.js +1 -1
- package/dist/commands/ingest-commands.js +4 -4
- package/dist/commands/mcp-commands.js +12 -12
- package/dist/commands/runtime-commands.js +4 -4
- package/dist/commands/setup-commands.js +6 -5
- package/dist/commands/sl-commands.js +1 -1
- package/dist/commands/sql-commands.js +1 -1
- package/dist/commands/status-commands.js +1 -1
- package/dist/community-cta.d.ts +11 -0
- package/dist/community-cta.js +19 -0
- package/dist/connection.js +1 -1
- package/dist/connectors/clickhouse/connector.js +1 -1
- package/dist/connectors/mysql/connector.js +1 -1
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +3 -3
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +116 -2
- package/dist/context/core/git-env.d.ts +12 -1
- package/dist/context/core/git-env.js +17 -2
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +86 -15
- package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
- package/dist/context/ingest/adapters/looker/client.js +7 -2
- package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
- package/dist/context/ingest/adapters/looker/factory.js +9 -0
- package/dist/context/ingest/adapters/looker/mapping.js +1 -1
- package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.js +1 -1
- package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
- package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
- package/dist/context/ingest/artifact-gates.d.ts +2 -6
- package/dist/context/ingest/artifact-gates.js +5 -47
- package/dist/context/ingest/constrained-repair.d.ts +55 -0
- package/dist/context/ingest/constrained-repair.js +167 -0
- package/dist/context/ingest/final-gate-repair.d.ts +9 -11
- package/dist/context/ingest/final-gate-repair.js +40 -128
- package/dist/context/ingest/finalization-scope.d.ts +1 -1
- package/dist/context/ingest/finalization-scope.js +15 -15
- package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
- package/dist/context/ingest/ingest-bundle.runner.js +101 -67
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
- package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
- package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
- package/dist/context/ingest/local-bundle-runtime.js +9 -10
- package/dist/context/ingest/local-ingest.d.ts +2 -0
- package/dist/context/ingest/local-ingest.js +2 -0
- package/dist/context/ingest/memory-flow/view-model.js +1 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
- package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
- package/dist/context/llm/ai-sdk-runtime.js +2 -2
- package/dist/context/llm/claude-code-runtime.js +1 -1
- package/dist/context/llm/local-config.js +1 -1
- package/dist/context/llm/runtime-tools.js +2 -2
- package/dist/context/mcp/context-tools.js +7 -7
- package/dist/context/mcp/local-project-ports.js +23 -54
- package/dist/context/memory/local-memory.js +4 -1
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +11 -4
- package/dist/context/project/config.js +85 -30
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/project/mappings-yaml-schema.js +2 -2
- package/dist/context/project/project.js +12 -4
- package/dist/context/scan/description-generation.js +4 -4
- package/dist/context/scan/local-enrichment-artifacts.js +2 -1
- package/dist/context/scan/local-scan.js +2 -2
- package/dist/context/scan/local-structural-artifacts.js +5 -5
- package/dist/context/scan/relationship-benchmark-report.js +1 -1
- package/dist/context/scan/relationship-discovery.js +3 -3
- package/dist/context/scan/relationship-llm-proposal.js +3 -3
- package/dist/context/sl/local-query.js +3 -33
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +44 -69
- package/dist/context/sl/semantic-layer.service.d.ts +25 -8
- package/dist/context/sl/semantic-layer.service.js +109 -56
- package/dist/context/sl/source-files.d.ts +46 -0
- package/dist/context/sl/source-files.js +131 -0
- package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
- package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
- package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
- package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
- package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
- package/dist/context/sql-analysis/dialect.d.ts +2 -0
- package/dist/context/sql-analysis/dialect.js +20 -0
- package/dist/context/tools/base-tool.d.ts +6 -19
- package/dist/context/tools/base-tool.js +0 -14
- package/dist/context-build-view.js +5 -5
- package/dist/database-tree-picker.js +18 -3
- package/dist/demo-assets.js +0 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -23
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +44 -0
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +8 -2
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/io/tty.d.ts +17 -0
- package/dist/io/tty.js +21 -0
- package/dist/links.d.ts +1 -0
- package/dist/links.js +1 -0
- package/dist/llm/embedding-health.js +1 -1
- package/dist/llm/embedding-provider.js +3 -3
- package/dist/llm/model-provider.js +1 -1
- package/dist/local-adapters.d.ts +1 -0
- package/dist/local-adapters.js +2 -2
- package/dist/local-scan-connectors.js +1 -1
- package/dist/managed-local-embeddings.js +17 -8
- package/dist/managed-mcp-daemon.js +3 -3
- package/dist/managed-python-command.d.ts +7 -0
- package/dist/managed-python-command.js +34 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-runtime.d.ts +30 -1
- package/dist/managed-python-runtime.js +134 -18
- package/dist/managed-uv-release.d.ts +7 -0
- package/dist/managed-uv-release.js +11 -0
- package/dist/mcp-http-server.js +4 -4
- package/dist/mcp-server-factory.js +3 -3
- package/dist/mcp-stdio-server.js +1 -1
- package/dist/memory-flow-hud.js +2 -2
- package/dist/next-steps.js +2 -2
- package/dist/prompt-navigation.d.ts +17 -0
- package/dist/prompt-navigation.js +49 -3
- package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
- package/dist/prompts/memory_agent_external_ingest.md +2 -2
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.js +3 -3
- package/dist/release-version.js +1 -1
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +9 -9
- package/dist/scan.js +1 -1
- package/dist/setup-agents.js +22 -35
- package/dist/setup-banner.d.ts +20 -0
- package/dist/setup-banner.js +39 -0
- package/dist/setup-context.js +24 -15
- package/dist/setup-databases.js +31 -59
- package/dist/setup-demo-tour.js +12 -8
- package/dist/setup-embeddings.js +9 -9
- package/dist/setup-interrupt.js +1 -1
- package/dist/setup-models.d.ts +4 -1
- package/dist/setup-models.js +54 -28
- package/dist/setup-project.js +29 -5
- package/dist/setup-prompts.js +16 -5
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +27 -7
- package/dist/setup.d.ts +25 -0
- package/dist/setup.js +90 -19
- package/dist/skills/analytics/SKILL.md +3 -3
- package/dist/skills/dbt_ingest/SKILL.md +3 -3
- package/dist/skills/looker_ingest/SKILL.md +3 -3
- package/dist/skills/lookml_ingest/SKILL.md +7 -7
- package/dist/skills/metabase_ingest/SKILL.md +4 -4
- package/dist/skills/metricflow_ingest/SKILL.md +15 -15
- package/dist/skills/notion_synthesize/SKILL.md +1 -1
- package/dist/skills/sl/SKILL.md +3 -3
- package/dist/skills/sl_capture/SKILL.md +1 -1
- package/dist/skills/wiki_capture/SKILL.md +1 -1
- package/dist/source-mapping.js +1 -1
- package/dist/startup-profile.js +1 -1
- package/dist/status-project.d.ts +0 -2
- package/dist/status-project.js +4 -6
- package/dist/telemetry/command-hook.d.ts +24 -0
- package/dist/telemetry/command-hook.js +37 -3
- package/dist/telemetry/events.d.ts +1 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/telemetry/index.d.ts +2 -2
- package/dist/telemetry/index.js +2 -2
- package/dist/text-ingest.js +1 -1
- package/dist/tree-picker-tui.d.ts +0 -1
- package/dist/tree-picker-tui.js +2 -3
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@ function parseWarning(rawWarning, path) {
|
|
|
25
25
|
!scanWarningCodes.has(rawWarning.code) ||
|
|
26
26
|
typeof rawWarning.message !== 'string' ||
|
|
27
27
|
typeof rawWarning.recoverable !== 'boolean') {
|
|
28
|
-
throw new Error(`Invalid
|
|
28
|
+
throw new Error(`Invalid ktx schema warning artifact: ${path}`);
|
|
29
29
|
}
|
|
30
30
|
return {
|
|
31
31
|
code: rawWarning.code,
|
|
@@ -42,7 +42,7 @@ async function readWarnings(input) {
|
|
|
42
42
|
const warningRaw = await input.project.fileStore.readFile(path);
|
|
43
43
|
const parsed = JSON.parse(warningRaw.content);
|
|
44
44
|
if (!isRecord(parsed) || !Array.isArray(parsed.warnings)) {
|
|
45
|
-
throw new Error(`Invalid
|
|
45
|
+
throw new Error(`Invalid ktx schema warnings artifact: ${path}`);
|
|
46
46
|
}
|
|
47
47
|
return parsed.warnings.map((warning) => parseWarning(warning, path));
|
|
48
48
|
}
|
|
@@ -68,7 +68,7 @@ function parseColumn(rawColumn, path) {
|
|
|
68
68
|
rawColumn.dimensionType !== 'string' &&
|
|
69
69
|
rawColumn.dimensionType !== 'number' &&
|
|
70
70
|
rawColumn.dimensionType !== 'boolean')) {
|
|
71
|
-
throw new Error(`Invalid
|
|
71
|
+
throw new Error(`Invalid ktx schema column artifact: ${path}`);
|
|
72
72
|
}
|
|
73
73
|
return {
|
|
74
74
|
name: rawColumn.name,
|
|
@@ -85,7 +85,7 @@ function parseForeignKey(rawForeignKey, path) {
|
|
|
85
85
|
typeof rawForeignKey.fromColumn !== 'string' ||
|
|
86
86
|
typeof rawForeignKey.toTable !== 'string' ||
|
|
87
87
|
typeof rawForeignKey.toColumn !== 'string') {
|
|
88
|
-
throw new Error(`Invalid
|
|
88
|
+
throw new Error(`Invalid ktx schema foreign key artifact: ${path}`);
|
|
89
89
|
}
|
|
90
90
|
return {
|
|
91
91
|
fromColumn: rawForeignKey.fromColumn,
|
|
@@ -99,7 +99,7 @@ function parseForeignKey(rawForeignKey, path) {
|
|
|
99
99
|
function parseTable(raw, path) {
|
|
100
100
|
const parsed = JSON.parse(raw);
|
|
101
101
|
if (!isRecord(parsed) || typeof parsed.name !== 'string' || !Array.isArray(parsed.columns)) {
|
|
102
|
-
throw new Error(`Invalid
|
|
102
|
+
throw new Error(`Invalid ktx schema table artifact: ${path}`);
|
|
103
103
|
}
|
|
104
104
|
return {
|
|
105
105
|
catalog: optionalStringOrNull(parsed.catalog) ?? null,
|
|
@@ -211,7 +211,7 @@ function compositeSkipBlocks(report) {
|
|
|
211
211
|
}
|
|
212
212
|
export function formatKtxRelationshipBenchmarkReportMarkdown(report) {
|
|
213
213
|
const lines = [
|
|
214
|
-
'#
|
|
214
|
+
'# ktx Relationship Discovery Benchmark Evidence',
|
|
215
215
|
'',
|
|
216
216
|
`Generated: ${report.generatedAt}`,
|
|
217
217
|
'',
|
|
@@ -87,7 +87,7 @@ async function detectCompositeRelationships(input) {
|
|
|
87
87
|
catch (error) {
|
|
88
88
|
input.warnings.push({
|
|
89
89
|
code: 'relationship_validation_failed',
|
|
90
|
-
message: `
|
|
90
|
+
message: `ktx composite relationship detection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
91
91
|
recoverable: true,
|
|
92
92
|
metadata: { source: 'composite_relationship_detection' },
|
|
93
93
|
});
|
|
@@ -110,7 +110,7 @@ function sqlExecutor(input) {
|
|
|
110
110
|
warnings: [
|
|
111
111
|
{
|
|
112
112
|
code: 'connector_capability_missing',
|
|
113
|
-
message: '
|
|
113
|
+
message: 'ktx scan connector cannot run read-only SQL relationship validation',
|
|
114
114
|
recoverable: true,
|
|
115
115
|
metadata: { capability: 'readOnlySql' },
|
|
116
116
|
},
|
|
@@ -123,7 +123,7 @@ function sqlExecutor(input) {
|
|
|
123
123
|
warnings: [
|
|
124
124
|
{
|
|
125
125
|
code: 'relationship_validation_failed',
|
|
126
|
-
message: '
|
|
126
|
+
message: 'ktx scan connector advertises readOnlySql but does not expose executeReadOnly',
|
|
127
127
|
recoverable: true,
|
|
128
128
|
metadata: { capability: 'readOnlySql' },
|
|
129
129
|
},
|
|
@@ -116,7 +116,7 @@ function mapValidProposals(schema, output, settings) {
|
|
|
116
116
|
const fromColumn = fromTable ? findColumn(fromTable, item.fromColumn) : null;
|
|
117
117
|
const toColumn = toTable ? findColumn(toTable, item.toColumn) : null;
|
|
118
118
|
if (!fromTable || !toTable || !fromColumn || !toColumn) {
|
|
119
|
-
warnings.push(invalidReferenceWarning('
|
|
119
|
+
warnings.push(invalidReferenceWarning('ktx relationship LLM proposal referenced a table or column that is not in the schema.', {
|
|
120
120
|
proposal: item,
|
|
121
121
|
}));
|
|
122
122
|
continue;
|
|
@@ -148,7 +148,7 @@ function generationFailureWarning(error) {
|
|
|
148
148
|
const message = error instanceof Error ? error.message : String(error);
|
|
149
149
|
return {
|
|
150
150
|
code: 'relationship_llm_proposal_failed',
|
|
151
|
-
message: `
|
|
151
|
+
message: `ktx relationship LLM proposal failed: ${message}`,
|
|
152
152
|
recoverable: true,
|
|
153
153
|
};
|
|
154
154
|
}
|
|
@@ -159,7 +159,7 @@ export async function proposeKtxRelationshipCandidatesWithLlm(input) {
|
|
|
159
159
|
const settings = mergeSettings(input.settings);
|
|
160
160
|
const evidence = buildEvidencePacket(input.schema, input.profile, settings);
|
|
161
161
|
const system = [
|
|
162
|
-
'You are helping
|
|
162
|
+
'You are helping ktx review possible SQL relationships before validation.',
|
|
163
163
|
'Use only the compact schema evidence. Propose likely primary keys and foreign keys for later SQL validation.',
|
|
164
164
|
'Return structured output only; never assume a join is accepted.',
|
|
165
165
|
].join('\n');
|
|
@@ -1,38 +1,8 @@
|
|
|
1
|
+
import { sqlAnalysisDialectForDriver } from '../sql-analysis/dialect.js';
|
|
1
2
|
import { loadLocalSlSourceRecords } from './local-sl.js';
|
|
2
3
|
import { toResolvedWire } from './semantic-layer.service.js';
|
|
4
|
+
import { assertSafeConnectionId } from './source-files.js';
|
|
3
5
|
const COMPILE_ONLY_REASON = 'Local semantic-layer query compiled SQL but no data-source execution adapter is configured.';
|
|
4
|
-
function assertSafePathToken(kind, value) {
|
|
5
|
-
if (value.trim().length === 0 ||
|
|
6
|
-
value.includes('..') ||
|
|
7
|
-
value.includes('\\') ||
|
|
8
|
-
value.startsWith('/') ||
|
|
9
|
-
value.startsWith('.') ||
|
|
10
|
-
value.includes('//')) {
|
|
11
|
-
throw new Error(`Unsafe ${kind}: ${value}`);
|
|
12
|
-
}
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
function assertSafeConnectionId(connectionId) {
|
|
16
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
|
|
17
|
-
throw new Error(`Unsafe connection id: ${connectionId}`);
|
|
18
|
-
}
|
|
19
|
-
return assertSafePathToken('connection id', connectionId);
|
|
20
|
-
}
|
|
21
|
-
function dialectForDriver(driver) {
|
|
22
|
-
const normalized = (driver ?? 'postgres').toUpperCase();
|
|
23
|
-
const map = {
|
|
24
|
-
POSTGRES: 'postgres',
|
|
25
|
-
BIGQUERY: 'bigquery',
|
|
26
|
-
SNOWFLAKE: 'snowflake',
|
|
27
|
-
MYSQL: 'mysql',
|
|
28
|
-
SQLSERVER: 'tsql',
|
|
29
|
-
SQLITE: 'sqlite',
|
|
30
|
-
DUCKDB: 'duckdb',
|
|
31
|
-
CLICKHOUSE: 'clickhouse',
|
|
32
|
-
DATABRICKS: 'databricks',
|
|
33
|
-
};
|
|
34
|
-
return map[normalized] ?? 'postgres';
|
|
35
|
-
}
|
|
36
6
|
function resolveLocalConnectionId(project, requested) {
|
|
37
7
|
if (requested) {
|
|
38
8
|
return assertSafeConnectionId(requested);
|
|
@@ -56,7 +26,7 @@ function headersFromColumns(columns) {
|
|
|
56
26
|
export async function compileLocalSlQuery(project, options) {
|
|
57
27
|
await options.onProgress?.({ progress: 0, message: 'Compiling query' });
|
|
58
28
|
const connectionId = resolveLocalConnectionId(project, options.connectionId);
|
|
59
|
-
const dialect =
|
|
29
|
+
const dialect = sqlAnalysisDialectForDriver(project.config.connections[connectionId]?.driver);
|
|
60
30
|
const sources = await loadComputableSources(project, connectionId);
|
|
61
31
|
await options.onProgress?.({ progress: 0.3, message: 'Generating SQL' });
|
|
62
32
|
const response = await options.compute.query({
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { KtxEmbeddingPort } from '../../context/core/embedding.js';
|
|
2
|
-
import type { KtxFileWriteResult } from '../../context/core/file-store.js';
|
|
3
2
|
import type { KtxLocalProject } from '../../context/project/project.js';
|
|
4
3
|
import type { PgliteSlSearchPrototypeOwnerOptions } from './pglite-sl-search-prototype.js';
|
|
5
4
|
import type { SemanticLayerSource, SlDictionaryMatch, SlSearchLaneSummary, SlSearchMatchReason } from './types.js';
|
|
@@ -56,13 +55,6 @@ export declare function validateLocalSlSource(rawYaml: string, options?: {
|
|
|
56
55
|
connectionId?: string;
|
|
57
56
|
sourceName?: string;
|
|
58
57
|
}): Promise<LocalSlValidationResult>;
|
|
59
|
-
/** @internal */
|
|
60
|
-
export declare function writeLocalSlSource(project: KtxLocalProject, input: {
|
|
61
|
-
connectionId: string;
|
|
62
|
-
sourceName: string;
|
|
63
|
-
yaml: string;
|
|
64
|
-
}): Promise<KtxFileWriteResult>;
|
|
65
|
-
/** @internal */
|
|
66
58
|
export declare function readLocalSlSource(project: KtxLocalProject, input: {
|
|
67
59
|
connectionId: string;
|
|
68
60
|
sourceName: string;
|
|
@@ -7,48 +7,12 @@ import { normalizeSemanticLayerDescriptions } from './description-normalization.
|
|
|
7
7
|
import { sourceDefinitionSchema, sourceOverlaySchema } from './schemas.js';
|
|
8
8
|
import { composeOverlay, projectManifestEntry, SemanticLayerService, toResolvedWire, } from './semantic-layer.service.js';
|
|
9
9
|
import { loadLatestSlDictionaryEntries } from './sl-dictionary-profile.js';
|
|
10
|
+
import { assertSafeConnectionId, isSafeConnectionId, isSlYamlPath, slSourceNameForFile, sourceNameFromPath, } from './source-files.js';
|
|
10
11
|
import { buildSemanticLayerSourceSearchText, SlSearchService } from './sl-search.service.js';
|
|
11
12
|
import { SqliteSlSourcesIndex } from './sqlite-sl-sources-index.js';
|
|
12
|
-
const LOCAL_AUTHOR = 'ktx';
|
|
13
|
-
const LOCAL_AUTHOR_EMAIL = 'ktx@example.com';
|
|
14
|
-
function assertSafePathToken(kind, value) {
|
|
15
|
-
if (value.trim().length === 0 ||
|
|
16
|
-
value.includes('..') ||
|
|
17
|
-
value.includes('\\') ||
|
|
18
|
-
value.startsWith('/') ||
|
|
19
|
-
value.startsWith('.') ||
|
|
20
|
-
value.includes('//')) {
|
|
21
|
-
throw new Error(`Unsafe ${kind}: ${value}`);
|
|
22
|
-
}
|
|
23
|
-
return value;
|
|
24
|
-
}
|
|
25
|
-
function assertSafeConnectionId(connectionId) {
|
|
26
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
|
|
27
|
-
throw new Error(`Unsafe connection id: ${connectionId}`);
|
|
28
|
-
}
|
|
29
|
-
return assertSafePathToken('connection id', connectionId);
|
|
30
|
-
}
|
|
31
|
-
function isSafeConnectionId(connectionId) {
|
|
32
|
-
return typeof connectionId === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId);
|
|
33
|
-
}
|
|
34
|
-
function assertSafeSourceName(sourceName) {
|
|
35
|
-
if (!/^[a-z0-9][a-z0-9_]*$/.test(sourceName)) {
|
|
36
|
-
throw new Error(`Unsafe semantic-layer source name: ${sourceName}`);
|
|
37
|
-
}
|
|
38
|
-
return assertSafePathToken('semantic-layer source name', sourceName);
|
|
39
|
-
}
|
|
40
13
|
function isRecord(value) {
|
|
41
14
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
42
15
|
}
|
|
43
|
-
function slPath(connectionId, sourceName) {
|
|
44
|
-
return `semantic-layer/${assertSafeConnectionId(connectionId)}/${assertSafeSourceName(sourceName)}.yaml`;
|
|
45
|
-
}
|
|
46
|
-
function sourceNameFromPath(path) {
|
|
47
|
-
return (path
|
|
48
|
-
.split('/')
|
|
49
|
-
.at(-1)
|
|
50
|
-
?.replace(/\.ya?ml$/, '') ?? path);
|
|
51
|
-
}
|
|
52
16
|
function parseYamlRecord(raw) {
|
|
53
17
|
const parsed = YAML.parse(raw);
|
|
54
18
|
if (!isRecord(parsed)) {
|
|
@@ -126,11 +90,17 @@ export async function loadLocalSlSourceRecords(project, input) {
|
|
|
126
90
|
const dir = `semantic-layer/${connectionId}`;
|
|
127
91
|
const schemaDir = `${dir}/_schema`;
|
|
128
92
|
const listed = await project.fileStore.listFiles(dir);
|
|
129
|
-
const paths = listed.files.filter(
|
|
93
|
+
const paths = listed.files.filter(isSlYamlPath).sort();
|
|
130
94
|
const sources = new Map();
|
|
131
95
|
for (const path of paths.filter((file) => file.startsWith(`${schemaDir}/`))) {
|
|
132
96
|
const raw = await project.fileStore.readFile(path);
|
|
133
|
-
|
|
97
|
+
let tables;
|
|
98
|
+
try {
|
|
99
|
+
tables = manifestTables(parseYamlRecord(raw.content));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw new Error(`${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
}
|
|
134
104
|
if (!tables) {
|
|
135
105
|
continue;
|
|
136
106
|
}
|
|
@@ -146,7 +116,30 @@ export async function loadLocalSlSourceRecords(project, input) {
|
|
|
146
116
|
}
|
|
147
117
|
for (const path of paths.filter((file) => !file.startsWith(`${schemaDir}/`))) {
|
|
148
118
|
const raw = await project.fileStore.readFile(path);
|
|
149
|
-
|
|
119
|
+
let parsed;
|
|
120
|
+
try {
|
|
121
|
+
parsed = parseYamlRecord(raw.content);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// A source mid-edit (e.g. an agent saved half-written YAML) must not take
|
|
125
|
+
// down reads, listings, or search for its siblings. Key it by the same
|
|
126
|
+
// name the writer side uses (the intact top-level `name:`, recovered even
|
|
127
|
+
// when the YAML is broken below it; filename only as a last resort) so a
|
|
128
|
+
// broken uppercase/hashed/human-renamed source stays reachable under its
|
|
129
|
+
// real name, and surface the raw content for repair.
|
|
130
|
+
const brokenName = slSourceNameForFile(path, raw.content);
|
|
131
|
+
sources.set(brokenName, {
|
|
132
|
+
connectionId,
|
|
133
|
+
name: brokenName,
|
|
134
|
+
path,
|
|
135
|
+
columnCount: 0,
|
|
136
|
+
measureCount: 0,
|
|
137
|
+
joinCount: 0,
|
|
138
|
+
yaml: raw.content,
|
|
139
|
+
source: { name: brokenName, grain: [], columns: [], joins: [], measures: [] },
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
150
143
|
const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : sourceNameFromPath(path);
|
|
151
144
|
if (parsed.table || parsed.sql) {
|
|
152
145
|
const source = parsedStandaloneSource(parsed, name);
|
|
@@ -191,36 +184,18 @@ export async function validateLocalSlSource(rawYaml, options) {
|
|
|
191
184
|
return { valid: false, errors: validationErrors(error) };
|
|
192
185
|
}
|
|
193
186
|
}
|
|
194
|
-
/** @internal */
|
|
195
|
-
export async function writeLocalSlSource(project, input) {
|
|
196
|
-
const validation = await validateLocalSlSource(input.yaml, { project, connectionId: input.connectionId });
|
|
197
|
-
if (!validation.valid) {
|
|
198
|
-
throw new Error(`Invalid semantic-layer source: ${validation.errors.join('; ')}`);
|
|
199
|
-
}
|
|
200
|
-
const parsed = parseYamlRecord(input.yaml);
|
|
201
|
-
if (typeof parsed.name === 'string' && parsed.name !== input.sourceName) {
|
|
202
|
-
throw new Error(`Semantic-layer source name "${parsed.name}" does not match requested path "${input.sourceName}"`);
|
|
203
|
-
}
|
|
204
|
-
const path = slPath(input.connectionId, input.sourceName);
|
|
205
|
-
return project.fileStore.writeFile(path, input.yaml.endsWith('\n') ? input.yaml : `${input.yaml}\n`, LOCAL_AUTHOR, LOCAL_AUTHOR_EMAIL, `Write semantic-layer source: ${input.connectionId}/${input.sourceName}`);
|
|
206
|
-
}
|
|
207
|
-
/** @internal */
|
|
208
187
|
export async function readLocalSlSource(project, input) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
});
|
|
221
|
-
const record = records.find((source) => source.name === input.sourceName);
|
|
222
|
-
return record ? { ...record } : null;
|
|
223
|
-
}
|
|
188
|
+
// Source identity is the in-file `name:` (mirroring the warehouse identifier
|
|
189
|
+
// verbatim, e.g. Snowflake's uppercase `WIDGET_SALES`), never the filename. The
|
|
190
|
+
// record loader resolves standalone files, overlays, manifest-backed sources,
|
|
191
|
+
// and mid-edit files whose YAML no longer parses — so readers — `ktx sl read`,
|
|
192
|
+
// `ktx sl validate`, and the `sl_read_source` MCP tool — can surface broken
|
|
193
|
+
// content for repair instead of failing on it.
|
|
194
|
+
const records = await loadLocalSlSourceRecords(project, {
|
|
195
|
+
connectionId: input.connectionId,
|
|
196
|
+
});
|
|
197
|
+
const record = records.find((source) => source.name === input.sourceName);
|
|
198
|
+
return record ? { ...record } : null;
|
|
224
199
|
}
|
|
225
200
|
export async function resolveLocalSlSource(project, input) {
|
|
226
201
|
if (input.connectionId !== undefined) {
|
|
@@ -38,17 +38,23 @@ export declare class SemanticLayerService {
|
|
|
38
38
|
name: string;
|
|
39
39
|
connectionType: string;
|
|
40
40
|
}>>;
|
|
41
|
-
private
|
|
41
|
+
private resolveWritePath;
|
|
42
42
|
writeSource(connectionId: string, source: SemanticLayerSource, author: string, authorEmail: string, commitMessage?: string, options?: WriteSourceOptions & {
|
|
43
43
|
skipLock?: boolean;
|
|
44
44
|
}): Promise<{
|
|
45
|
+
path: string;
|
|
45
46
|
warnings: string[];
|
|
46
47
|
commitHash?: string | null;
|
|
47
48
|
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Raw standalone/overlay file for a source, resolved by its in-file `name:`.
|
|
51
|
+
* Returns null when no file declares the name (the source may still exist as
|
|
52
|
+
* a manifest entry under `_schema/`).
|
|
53
|
+
*/
|
|
48
54
|
readSourceFile(connectionId: string, sourceName: string): Promise<{
|
|
49
55
|
content: string;
|
|
50
56
|
path: string;
|
|
51
|
-
}>;
|
|
57
|
+
} | null>;
|
|
52
58
|
loadSource(connectionId: string, sourceName: string): Promise<SemanticLayerSource | null>;
|
|
53
59
|
loadAllSources(connectionId: string): Promise<LoadAllSourcesResult>;
|
|
54
60
|
/**
|
|
@@ -87,14 +93,8 @@ export declare class SemanticLayerService {
|
|
|
87
93
|
} | null>;
|
|
88
94
|
validatePhysicalTableReferences(connectionId: string, sources: SemanticLayerSource[]): Promise<string[]>;
|
|
89
95
|
getDialectForConnection(connectionId: string): Promise<string>;
|
|
90
|
-
listSourceNames(connectionId: string): Promise<string[]>;
|
|
91
96
|
listFilesForConnection(connectionId: string): Promise<string[]>;
|
|
92
|
-
readFileByPath(connectionId: string, relativePath: string): Promise<{
|
|
93
|
-
content: string;
|
|
94
|
-
readOnly: boolean;
|
|
95
|
-
}>;
|
|
96
97
|
deleteSource(connectionId: string, sourceName: string, author: string, authorEmail: string): Promise<import("../../context/core/file-store.js").KtxFileWriteResult | null>;
|
|
97
|
-
getSourceHistory(connectionId: string, sourceName: string): Promise<unknown>;
|
|
98
98
|
/**
|
|
99
99
|
* Validate the semantic layer state that *would* exist if `proposedSource`
|
|
100
100
|
* were written, without persisting anything. Used by write/edit tools to
|
|
@@ -203,6 +203,23 @@ export interface ManifestTableEntry {
|
|
|
203
203
|
usage?: TableUsageOutput;
|
|
204
204
|
}
|
|
205
205
|
export declare function projectManifestEntry(name: string, entry: ManifestTableEntry): SemanticLayerSource;
|
|
206
|
+
export interface MissingJoinTarget {
|
|
207
|
+
to: string;
|
|
208
|
+
/** Source whose name matches only case-insensitively, if any — the usual authoring mistake. */
|
|
209
|
+
caseMismatch: string | null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Join targets that do not exactly match a known source name. The Python
|
|
213
|
+
* engine resolves `joins[].to` by exact name within one connection's source
|
|
214
|
+
* set (`engine._collect_orphan_join_target_errors`) and `query()` raises on a
|
|
215
|
+
* miss, so anything looser here — case-insensitive matches, table refs,
|
|
216
|
+
* sources in other connections — would pass this gate and then fail
|
|
217
|
+
* query/validation as an orphan join target.
|
|
218
|
+
*/
|
|
219
|
+
export declare function findMissingJoinTargets(joins: Array<{
|
|
220
|
+
to: string;
|
|
221
|
+
}> | undefined, knownSourceNames: Iterable<string>): MissingJoinTarget[];
|
|
222
|
+
export declare function formatMissingJoinTarget(missing: MissingJoinTarget): string;
|
|
206
223
|
/**
|
|
207
224
|
* Returns one message per measure-level segment reference that doesn't resolve to
|
|
208
225
|
* a segment defined on the source. Array is empty when every reference checks out.
|