@kaelio/ktx 0.13.1 → 0.15.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.13.1-py3-none-any.whl → kaelio_ktx-0.15.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli-program.js +1 -1
- package/dist/commands/ingest-commands.d.ts +9 -0
- package/dist/commands/ingest-commands.js +37 -1
- package/dist/commands/knowledge-commands.js +3 -0
- package/dist/commands/setup-commands.js +15 -1
- package/dist/connection-drivers.d.ts +2 -0
- package/dist/connection-drivers.js +7 -3
- package/dist/connection.d.ts +3 -0
- package/dist/connection.js +27 -0
- package/dist/connectors/bigquery/connector.d.ts +17 -6
- package/dist/connectors/bigquery/connector.js +152 -76
- package/dist/connectors/bigquery/dialect.d.ts +2 -2
- package/dist/connectors/clickhouse/connector.d.ts +1 -0
- package/dist/connectors/clickhouse/connector.js +45 -16
- package/dist/connectors/clickhouse/dialect.d.ts +2 -2
- package/dist/connectors/mongodb/connector.d.ts +69 -0
- package/dist/connectors/mongodb/connector.js +307 -0
- package/dist/connectors/mongodb/dialect.d.ts +17 -0
- package/dist/connectors/mongodb/dialect.js +50 -0
- package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
- package/dist/connectors/mongodb/live-database-introspection.js +24 -0
- package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
- package/dist/connectors/mongodb/schema-inference.js +110 -0
- package/dist/connectors/mysql/connector.d.ts +1 -0
- package/dist/connectors/mysql/connector.js +18 -2
- package/dist/connectors/mysql/dialect.d.ts +2 -2
- package/dist/connectors/postgres/connector.d.ts +1 -0
- package/dist/connectors/postgres/connector.js +20 -3
- package/dist/connectors/postgres/dialect.d.ts +2 -2
- package/dist/connectors/snowflake/connector.d.ts +1 -0
- package/dist/connectors/snowflake/connector.js +27 -3
- package/dist/connectors/snowflake/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/connector.d.ts +11 -1
- package/dist/connectors/sqlite/connector.js +120 -17
- package/dist/connectors/sqlite/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
- package/dist/connectors/sqlite/read-query-child.js +23 -0
- package/dist/connectors/sqlserver/connector.d.ts +2 -0
- package/dist/connectors/sqlserver/connector.js +20 -3
- package/dist/connectors/sqlserver/dialect.d.ts +2 -2
- package/dist/context/cache/content-result-cache.d.ts +36 -0
- package/dist/context/cache/content-result-cache.js +14 -0
- package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
- package/dist/context/cache/sqlite-content-result-cache.js +223 -0
- package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
- package/dist/context/connections/bigquery-identifiers.js +7 -0
- package/dist/context/connections/configured-connections.d.ts +9 -0
- package/dist/context/connections/configured-connections.js +18 -0
- package/dist/context/connections/dialects.d.ts +30 -4
- package/dist/context/connections/dialects.js +38 -11
- package/dist/context/connections/drivers.js +21 -0
- package/dist/context/connections/gdrive-config.d.ts +19 -0
- package/dist/context/connections/gdrive-config.js +57 -0
- package/dist/context/connections/query-deadline.d.ts +31 -0
- package/dist/context/connections/query-deadline.js +37 -0
- package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
- package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
- package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
- package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
- package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
- package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
- package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
- package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
- package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
- package/dist/context/ingest/adapters/gdrive/types.js +36 -0
- package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
- package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
- package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
- package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
- package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
- package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
- package/dist/context/ingest/adapters/live-database/stage.js +5 -5
- package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/types.js +1 -1
- package/dist/context/ingest/adapters/sigma/chunk.d.ts +6 -0
- package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
- package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
- package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
- package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
- package/dist/context/ingest/adapters/sigma/client.js +176 -0
- package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
- package/dist/context/ingest/adapters/sigma/detect.js +23 -0
- package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
- package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
- package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
- package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
- package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
- package/dist/context/ingest/adapters/sigma/project.js +186 -0
- package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
- package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
- package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
- package/dist/context/ingest/adapters/sigma/types.js +82 -0
- package/dist/context/ingest/artifact-gates.d.ts +32 -1
- package/dist/context/ingest/artifact-gates.js +85 -18
- package/dist/context/ingest/final-gate-prune.d.ts +35 -0
- package/dist/context/ingest/final-gate-prune.js +229 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
- package/dist/context/ingest/ingest-bundle.runner.js +459 -240
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
- package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
- package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
- package/dist/context/ingest/local-adapters.d.ts +2 -1
- package/dist/context/ingest/local-adapters.js +28 -0
- package/dist/context/ingest/local-bundle-runtime.js +7 -2
- package/dist/context/ingest/local-ingest.js +1 -1
- package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
- package/dist/context/ingest/local-stage-ingest.js +3 -1
- package/dist/context/ingest/ports.d.ts +3 -0
- package/dist/context/ingest/report-snapshot.js +13 -3
- package/dist/context/ingest/reports.d.ts +3 -3
- package/dist/context/ingest/reports.js +3 -1
- package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
- package/dist/context/ingest/stages/build-wu-context.js +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
- package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
- package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
- package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
- package/dist/context/ingest/types.d.ts +3 -0
- package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
- package/dist/context/ingest/wiki-body-refs.js +37 -8
- package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
- package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
- package/dist/context/ingest/work-unit-cache.d.ts +67 -0
- package/dist/context/ingest/work-unit-cache.js +230 -0
- package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
- package/dist/context/llm/ai-sdk-runtime.js +5 -0
- package/dist/context/llm/claude-code-runtime.d.ts +4 -1
- package/dist/context/llm/claude-code-runtime.js +38 -8
- package/dist/context/llm/codex-runtime.d.ts +4 -1
- package/dist/context/llm/codex-runtime.js +48 -35
- package/dist/context/llm/runtime-port.d.ts +26 -0
- package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
- package/dist/context/llm/subprocess-generate-object-child.js +34 -0
- package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
- package/dist/context/llm/subprocess-generate-object.js +122 -0
- package/dist/context/mcp/context-tools.d.ts +2 -0
- package/dist/context/mcp/context-tools.js +111 -17
- package/dist/context/mcp/local-project-ports.d.ts +7 -0
- package/dist/context/mcp/local-project-ports.js +29 -0
- package/dist/context/mcp/logger.d.ts +24 -0
- package/dist/context/mcp/logger.js +49 -0
- package/dist/context/mcp/server.js +2 -0
- package/dist/context/mcp/types.d.ts +17 -0
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +72 -0
- package/dist/context/project/config.js +5 -0
- package/dist/context/project/driver-schemas.d.ts +35 -0
- package/dist/context/project/driver-schemas.js +89 -1
- package/dist/context/project/project.js +1 -1
- package/dist/context/project/setup-config.js +1 -0
- package/dist/context/scan/description-generation.d.ts +4 -0
- package/dist/context/scan/description-generation.js +100 -13
- package/dist/context/scan/enabled-tables.d.ts +5 -0
- package/dist/context/scan/enabled-tables.js +13 -1
- package/dist/context/scan/enrichment-state.d.ts +50 -7
- package/dist/context/scan/enrichment-state.js +30 -13
- package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
- package/dist/context/scan/local-enrichment-artifacts.js +155 -32
- package/dist/context/scan/local-enrichment.d.ts +39 -4
- package/dist/context/scan/local-enrichment.js +345 -92
- package/dist/context/scan/local-scan.d.ts +4 -1
- package/dist/context/scan/local-scan.js +54 -13
- package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
- package/dist/context/scan/local-structural-artifacts.js +5 -0
- package/dist/context/scan/object-introspection.d.ts +21 -0
- package/dist/context/scan/object-introspection.js +35 -0
- package/dist/context/scan/relationship-benchmarks.js +5 -3
- package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
- package/dist/context/scan/relationship-composite-candidates.js +13 -1
- package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
- package/dist/context/scan/relationship-detection-budget.js +53 -0
- package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
- package/dist/context/scan/relationship-diagnostics.js +2 -0
- package/dist/context/scan/relationship-discovery.d.ts +9 -3
- package/dist/context/scan/relationship-discovery.js +27 -4
- package/dist/context/scan/relationship-llm-proposal.js +36 -27
- package/dist/context/scan/relationship-profiling.d.ts +7 -3
- package/dist/context/scan/relationship-profiling.js +53 -41
- package/dist/context/scan/relationship-validation.d.ts +6 -4
- package/dist/context/scan/relationship-validation.js +46 -32
- package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
- package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
- package/dist/context/scan/types.d.ts +5 -3
- package/dist/context/scan/types.js +2 -0
- package/dist/context/sl/local-query.js +5 -0
- package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
- package/dist/context/sl/semantic-layer.service.js +1 -1
- package/dist/context/sl/source-files.d.ts +5 -0
- package/dist/context/sl/source-files.js +17 -11
- package/dist/context/sl/tools/connection-id-schema.js +1 -1
- package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
- package/dist/context/sql-analysis/dialect-notes.js +40 -0
- package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
- package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
- package/dist/context/sql-analysis/dialects/mysql.md +9 -0
- package/dist/context/sql-analysis/dialects/postgres.md +10 -0
- package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
- package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
- package/dist/context/sql-analysis/dialects/tsql.md +10 -0
- package/dist/context/wiki/keys.js +1 -1
- package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
- package/dist/context/wiki/knowledge-wiki.service.js +2 -1
- package/dist/context/wiki/local-knowledge.d.ts +13 -0
- package/dist/context/wiki/local-knowledge.js +50 -6
- package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
- package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
- package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
- package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
- package/dist/context/wiki/types.d.ts +6 -0
- package/dist/context-build-view.d.ts +3 -0
- package/dist/context-build-view.js +1 -0
- package/dist/knowledge.d.ts +2 -0
- package/dist/knowledge.js +12 -1
- package/dist/local-adapters.js +11 -0
- package/dist/mcp-http-server.js +15 -1
- package/dist/mcp-server-factory.d.ts +2 -0
- package/dist/mcp-server-factory.js +15 -1
- package/dist/mcp-stdio-server.js +10 -2
- package/dist/notion-page-picker.js +1 -1
- package/dist/prompts/memory_agent_external_ingest.md +2 -0
- package/dist/public-ingest.d.ts +3 -1
- package/dist/public-ingest.js +4 -0
- package/dist/scan.d.ts +3 -1
- package/dist/scan.js +7 -0
- package/dist/setup-databases.d.ts +1 -1
- package/dist/setup-databases.js +43 -1
- package/dist/setup-sources.d.ts +6 -1
- package/dist/setup-sources.js +208 -48
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +6 -1
- package/dist/skills/analytics/SKILL.md +200 -2
- package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
- package/dist/skills/sigma_ingest/SKILL.md +189 -0
- package/dist/skills/wiki_capture/SKILL.md +24 -0
- package/dist/sql.js +4 -0
- package/dist/status-project.d.ts +4 -0
- package/dist/status-project.js +54 -2
- package/dist/telemetry/events.d.ts +2 -2
- package/dist/text-ingest.d.ts +4 -0
- package/dist/text-ingest.js +58 -29
- package/dist/verbatim-ingest.d.ts +46 -0
- package/dist/verbatim-ingest.js +193 -0
- package/package.json +5 -1
- package/dist/context/ingest/final-gate-repair.d.ts +0 -28
- package/dist/context/ingest/final-gate-repair.js +0 -91
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import pLimit from 'p-limit';
|
|
2
|
-
import {
|
|
2
|
+
import { getSqlDialectForDriver } from '../connections/dialects.js';
|
|
3
3
|
import { buildDefaultKtxProjectConfig } from '../project/config.js';
|
|
4
4
|
import { KtxDescriptionGenerator } from './description-generation.js';
|
|
5
5
|
import { buildKtxColumnEmbeddingText } from './embedding-text.js';
|
|
6
|
-
import { completedKtxScanEnrichmentStateSummary,
|
|
6
|
+
import { completedKtxScanEnrichmentStateSummary, computeKtxDescriptionsStageHash, computeKtxEmbeddingsStageHash, computeKtxRelationshipsStageHash, computeKtxScanDescriptionDigest, KTX_SCAN_ENRICHMENT_STAGES, summarizeKtxScanEnrichmentState, } from './enrichment-state.js';
|
|
7
7
|
import { skippedKtxScanEnrichmentSummary } from './enrichment-summary.js';
|
|
8
|
+
import { tableRefKey } from './table-ref.js';
|
|
8
9
|
import { discoverKtxRelationships } from './relationship-discovery.js';
|
|
9
|
-
|
|
10
|
+
// Parallel per-table description generations. Default 4; raise via
|
|
11
|
+
// KTX_ENRICH_TABLE_CONCURRENCY for large schemas (the rate-limit governor still
|
|
12
|
+
// throttles if the provider pushes back, so a higher cap is safe headroom).
|
|
13
|
+
const DESCRIPTION_TABLE_CONCURRENCY = (() => {
|
|
14
|
+
const raw = Number(process.env.KTX_ENRICH_TABLE_CONCURRENCY);
|
|
15
|
+
return Number.isInteger(raw) && raw >= 1 && raw <= 64 ? raw : 4;
|
|
16
|
+
})();
|
|
10
17
|
function tableId(table) {
|
|
11
18
|
return [table.catalog, table.db, table.name].filter((value) => Boolean(value)).join('.');
|
|
12
19
|
}
|
|
@@ -82,6 +89,16 @@ function providerlessEnrichedWarning(relationshipDetection) {
|
|
|
82
89
|
},
|
|
83
90
|
};
|
|
84
91
|
}
|
|
92
|
+
function stagePrerequisiteReason(stage) {
|
|
93
|
+
switch (stage) {
|
|
94
|
+
case 'descriptions':
|
|
95
|
+
return 'LLM enrichment is not configured (set scan.enrichment.mode and an LLM provider)';
|
|
96
|
+
case 'embeddings':
|
|
97
|
+
return 'no embedding provider is configured (set scan.enrichment.embeddings)';
|
|
98
|
+
case 'relationships':
|
|
99
|
+
return 'relationship discovery is disabled (scan.relationships.enabled is false)';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
85
102
|
export function createDeterministicLocalScanEnrichmentProviders() {
|
|
86
103
|
return {
|
|
87
104
|
llmRuntime: deterministicLlmRuntime(),
|
|
@@ -108,14 +125,20 @@ function deterministicLlmRuntime() {
|
|
|
108
125
|
async runAgentLoop() {
|
|
109
126
|
return { stopReason: 'natural' };
|
|
110
127
|
},
|
|
128
|
+
subprocessForkSpec() {
|
|
129
|
+
return null;
|
|
130
|
+
},
|
|
111
131
|
};
|
|
112
132
|
}
|
|
113
|
-
export function snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId = new Map()) {
|
|
133
|
+
export function snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId = new Map(), descriptions = []) {
|
|
134
|
+
const descriptionByTable = new Map(descriptions.map((item) => [tableRefKey(item.table), item]));
|
|
114
135
|
const tables = snapshot.tables.map((table) => {
|
|
115
136
|
const id = tableId(table);
|
|
116
137
|
const ref = tableRef(table);
|
|
138
|
+
const tableDescription = descriptionByTable.get(tableRefKey(ref));
|
|
117
139
|
const columns = table.columns.map((column) => {
|
|
118
140
|
const idForColumn = columnId(table, column);
|
|
141
|
+
const aiColumnDescription = tableDescription?.columnDescriptions[column.name] ?? null;
|
|
119
142
|
return {
|
|
120
143
|
id: idForColumn,
|
|
121
144
|
tableId: id,
|
|
@@ -129,6 +152,7 @@ export function snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId = new
|
|
|
129
152
|
parentColumnId: null,
|
|
130
153
|
descriptions: {
|
|
131
154
|
...(column.comment ? { db: column.comment } : {}),
|
|
155
|
+
...(aiColumnDescription ? { ai: aiColumnDescription } : {}),
|
|
132
156
|
},
|
|
133
157
|
embedding: embeddingsByColumnId.get(idForColumn) ?? null,
|
|
134
158
|
sampleValues: null,
|
|
@@ -141,6 +165,7 @@ export function snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId = new
|
|
|
141
165
|
enabled: true,
|
|
142
166
|
descriptions: {
|
|
143
167
|
...(table.comment ? { db: table.comment } : {}),
|
|
168
|
+
...(tableDescription?.tableDescription ? { ai: tableDescription.tableDescription } : {}),
|
|
144
169
|
},
|
|
145
170
|
columns,
|
|
146
171
|
};
|
|
@@ -154,6 +179,19 @@ export function snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId = new
|
|
|
154
179
|
function embeddingBatchSize(maxBatchSize) {
|
|
155
180
|
return Number.isInteger(maxBatchSize) && maxBatchSize > 0 ? maxBatchSize : 100;
|
|
156
181
|
}
|
|
182
|
+
// Per-batch flush cadence: bounds the at-risk window (and the manifest-rewrite /
|
|
183
|
+
// git-commit cost) to a small number of tables.
|
|
184
|
+
const DESCRIPTION_FLUSH_EVERY = 10;
|
|
185
|
+
function isEnrichedDescriptionUpdate(update) {
|
|
186
|
+
return update.tableDescription !== null || Object.values(update.columnDescriptions).some((value) => value !== null);
|
|
187
|
+
}
|
|
188
|
+
function nullDescriptionUpdate(table) {
|
|
189
|
+
return {
|
|
190
|
+
table: tableRef(table),
|
|
191
|
+
tableDescription: null,
|
|
192
|
+
columnDescriptions: Object.fromEntries(table.columns.map((column) => [column.name, null])),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
157
195
|
async function generateDescriptions(input) {
|
|
158
196
|
const warningSink = input.warnings;
|
|
159
197
|
const generator = new KtxDescriptionGenerator({
|
|
@@ -173,52 +211,124 @@ async function generateDescriptions(input) {
|
|
|
173
211
|
concurrencyLimit: 4,
|
|
174
212
|
},
|
|
175
213
|
});
|
|
176
|
-
const updates = [];
|
|
177
214
|
const totalTables = input.snapshot.tables.length;
|
|
178
215
|
if (totalTables === 0) {
|
|
179
216
|
await input.progress?.update(1, 'No tables to describe');
|
|
180
|
-
return
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
// Resume: recover already-enriched tables (inputHash-gated) and re-issue LLM
|
|
220
|
+
// calls only for the remainder. A failed/skipped table carries null descriptions
|
|
221
|
+
// and is not recovered, so it is retried.
|
|
222
|
+
const recovered = input.resumeStore ? ((await input.resumeStore.load(input.inputHash)) ?? []) : [];
|
|
223
|
+
const enrichedById = new Map();
|
|
224
|
+
for (const update of recovered) {
|
|
225
|
+
if (isEnrichedDescriptionUpdate(update)) {
|
|
226
|
+
enrichedById.set(tableRefKey(update.table), update);
|
|
227
|
+
}
|
|
181
228
|
}
|
|
229
|
+
const remaining = input.snapshot.tables.filter((table) => !enrichedById.has(tableRefKey(tableRef(table))));
|
|
230
|
+
const recoveredCount = enrichedById.size;
|
|
231
|
+
if (recoveredCount > 0) {
|
|
232
|
+
input.context.logger?.info(`[enrich] resume: recovered ${recoveredCount}/${totalTables} descriptions, enriching ${remaining.length}`);
|
|
233
|
+
}
|
|
234
|
+
const pendingChanged = new Set();
|
|
235
|
+
let sinceFlush = 0;
|
|
236
|
+
let flushing = false;
|
|
237
|
+
const flush = async (force) => {
|
|
238
|
+
if (!input.resumeStore || flushing || pendingChanged.size === 0) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!force && sinceFlush < DESCRIPTION_FLUSH_EVERY) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
flushing = true;
|
|
245
|
+
const changedTableNames = new Set(pendingChanged);
|
|
246
|
+
pendingChanged.clear();
|
|
247
|
+
sinceFlush = 0;
|
|
248
|
+
try {
|
|
249
|
+
await input.resumeStore.flush({
|
|
250
|
+
inputHash: input.inputHash,
|
|
251
|
+
snapshot: input.snapshot,
|
|
252
|
+
descriptionUpdates: [...enrichedById.values()],
|
|
253
|
+
changedTableNames,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
flushing = false;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
182
260
|
const limitTable = pLimit(DESCRIPTION_TABLE_CONCURRENCY);
|
|
183
|
-
|
|
184
|
-
await input.progress?.update((index + 1) / totalTables, `Generating descriptions ${index + 1}/${totalTables}
|
|
261
|
+
await Promise.all(remaining.map((table, index) => limitTable(async () => {
|
|
262
|
+
await input.progress?.update((recoveredCount + index + 1) / totalTables, `Generating descriptions ${recoveredCount + index + 1}/${totalTables} (${table.name}, ${table.columns.length} cols)`, {
|
|
185
263
|
transient: true,
|
|
186
264
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
265
|
+
// Stage-level guarantee: a single table's failure costs one missing
|
|
266
|
+
// description, never the whole stage's output. (generateBatchedTableDescriptions
|
|
267
|
+
// already degrades its own failures to null descriptions; this backstop keeps
|
|
268
|
+
// the guarantee at the fan-out even if a future path throws.) A genuine
|
|
269
|
+
// cancellation still propagates so the stage fails and resumes.
|
|
270
|
+
let update;
|
|
271
|
+
try {
|
|
272
|
+
const batched = await generator.generateBatchedTableDescriptions({
|
|
273
|
+
connectionId: input.snapshot.connectionId,
|
|
274
|
+
connector: input.connector,
|
|
275
|
+
context: input.context,
|
|
276
|
+
dataSourceType: input.snapshot.driver,
|
|
277
|
+
supportsNestedAnalysis: input.connector.capabilities.nestedAnalysis,
|
|
278
|
+
table: {
|
|
279
|
+
catalog: table.catalog,
|
|
280
|
+
db: table.db,
|
|
281
|
+
name: table.name,
|
|
282
|
+
rawDescriptions: table.comment ? { db: table.comment } : {},
|
|
283
|
+
columns: table.columns.map((column) => ({
|
|
284
|
+
name: column.name,
|
|
285
|
+
type: column.nativeType,
|
|
286
|
+
...(column.comment ? { rawDescriptions: { db: column.comment } } : {}),
|
|
287
|
+
})),
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
update = {
|
|
291
|
+
table: tableRef(table),
|
|
292
|
+
tableDescription: batched.tableDescription,
|
|
293
|
+
columnDescriptions: Object.fromEntries(batched.columnDescriptions),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
if (input.context.signal?.aborted) {
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
301
|
+
input.context.logger?.warn(`[enrich] table ${table.name} failed: ${message}`);
|
|
302
|
+
warningSink?.push({
|
|
303
|
+
code: 'enrichment_failed',
|
|
304
|
+
message: `Failed to generate description for ${table.name}: ${message}`,
|
|
305
|
+
table: table.name,
|
|
306
|
+
recoverable: true,
|
|
307
|
+
metadata: {},
|
|
308
|
+
});
|
|
309
|
+
update = nullDescriptionUpdate(table);
|
|
310
|
+
}
|
|
311
|
+
if (isEnrichedDescriptionUpdate(update)) {
|
|
312
|
+
enrichedById.set(tableRefKey(tableRef(table)), update);
|
|
313
|
+
pendingChanged.add(table.name);
|
|
314
|
+
sinceFlush += 1;
|
|
315
|
+
await flush(false);
|
|
316
|
+
}
|
|
210
317
|
})));
|
|
211
|
-
|
|
318
|
+
await flush(true);
|
|
212
319
|
await input.progress?.update(1, `Generated descriptions for ${totalTables} tables`);
|
|
213
|
-
|
|
320
|
+
// Full set in snapshot order: recovered + freshly enriched, null for any still-failed.
|
|
321
|
+
return input.snapshot.tables.map((table) => enrichedById.get(tableRefKey(tableRef(table))) ?? nullDescriptionUpdate(table));
|
|
214
322
|
}
|
|
215
|
-
|
|
216
|
-
|
|
323
|
+
// The exact per-column text fed to the embedding model. Shared by the embeddings
|
|
324
|
+
// stage and the descriptionDigest so the embeddings hash content-addresses the
|
|
325
|
+
// real text the model sees (D4).
|
|
326
|
+
function buildKtxColumnEmbeddingTexts(snapshot, descriptions) {
|
|
327
|
+
const descriptionByTable = new Map(descriptions.map((item) => [tableRefKey(item.table), item]));
|
|
217
328
|
const texts = [];
|
|
218
|
-
for (const table of
|
|
219
|
-
const tableDescriptions = descriptionByTable.get(table
|
|
329
|
+
for (const table of snapshot.tables) {
|
|
330
|
+
const tableDescriptions = descriptionByTable.get(tableRefKey(tableRef(table)));
|
|
220
331
|
for (const column of table.columns) {
|
|
221
|
-
const id = columnId(table, column);
|
|
222
332
|
const text = buildKtxColumnEmbeddingText({
|
|
223
333
|
tableName: table.name,
|
|
224
334
|
columnName: column.name,
|
|
@@ -233,9 +343,13 @@ async function buildEmbeddings(input) {
|
|
|
233
343
|
incoming: [],
|
|
234
344
|
},
|
|
235
345
|
});
|
|
236
|
-
texts.push({ columnId:
|
|
346
|
+
texts.push({ columnId: columnId(table, column), text });
|
|
237
347
|
}
|
|
238
348
|
}
|
|
349
|
+
return texts;
|
|
350
|
+
}
|
|
351
|
+
async function buildEmbeddings(input) {
|
|
352
|
+
const texts = input.texts;
|
|
239
353
|
const embeddings = [];
|
|
240
354
|
const maxBatchSize = embeddingBatchSize(input.embedding.maxBatchSize);
|
|
241
355
|
const embeddingTexts = texts.map((item) => item.text);
|
|
@@ -271,15 +385,17 @@ async function buildEmbeddings(input) {
|
|
|
271
385
|
return { updates, byColumnId };
|
|
272
386
|
}
|
|
273
387
|
async function runEnrichmentStage(input) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
388
|
+
if (!input.forceRecompute) {
|
|
389
|
+
const existing = await input.stateStore?.findCompletedStage({
|
|
390
|
+
connectionId: input.connectionId,
|
|
391
|
+
stage: input.stage,
|
|
392
|
+
inputHash: input.inputHash,
|
|
393
|
+
});
|
|
394
|
+
if (existing) {
|
|
395
|
+
input.resumedStages.push(input.stage);
|
|
396
|
+
input.completedStages.push(input.stage);
|
|
397
|
+
return existing.output;
|
|
398
|
+
}
|
|
283
399
|
}
|
|
284
400
|
try {
|
|
285
401
|
const output = await input.compute();
|
|
@@ -330,22 +446,42 @@ export async function runLocalScanEnrichment(input) {
|
|
|
330
446
|
snapshot,
|
|
331
447
|
connectionId: input.connectionId,
|
|
332
448
|
});
|
|
333
|
-
const dialect =
|
|
449
|
+
const dialect = input.connector.capabilities.readOnlySql
|
|
450
|
+
? getSqlDialectForDriver(snapshot.driver)
|
|
451
|
+
: null;
|
|
334
452
|
const now = input.now ?? (() => new Date());
|
|
335
453
|
const state = completedKtxScanEnrichmentStateSummary();
|
|
336
454
|
const syncId = input.syncId ?? input.context.runId;
|
|
337
455
|
const relationshipSettings = input.relationshipSettings ?? buildDefaultKtxProjectConfig().scan.relationships;
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
});
|
|
456
|
+
const llmIdentity = input.llmIdentity ?? { model: null, baseUrlConfigured: false };
|
|
457
|
+
const embeddingIdentity = input.embeddingIdentity ?? {
|
|
458
|
+
model: null,
|
|
459
|
+
dimensions: null,
|
|
460
|
+
batchSize: null,
|
|
461
|
+
};
|
|
462
|
+
const descriptionsHash = computeKtxDescriptionsStageHash({ snapshot, llmIdentity });
|
|
463
|
+
const relationshipsHash = computeKtxRelationshipsStageHash({ snapshot, relationshipSettings, llmIdentity });
|
|
345
464
|
const warnings = [];
|
|
465
|
+
const selectedStages = input.stages;
|
|
466
|
+
const runsStage = (stage) => selectedStages === undefined || selectedStages.includes(stage);
|
|
467
|
+
const forcesStage = (stage) => selectedStages !== undefined && selectedStages.includes(stage);
|
|
346
468
|
let descriptions = [];
|
|
469
|
+
let descriptionsRanThisInvocation = false;
|
|
470
|
+
let priorDescriptions;
|
|
471
|
+
// Best-available descriptions for the downstream stages (embeddings,
|
|
472
|
+
// relationships): fresh ones when descriptions ran this invocation, else the
|
|
473
|
+
// descriptions persisted in the on-disk _schema. Behavior follows the input
|
|
474
|
+
// (did descriptions run?), not which stage subset the caller selected (D5).
|
|
475
|
+
const resolveDownstreamDescriptions = async () => {
|
|
476
|
+
if (descriptionsRanThisInvocation) {
|
|
477
|
+
return descriptions;
|
|
478
|
+
}
|
|
479
|
+
if (priorDescriptions === undefined) {
|
|
480
|
+
priorDescriptions = input.loadPriorDescriptions ? await input.loadPriorDescriptions(snapshot) : null;
|
|
481
|
+
}
|
|
482
|
+
return priorDescriptions ?? [];
|
|
483
|
+
};
|
|
347
484
|
let embeddingUpdates = [];
|
|
348
|
-
let schema = snapshotToKtxEnrichedSchema(snapshot);
|
|
349
485
|
const summary = { ...skippedKtxScanEnrichmentSummary };
|
|
350
486
|
const relationshipDetectionEnabled = relationshipSettings.enabled;
|
|
351
487
|
const shouldDetectRelationships = relationshipDetectionEnabled &&
|
|
@@ -353,36 +489,67 @@ export async function runLocalScanEnrichment(input) {
|
|
|
353
489
|
if (input.mode === 'enriched' && !input.providers) {
|
|
354
490
|
warnings.push(providerlessEnrichedWarning(shouldDetectRelationships));
|
|
355
491
|
}
|
|
492
|
+
// A stage explicitly named in --stages whose prerequisite is missing must be
|
|
493
|
+
// surfaced, never silently no-op (D2).
|
|
494
|
+
if (selectedStages !== undefined) {
|
|
495
|
+
const stageEligible = {
|
|
496
|
+
descriptions: input.mode === 'enriched' && input.providers != null,
|
|
497
|
+
embeddings: input.mode === 'enriched' && input.providers?.embedding != null,
|
|
498
|
+
relationships: shouldDetectRelationships,
|
|
499
|
+
};
|
|
500
|
+
for (const stage of selectedStages) {
|
|
501
|
+
if (!stageEligible[stage]) {
|
|
502
|
+
warnings.push({
|
|
503
|
+
code: 'enrichment_stage_skipped',
|
|
504
|
+
message: `Requested --stages ${stage}, but it cannot run: ${stagePrerequisiteReason(stage)}.`,
|
|
505
|
+
recoverable: true,
|
|
506
|
+
metadata: { stage },
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
356
511
|
if (input.mode === 'enriched' && input.providers) {
|
|
357
512
|
const providers = input.providers;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
513
|
+
if (runsStage('descriptions')) {
|
|
514
|
+
const descriptionProgress = progress?.startPhase(0.45);
|
|
515
|
+
descriptions = await runEnrichmentStage({
|
|
516
|
+
stateStore: input.stateStore,
|
|
517
|
+
runId: input.context.runId,
|
|
518
|
+
connectionId: input.connectionId,
|
|
519
|
+
syncId,
|
|
520
|
+
mode: input.mode,
|
|
521
|
+
stage: 'descriptions',
|
|
522
|
+
inputHash: descriptionsHash,
|
|
523
|
+
now,
|
|
524
|
+
forceRecompute: forcesStage('descriptions'),
|
|
525
|
+
resumedStages: state.resumedStages,
|
|
526
|
+
completedStages: state.completedStages,
|
|
527
|
+
failedStages: state.failedStages,
|
|
528
|
+
compute: () => generateDescriptions({
|
|
529
|
+
snapshot,
|
|
530
|
+
connector: input.connector,
|
|
531
|
+
context: input.context,
|
|
532
|
+
providers,
|
|
533
|
+
inputHash: descriptionsHash,
|
|
534
|
+
resumeStore: input.descriptionResumeStore,
|
|
535
|
+
progress: descriptionProgress,
|
|
536
|
+
warnings,
|
|
537
|
+
}),
|
|
538
|
+
});
|
|
539
|
+
descriptionsRanThisInvocation = true;
|
|
540
|
+
summary.dataDictionary = input.connector.sampleColumn ? 'completed' : 'skipped';
|
|
541
|
+
summary.tableDescriptions = 'completed';
|
|
542
|
+
summary.columnDescriptions = 'completed';
|
|
543
|
+
}
|
|
384
544
|
const embedding = providers.embedding;
|
|
385
|
-
if (embedding) {
|
|
545
|
+
if (embedding && runsStage('embeddings')) {
|
|
546
|
+
const embeddingProgress = progress?.startPhase(0.2);
|
|
547
|
+
const embeddingTexts = buildKtxColumnEmbeddingTexts(snapshot, await resolveDownstreamDescriptions());
|
|
548
|
+
const embeddingsHash = computeKtxEmbeddingsStageHash({
|
|
549
|
+
snapshot,
|
|
550
|
+
embeddingIdentity,
|
|
551
|
+
descriptionDigest: computeKtxScanDescriptionDigest(embeddingTexts.map((item) => item.text)),
|
|
552
|
+
});
|
|
386
553
|
embeddingUpdates = await runEnrichmentStage({
|
|
387
554
|
stateStore: input.stateStore,
|
|
388
555
|
runId: input.context.runId,
|
|
@@ -390,22 +557,21 @@ export async function runLocalScanEnrichment(input) {
|
|
|
390
557
|
syncId,
|
|
391
558
|
mode: input.mode,
|
|
392
559
|
stage: 'embeddings',
|
|
393
|
-
inputHash,
|
|
560
|
+
inputHash: embeddingsHash,
|
|
394
561
|
now,
|
|
562
|
+
forceRecompute: forcesStage('embeddings'),
|
|
395
563
|
resumedStages: state.resumedStages,
|
|
396
564
|
completedStages: state.completedStages,
|
|
397
565
|
failedStages: state.failedStages,
|
|
398
566
|
compute: async () => {
|
|
399
567
|
const embeddings = await buildEmbeddings({
|
|
400
|
-
snapshot,
|
|
401
568
|
embedding,
|
|
402
|
-
|
|
569
|
+
texts: embeddingTexts,
|
|
403
570
|
progress: embeddingProgress,
|
|
404
571
|
});
|
|
405
572
|
return embeddings.updates;
|
|
406
573
|
},
|
|
407
574
|
});
|
|
408
|
-
schema = snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId(embeddingUpdates));
|
|
409
575
|
summary.embeddings = 'completed';
|
|
410
576
|
}
|
|
411
577
|
}
|
|
@@ -413,9 +579,38 @@ export async function runLocalScanEnrichment(input) {
|
|
|
413
579
|
let relationshipProfile = null;
|
|
414
580
|
let resolvedRelationships = null;
|
|
415
581
|
let compositeRelationships = null;
|
|
582
|
+
let relationshipPartial = null;
|
|
416
583
|
let relationships = { accepted: 0, review: 0, rejected: 0, skipped: 0 };
|
|
417
|
-
|
|
584
|
+
// Promote any non-relationship stage that ran this invocation (descriptions or
|
|
585
|
+
// a `--stages embeddings,relationships` re-embed) before the slow, kill-prone
|
|
586
|
+
// relationship stage, so an interruption degrades to "no joins," never lost
|
|
587
|
+
// enrichment. descriptionUpdates uses the best-available set (D3): the manifest
|
|
588
|
+
// merge overwrites scan-managed descriptions, so the empty this-invocation set
|
|
589
|
+
// would delete prior on-disk ones.
|
|
590
|
+
const checkpointablePaidWork = summary.tableDescriptions === 'completed' || summary.embeddings === 'completed';
|
|
591
|
+
if (shouldDetectRelationships && checkpointablePaidWork && input.onCheckpoint) {
|
|
592
|
+
await input.onCheckpoint({
|
|
593
|
+
snapshot,
|
|
594
|
+
summary: { ...summary },
|
|
595
|
+
relationships,
|
|
596
|
+
state: summarizeKtxScanEnrichmentState(state),
|
|
597
|
+
warnings: [...warnings],
|
|
598
|
+
descriptionUpdates: await resolveDownstreamDescriptions(),
|
|
599
|
+
embeddingUpdates,
|
|
600
|
+
relationshipUpdate: null,
|
|
601
|
+
relationshipProfile: null,
|
|
602
|
+
resolvedRelationships: null,
|
|
603
|
+
compositeRelationships: null,
|
|
604
|
+
relationshipPartial: null,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (shouldDetectRelationships && runsStage('relationships')) {
|
|
418
608
|
const relationshipProgress = progress?.startPhase(0.25);
|
|
609
|
+
// Relationship detection (incl. llmProposals) runs against the
|
|
610
|
+
// best-available descriptions + this run's embeddings, so the join-proposal
|
|
611
|
+
// prompt carries descriptions on both the full-run and relationships-only
|
|
612
|
+
// paths (D5). Embeddings are this run's only — they are not re-hydrated.
|
|
613
|
+
const relationshipSchema = snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId(embeddingUpdates), await resolveDownstreamDescriptions());
|
|
419
614
|
const relationshipStage = await runEnrichmentStage({
|
|
420
615
|
stateStore: input.stateStore,
|
|
421
616
|
runId: input.context.runId,
|
|
@@ -423,8 +618,9 @@ export async function runLocalScanEnrichment(input) {
|
|
|
423
618
|
syncId,
|
|
424
619
|
mode: input.mode,
|
|
425
620
|
stage: 'relationships',
|
|
426
|
-
inputHash,
|
|
621
|
+
inputHash: relationshipsHash,
|
|
427
622
|
now,
|
|
623
|
+
forceRecompute: forcesStage('relationships'),
|
|
428
624
|
resumedStages: state.resumedStages,
|
|
429
625
|
completedStages: state.completedStages,
|
|
430
626
|
failedStages: state.failedStages,
|
|
@@ -434,10 +630,12 @@ export async function runLocalScanEnrichment(input) {
|
|
|
434
630
|
connectionId: input.connectionId,
|
|
435
631
|
dialect,
|
|
436
632
|
connector: input.connector,
|
|
437
|
-
schema,
|
|
633
|
+
schema: relationshipSchema,
|
|
438
634
|
context: input.context,
|
|
439
635
|
settings: relationshipSettings,
|
|
440
636
|
llmRuntime: input.providers?.llmRuntime ?? null,
|
|
637
|
+
...(relationshipProgress ? { progress: relationshipProgress } : {}),
|
|
638
|
+
...(input.now ? { now: () => input.now().getTime() } : {}),
|
|
441
639
|
});
|
|
442
640
|
await relationshipProgress?.update(1, `Relationship detection found ${detection.relationships.accepted} accepted, ${detection.relationships.review} review`);
|
|
443
641
|
return {
|
|
@@ -449,6 +647,7 @@ export async function runLocalScanEnrichment(input) {
|
|
|
449
647
|
statisticalValidation: detection.statisticalValidation,
|
|
450
648
|
llmRelationshipValidation: detection.llmRelationshipValidation,
|
|
451
649
|
warnings: detection.warnings,
|
|
650
|
+
partial: detection.partial,
|
|
452
651
|
};
|
|
453
652
|
},
|
|
454
653
|
});
|
|
@@ -460,20 +659,74 @@ export async function runLocalScanEnrichment(input) {
|
|
|
460
659
|
resolvedRelationships = relationshipStage.resolvedRelationships;
|
|
461
660
|
compositeRelationships = relationshipStage.compositeRelationships;
|
|
462
661
|
relationships = relationshipStage.relationships;
|
|
662
|
+
relationshipPartial = relationshipStage.partial;
|
|
463
663
|
warnings.push(...relationshipStage.warnings);
|
|
664
|
+
if (relationshipPartial) {
|
|
665
|
+
warnings.push({
|
|
666
|
+
code: 'relationship_detection_partial',
|
|
667
|
+
message: relationshipPartial.reason === 'aborted'
|
|
668
|
+
? 'Relationship detection was cancelled before completing; the joins found so far are partial.'
|
|
669
|
+
: 'Relationship detection hit its wall-clock budget (scan.relationships.detectionBudgetMs) before completing; the joins found so far are partial. Raise the budget to run a fuller pass.',
|
|
670
|
+
recoverable: true,
|
|
671
|
+
metadata: { reason: relationshipPartial.reason },
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// Derived staleness: after a selective run, surface (never silently leave) any
|
|
676
|
+
// unselected stage whose stored hash no longer matches its current inputs (D4).
|
|
677
|
+
// The embeddings hash includes the description digest, so a re-describe makes
|
|
678
|
+
// embeddings diverge here; relationships are deliberately decoupled (D5) and so
|
|
679
|
+
// never diverge from a description change.
|
|
680
|
+
if (selectedStages !== undefined && input.stateStore) {
|
|
681
|
+
const currentStageHash = {
|
|
682
|
+
descriptions: () => Promise.resolve(descriptionsHash),
|
|
683
|
+
relationships: () => Promise.resolve(relationshipsHash),
|
|
684
|
+
embeddings: async () => {
|
|
685
|
+
const embeddingTexts = buildKtxColumnEmbeddingTexts(snapshot, await resolveDownstreamDescriptions());
|
|
686
|
+
return computeKtxEmbeddingsStageHash({
|
|
687
|
+
snapshot,
|
|
688
|
+
embeddingIdentity,
|
|
689
|
+
descriptionDigest: computeKtxScanDescriptionDigest(embeddingTexts.map((item) => item.text)),
|
|
690
|
+
});
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
for (const stage of KTX_SCAN_ENRICHMENT_STAGES) {
|
|
694
|
+
if (selectedStages.includes(stage)) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const completed = await input.stateStore.findLatestCompletedStage({ connectionId: input.connectionId, stage });
|
|
698
|
+
if (!completed) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (completed.inputHash !== (await currentStageHash[stage]())) {
|
|
702
|
+
warnings.push({
|
|
703
|
+
code: 'enrichment_stage_stale',
|
|
704
|
+
message: `The ${stage} enrichment stage is now stale: its inputs changed since it last ran. Refresh it with \`ktx ingest ${input.connectionId} --stages ${stage}\`.`,
|
|
705
|
+
recoverable: true,
|
|
706
|
+
metadata: { stage },
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
464
710
|
}
|
|
465
711
|
await progress?.update(1, 'Enrichment complete');
|
|
712
|
+
// The manifest merge treats ai/db descriptions as scan-managed and overwrites
|
|
713
|
+
// them with whatever this run emits, so a subset run that skips descriptions
|
|
714
|
+
// must still emit the prior on-disk ones — else the write deletes them (D3
|
|
715
|
+
// "unselected stages are left untouched on disk"). Fresh-this-run if descriptions
|
|
716
|
+
// ran, else loaded from the on-disk _schema.
|
|
717
|
+
const writtenDescriptionUpdates = await resolveDownstreamDescriptions();
|
|
466
718
|
return {
|
|
467
719
|
snapshot,
|
|
468
720
|
summary,
|
|
469
721
|
relationships,
|
|
470
722
|
state: summarizeKtxScanEnrichmentState(state),
|
|
471
723
|
warnings,
|
|
472
|
-
descriptionUpdates:
|
|
724
|
+
descriptionUpdates: writtenDescriptionUpdates,
|
|
473
725
|
embeddingUpdates,
|
|
474
726
|
relationshipUpdate,
|
|
475
727
|
relationshipProfile,
|
|
476
728
|
resolvedRelationships,
|
|
477
729
|
compositeRelationships,
|
|
730
|
+
relationshipPartial,
|
|
478
731
|
};
|
|
479
732
|
}
|
|
@@ -4,12 +4,14 @@ import type { SourceAdapter } from '../../context/ingest/types.js';
|
|
|
4
4
|
import type { KtxLocalProject } from '../../context/project/project.js';
|
|
5
5
|
import { type KtxLocalScanEnrichmentProviders } from './local-enrichment.js';
|
|
6
6
|
import { SqliteLocalScanEnrichmentStateStore } from './sqlite-local-enrichment-state-store.js';
|
|
7
|
-
import type { KtxProgressPort, KtxScanConnector, KtxScanMode, KtxScanReport, KtxScanTrigger } from './types.js';
|
|
7
|
+
import type { KtxProgressPort, KtxScanConnector, KtxScanEnrichmentStage, KtxScanMode, KtxScanReport, KtxScanTrigger } from './types.js';
|
|
8
8
|
export interface RunLocalScanOptions {
|
|
9
9
|
project: KtxLocalProject;
|
|
10
10
|
connectionId: string;
|
|
11
11
|
mode?: KtxScanMode;
|
|
12
12
|
detectRelationships?: boolean;
|
|
13
|
+
/** Enrichment stages to (re)run; omit to run all eligible stages. */
|
|
14
|
+
stages?: KtxScanEnrichmentStage[];
|
|
13
15
|
dryRun?: boolean;
|
|
14
16
|
trigger?: KtxScanTrigger;
|
|
15
17
|
databaseIntrospectionUrl?: string;
|
|
@@ -22,6 +24,7 @@ export interface RunLocalScanOptions {
|
|
|
22
24
|
enrichmentStateStore?: SqliteLocalScanEnrichmentStateStore | null;
|
|
23
25
|
progress?: KtxProgressPort;
|
|
24
26
|
embeddingProvider?: KtxEmbeddingProvider | null;
|
|
27
|
+
signal?: AbortSignal;
|
|
25
28
|
}
|
|
26
29
|
export interface LocalScanRunResult {
|
|
27
30
|
runId: string;
|