@kaelio/ktx 0.13.0 → 0.14.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.0-py3-none-any.whl → kaelio_ktx-0.14.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 +14 -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 +23 -5
- 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/connections/read-only-sql.d.ts +5 -0
- package/dist/context/connections/read-only-sql.js +139 -1
- 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/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.js +6 -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 +42 -0
- package/dist/context/project/config.js +5 -0
- package/dist/context/project/driver-schemas.d.ts +20 -0
- package/dist/context/project/driver-schemas.js +50 -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 +3 -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 +5 -1
- package/dist/setup-sources.js +124 -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/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,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { KtxSubprocessDeadlineError, runGenerateObjectInSubprocess, } from '../../context/llm/subprocess-generate-object.js';
|
|
2
3
|
const batchedTableDescriptionSchema = z.object({
|
|
3
4
|
tableDescription: z.string(),
|
|
4
5
|
columns: z.array(z.object({
|
|
@@ -281,11 +282,13 @@ export class KtxDescriptionGenerator {
|
|
|
281
282
|
logger;
|
|
282
283
|
onWarning;
|
|
283
284
|
settings;
|
|
285
|
+
spawnSubprocessGenerateChild;
|
|
284
286
|
constructor(options) {
|
|
285
287
|
this.llmRuntime = options.llmRuntime;
|
|
286
288
|
this.cache = options.cache;
|
|
287
289
|
this.logger = options.logger;
|
|
288
290
|
this.onWarning = options.onWarning;
|
|
291
|
+
this.spawnSubprocessGenerateChild = options.spawnSubprocessGenerateChild;
|
|
289
292
|
this.settings = {
|
|
290
293
|
columnMaxWords: options.settings.columnMaxWords,
|
|
291
294
|
tableMaxWords: options.settings.tableMaxWords,
|
|
@@ -500,6 +503,20 @@ export class KtxDescriptionGenerator {
|
|
|
500
503
|
const descriptions = new Map();
|
|
501
504
|
let tableDescription = null;
|
|
502
505
|
let structuredGenerationSucceeded = false;
|
|
506
|
+
// Bound + retry the per-table enrichment LLM call. A transient backend error
|
|
507
|
+
// (e.g. an "overloaded"/burst rejection when many tables enrich concurrently)
|
|
508
|
+
// otherwise nulls a whole table's descriptions on the FIRST failure — sampleTable
|
|
509
|
+
// already retries, this call did not, so transient errors silently dropped most
|
|
510
|
+
// tables of a db. retryAsync gives it the same 3-attempt backoff. A FRESH timeout
|
|
511
|
+
// per attempt still bounds a wedged wide table (it never returns a result message);
|
|
512
|
+
// a timeout is surfaced as KtxAbortedError so retryAsync does NOT retry it (one
|
|
513
|
+
// wedge stays one timeout, not 3×). Tune via KTX_ENRICH_LLM_TIMEOUT_MS (default
|
|
514
|
+
// 120s) and KTX_ENRICH_LLM_ATTEMPTS (default 3).
|
|
515
|
+
const rawEnrichTimeoutMs = Number(process.env.KTX_ENRICH_LLM_TIMEOUT_MS);
|
|
516
|
+
const enrichTimeoutMs = Number.isFinite(rawEnrichTimeoutMs) && rawEnrichTimeoutMs > 0 ? rawEnrichTimeoutMs : 120_000;
|
|
517
|
+
const enrichAttempts = Math.max(1, Number(process.env.KTX_ENRICH_LLM_ATTEMPTS ?? 3) || 3);
|
|
518
|
+
let llmStartedAt = 0;
|
|
519
|
+
let lastTimedOut = false;
|
|
503
520
|
try {
|
|
504
521
|
const prompt = batchedPrompt({
|
|
505
522
|
table: input.table,
|
|
@@ -508,12 +525,78 @@ export class KtxDescriptionGenerator {
|
|
|
508
525
|
tableMaxWords: this.settings.tableMaxWords,
|
|
509
526
|
columnMaxWords: this.settings.columnMaxWords,
|
|
510
527
|
});
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
528
|
+
llmStartedAt = Date.now();
|
|
529
|
+
this.logger?.info(`[enrich] llm:start table=${input.table.name} cols=${input.table.columns.length} promptChars=${prompt.user.length} timeoutMs=${enrichTimeoutMs} attempts=${enrichAttempts}`, { connectorId: input.connector.id, table: input.table.name, columns: input.table.columns.length });
|
|
530
|
+
// Subprocess backends (codex/claude-code) own an SDK child that ignores the
|
|
531
|
+
// in-process abort, so each attempt runs behind a tree-killable boundary;
|
|
532
|
+
// HTTP backends keep the native abortSignal -> fetch cancellation.
|
|
533
|
+
const enrichForkSpec = this.llmRuntime.subprocessForkSpec();
|
|
534
|
+
const enrichJsonSchema = enrichForkSpec
|
|
535
|
+
? z.toJSONSchema(batchedTableDescriptionSchema, { target: 'draft-7' })
|
|
536
|
+
: null;
|
|
537
|
+
const generated = await retryAsync(async () => {
|
|
538
|
+
if (enrichForkSpec && enrichJsonSchema) {
|
|
539
|
+
try {
|
|
540
|
+
return await runGenerateObjectInSubprocess({
|
|
541
|
+
forkSpec: enrichForkSpec,
|
|
542
|
+
role: 'candidateExtraction',
|
|
543
|
+
system: prompt.system,
|
|
544
|
+
prompt: prompt.user,
|
|
545
|
+
schema: batchedTableDescriptionSchema,
|
|
546
|
+
jsonSchema: enrichJsonSchema,
|
|
547
|
+
deadlineMs: enrichTimeoutMs,
|
|
548
|
+
...(input.context.signal ? { signal: input.context.signal } : {}),
|
|
549
|
+
...(this.spawnSubprocessGenerateChild
|
|
550
|
+
? { spawnChild: this.spawnSubprocessGenerateChild }
|
|
551
|
+
: {}),
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
// The boundary tree-kills the wedged child on deadline; a per-table
|
|
556
|
+
// timeout is not worth retrying (it would just time out again), so
|
|
557
|
+
// surface it as KtxAbortedError so retryAsync stops immediately.
|
|
558
|
+
if (error instanceof KtxSubprocessDeadlineError && !input.context.signal?.aborted) {
|
|
559
|
+
lastTimedOut = true;
|
|
560
|
+
throw new KtxAbortedError();
|
|
561
|
+
}
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const enrichTimeout = AbortSignal.timeout(enrichTimeoutMs);
|
|
566
|
+
const abortSignal = input.context.signal
|
|
567
|
+
? AbortSignal.any([enrichTimeout, input.context.signal])
|
|
568
|
+
: enrichTimeout;
|
|
569
|
+
try {
|
|
570
|
+
return await this.llmRuntime.generateObject({
|
|
571
|
+
role: 'candidateExtraction',
|
|
572
|
+
system: prompt.system,
|
|
573
|
+
prompt: prompt.user,
|
|
574
|
+
schema: batchedTableDescriptionSchema,
|
|
575
|
+
temperature: this.settings.temperature,
|
|
576
|
+
abortSignal,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
// A per-table timeout is not worth retrying (it would just time out
|
|
581
|
+
// again); surface it as KtxAbortedError so retryAsync stops immediately.
|
|
582
|
+
// A genuine context cancellation is handled by retryAsync's own signal check.
|
|
583
|
+
if (enrichTimeout.aborted && !input.context.signal?.aborted) {
|
|
584
|
+
lastTimedOut = true;
|
|
585
|
+
throw new KtxAbortedError();
|
|
586
|
+
}
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
}, {
|
|
590
|
+
attempts: enrichAttempts,
|
|
591
|
+
baseDelayMs: 500,
|
|
592
|
+
...(input.context.signal ? { signal: input.context.signal } : {}),
|
|
593
|
+
onAttemptFailure: (error, attempt) => {
|
|
594
|
+
this.logger?.warn(`[enrich] llm:retry table=${input.table.name} attempt=${attempt}: ${errorMessage(error)}`, { connectorId: input.connector.id, table: input.table.name, attempt });
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
this.logger?.info(`[enrich] llm:done table=${input.table.name} ms=${Date.now() - llmStartedAt}`, {
|
|
598
|
+
connectorId: input.connector.id,
|
|
599
|
+
table: input.table.name,
|
|
517
600
|
});
|
|
518
601
|
structuredGenerationSucceeded = true;
|
|
519
602
|
tableDescription = generated.tableDescription.trim() || null;
|
|
@@ -533,16 +616,20 @@ export class KtxDescriptionGenerator {
|
|
|
533
616
|
}
|
|
534
617
|
}
|
|
535
618
|
catch (error) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
619
|
+
// A genuine cancellation propagates so the stage fails and resumes; a
|
|
620
|
+
// per-table timeout (context.signal not aborted) still degrades to null.
|
|
621
|
+
if (input.context.signal?.aborted) {
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
const elapsedMs = llmStartedAt ? Date.now() - llmStartedAt : 0;
|
|
625
|
+
const timedOut = lastTimedOut;
|
|
626
|
+
this.logger?.warn(`[enrich] llm:${timedOut ? 'TIMEOUT' : 'fail'} table=${input.table.name} cols=${input.table.columns.length} ms=${elapsedMs}: ${errorMessage(error)}`, { connectorId: input.connector.id, table: input.table.name, timedOut, elapsedMs });
|
|
540
627
|
this.onWarning?.({
|
|
541
|
-
code: 'enrichment_failed',
|
|
542
|
-
message: `Failed to generate batched description for table ${input.table.name}: ${errorMessage(error)}`,
|
|
628
|
+
code: timedOut ? 'enrichment_timeout' : 'enrichment_failed',
|
|
629
|
+
message: `${timedOut ? `Timed out after ${elapsedMs}ms generating` : 'Failed to generate'} batched description for table ${input.table.name}: ${errorMessage(error)}`,
|
|
543
630
|
table: input.table.name,
|
|
544
631
|
recoverable: true,
|
|
545
|
-
metadata: { connectorId: input.connector.id },
|
|
632
|
+
metadata: { connectorId: input.connector.id, ...(timedOut ? { timeoutMs: enrichTimeoutMs } : {}) },
|
|
546
633
|
});
|
|
547
634
|
}
|
|
548
635
|
if (!structuredGenerationSucceeded) {
|
|
@@ -9,6 +9,11 @@ import type { KtxTableRef } from './types.js';
|
|
|
9
9
|
* "catalog.db.name" — fully qualified
|
|
10
10
|
* "db.name" — schema-qualified (catalog = null)
|
|
11
11
|
* "name" — bare (catalog = db = null; SQLite-shape)
|
|
12
|
+
*
|
|
13
|
+
* SQLite exposes a single schema named `main` but the connector emits objects
|
|
14
|
+
* with `db: null`, so the `"main.<name>"` form is normalized to the bare shape
|
|
15
|
+
* to match. Both `"main.customers"` and `"customers"` therefore select the same
|
|
16
|
+
* object.
|
|
12
17
|
*/
|
|
13
18
|
export declare function resolveEnabledTables(connection: Record<string, unknown> | undefined): ReadonlySet<KtxTableRefKey> | null;
|
|
14
19
|
/** @internal */
|
|
@@ -8,21 +8,33 @@ import { tableRefSet } from './table-ref.js';
|
|
|
8
8
|
* "catalog.db.name" — fully qualified
|
|
9
9
|
* "db.name" — schema-qualified (catalog = null)
|
|
10
10
|
* "name" — bare (catalog = db = null; SQLite-shape)
|
|
11
|
+
*
|
|
12
|
+
* SQLite exposes a single schema named `main` but the connector emits objects
|
|
13
|
+
* with `db: null`, so the `"main.<name>"` form is normalized to the bare shape
|
|
14
|
+
* to match. Both `"main.customers"` and `"customers"` therefore select the same
|
|
15
|
+
* object.
|
|
11
16
|
*/
|
|
12
17
|
export function resolveEnabledTables(connection) {
|
|
13
18
|
const raw = connection?.enabled_tables;
|
|
14
19
|
if (!Array.isArray(raw) || raw.length === 0)
|
|
15
20
|
return null;
|
|
21
|
+
const driver = typeof connection?.driver === 'string' ? connection.driver : undefined;
|
|
16
22
|
const refs = [];
|
|
17
23
|
for (const value of raw) {
|
|
18
24
|
const parsed = parseEnabledTableEntry(value);
|
|
19
25
|
if (parsed)
|
|
20
|
-
refs.push(parsed);
|
|
26
|
+
refs.push(normalizeRefForDriver(parsed, driver));
|
|
21
27
|
}
|
|
22
28
|
if (refs.length === 0)
|
|
23
29
|
return null;
|
|
24
30
|
return tableRefSet(refs);
|
|
25
31
|
}
|
|
32
|
+
function normalizeRefForDriver(ref, driver) {
|
|
33
|
+
if (driver === 'sqlite' && ref.catalog === null && ref.db === 'main') {
|
|
34
|
+
return { catalog: null, db: null, name: ref.name };
|
|
35
|
+
}
|
|
36
|
+
return ref;
|
|
37
|
+
}
|
|
26
38
|
function parseEnabledTableEntry(value) {
|
|
27
39
|
if (typeof value === 'string') {
|
|
28
40
|
return parseDottedTableEntry(value);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import type { KtxScanRelationshipConfig } from '../project/config.js';
|
|
1
2
|
import type { KtxScanEnrichmentStage, KtxScanEnrichmentStateSummary, KtxScanMode, KtxSchemaSnapshot } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Canonical enrichment-stage registry. The `--stages` CLI parser validates
|
|
5
|
+
* against this list, and stage selection / iteration derives its order here.
|
|
6
|
+
*/
|
|
7
|
+
export declare const KTX_SCAN_ENRICHMENT_STAGES: readonly KtxScanEnrichmentStage[];
|
|
2
8
|
export interface KtxScanEnrichmentStageLookup {
|
|
3
|
-
|
|
9
|
+
connectionId: string;
|
|
4
10
|
stage: KtxScanEnrichmentStage;
|
|
5
11
|
inputHash: string;
|
|
6
12
|
}
|
|
@@ -31,17 +37,54 @@ export interface KtxScanEnrichmentFailedStage {
|
|
|
31
37
|
export type KtxScanEnrichmentStageRecord<TOutput = unknown> = KtxScanEnrichmentCompletedStage<TOutput> | KtxScanEnrichmentFailedStage;
|
|
32
38
|
export interface KtxScanEnrichmentStateStore {
|
|
33
39
|
findCompletedStage<TOutput = unknown>(input: KtxScanEnrichmentStageLookup): Promise<KtxScanEnrichmentCompletedStage<TOutput> | null>;
|
|
40
|
+
/**
|
|
41
|
+
* The most recently completed row for a (connection, stage) pair regardless of
|
|
42
|
+
* input hash. Used by the staleness check to compare a stage's stored hash
|
|
43
|
+
* against its freshly recomputed one (D4).
|
|
44
|
+
*/
|
|
45
|
+
findLatestCompletedStage(input: {
|
|
46
|
+
connectionId: string;
|
|
47
|
+
stage: KtxScanEnrichmentStage;
|
|
48
|
+
}): Promise<KtxScanEnrichmentCompletedStage | null>;
|
|
34
49
|
saveCompletedStage<TOutput = unknown>(input: Omit<KtxScanEnrichmentCompletedStage<TOutput>, 'status' | 'errorMessage'>): Promise<void>;
|
|
35
50
|
saveFailedStage(input: Omit<KtxScanEnrichmentFailedStage, 'status' | 'output'>): Promise<void>;
|
|
36
51
|
listRunStages(runId: string): Promise<KtxScanEnrichmentStageRecord[]>;
|
|
37
52
|
}
|
|
38
|
-
|
|
53
|
+
/** Description-LLM identity: the inputs that change a description's content. */
|
|
54
|
+
export interface KtxScanLlmIdentity {
|
|
55
|
+
model: string | null;
|
|
56
|
+
baseUrlConfigured: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** Embedding-model identity: the inputs that change an embedding vector. */
|
|
59
|
+
export interface KtxScanEmbeddingIdentity {
|
|
60
|
+
model: string | null;
|
|
61
|
+
dimensions: number | null;
|
|
62
|
+
batchSize: number | null;
|
|
63
|
+
}
|
|
64
|
+
export interface KtxDescriptionsStageHashInput {
|
|
39
65
|
snapshot: KtxSchemaSnapshot;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
llmIdentity: KtxScanLlmIdentity;
|
|
67
|
+
}
|
|
68
|
+
export interface KtxEmbeddingsStageHashInput {
|
|
69
|
+
snapshot: KtxSchemaSnapshot;
|
|
70
|
+
embeddingIdentity: KtxScanEmbeddingIdentity;
|
|
71
|
+
/** Digest of the resolved description text the embeddings consume (see {@link computeKtxScanDescriptionDigest}). */
|
|
72
|
+
descriptionDigest: string;
|
|
73
|
+
}
|
|
74
|
+
export interface KtxRelationshipsStageHashInput {
|
|
75
|
+
snapshot: KtxSchemaSnapshot;
|
|
76
|
+
relationshipSettings: KtxScanRelationshipConfig;
|
|
77
|
+
llmIdentity: KtxScanLlmIdentity;
|
|
44
78
|
}
|
|
45
|
-
export declare function
|
|
79
|
+
export declare function computeKtxDescriptionsStageHash(input: KtxDescriptionsStageHashInput): string;
|
|
80
|
+
export declare function computeKtxEmbeddingsStageHash(input: KtxEmbeddingsStageHashInput): string;
|
|
81
|
+
export declare function computeKtxRelationshipsStageHash(input: KtxRelationshipsStageHashInput): string;
|
|
82
|
+
/**
|
|
83
|
+
* Content digest of the resolved per-column description text the embeddings
|
|
84
|
+
* stage consumes. Folding it into the embeddings hash content-addresses
|
|
85
|
+
* embeddings on their real upstream, so re-describing busts only the embeddings
|
|
86
|
+
* that depend on the changed text (D4 self-healing).
|
|
87
|
+
*/
|
|
88
|
+
export declare function computeKtxScanDescriptionDigest(texts: readonly string[]): string;
|
|
46
89
|
export declare function completedKtxScanEnrichmentStateSummary(): KtxScanEnrichmentStateSummary;
|
|
47
90
|
export declare function summarizeKtxScanEnrichmentState(input: KtxScanEnrichmentStateSummary): KtxScanEnrichmentStateSummary;
|
|
@@ -1,21 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { stableContentHash } from '../cache/content-result-cache.js';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical enrichment-stage registry. The `--stages` CLI parser validates
|
|
4
|
+
* against this list, and stage selection / iteration derives its order here.
|
|
5
|
+
*/
|
|
6
|
+
export const KTX_SCAN_ENRICHMENT_STAGES = [
|
|
3
7
|
'descriptions',
|
|
4
8
|
'embeddings',
|
|
5
9
|
'relationships',
|
|
6
10
|
];
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
export function computeKtxDescriptionsStageHash(input) {
|
|
12
|
+
return stableContentHash({ snapshot: input.snapshot, llmIdentity: input.llmIdentity });
|
|
13
|
+
}
|
|
14
|
+
export function computeKtxEmbeddingsStageHash(input) {
|
|
15
|
+
return stableContentHash({
|
|
16
|
+
snapshot: input.snapshot,
|
|
17
|
+
embeddingIdentity: input.embeddingIdentity,
|
|
18
|
+
descriptionDigest: input.descriptionDigest,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function computeKtxRelationshipsStageHash(input) {
|
|
22
|
+
return stableContentHash({
|
|
23
|
+
snapshot: input.snapshot,
|
|
24
|
+
relationshipSettings: input.relationshipSettings,
|
|
25
|
+
llmIdentity: input.llmIdentity,
|
|
26
|
+
});
|
|
16
27
|
}
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Content digest of the resolved per-column description text the embeddings
|
|
30
|
+
* stage consumes. Folding it into the embeddings hash content-addresses
|
|
31
|
+
* embeddings on their real upstream, so re-describing busts only the embeddings
|
|
32
|
+
* that depend on the changed text (D4 self-healing).
|
|
33
|
+
*/
|
|
34
|
+
export function computeKtxScanDescriptionDigest(texts) {
|
|
35
|
+
return stableContentHash(texts);
|
|
19
36
|
}
|
|
20
37
|
function uniqueStages(stages) {
|
|
21
38
|
const seen = new Set();
|
|
@@ -11,6 +11,12 @@ export interface WriteLocalScanManifestShardsInput {
|
|
|
11
11
|
dryRun: boolean;
|
|
12
12
|
descriptionUpdates?: KtxLocalScanEnrichmentResult['descriptionUpdates'];
|
|
13
13
|
relationshipUpdate?: KtxLocalScanEnrichmentResult['relationshipUpdate'];
|
|
14
|
+
/**
|
|
15
|
+
* When set, write only the shards that contain one of these tables. All shards
|
|
16
|
+
* are still built (so merging preserves prior content); the unlisted shards are
|
|
17
|
+
* left untouched on disk. Used by the incremental flush to bound git commits.
|
|
18
|
+
*/
|
|
19
|
+
onlyChangedTableNames?: ReadonlySet<string>;
|
|
14
20
|
}
|
|
15
21
|
export interface WriteLocalScanManifestShardsResult {
|
|
16
22
|
manifestShards: string[];
|
|
@@ -28,5 +34,40 @@ export interface WriteLocalScanEnrichmentArtifactsInput {
|
|
|
28
34
|
export interface WriteLocalScanEnrichmentArtifactsResult extends WriteLocalScanManifestShardsResult {
|
|
29
35
|
enrichmentArtifacts: string[];
|
|
30
36
|
}
|
|
37
|
+
type LocalDescriptionUpdates = KtxLocalScanEnrichmentResult['descriptionUpdates'];
|
|
38
|
+
/**
|
|
39
|
+
* Reconstructs the descriptions already persisted in the on-disk `_schema` as
|
|
40
|
+
* the in-memory `descriptionUpdates` shape, so a stage-selective run that skips
|
|
41
|
+
* the descriptions stage (e.g. `--stages relationships`/`--stages embeddings`)
|
|
42
|
+
* can still feed embeddings + relationships the prior AI descriptions. Tables or
|
|
43
|
+
* columns with no AI description carry `null`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function loadOnDiskDescriptionUpdates(project: KtxLocalProject, connectionId: string, snapshot: KtxSchemaSnapshot): Promise<LocalDescriptionUpdates>;
|
|
46
|
+
export interface KtxScanDescriptionResumeStore {
|
|
47
|
+
/** Prior enriched descriptions when the durable record matches `inputHash`, else null. */
|
|
48
|
+
load(inputHash: string): Promise<LocalDescriptionUpdates | null>;
|
|
49
|
+
/** Persist the descriptions so far + the manifest shards that gained a table this batch. */
|
|
50
|
+
flush(input: {
|
|
51
|
+
inputHash: string;
|
|
52
|
+
snapshot: KtxSchemaSnapshot;
|
|
53
|
+
descriptionUpdates: LocalDescriptionUpdates;
|
|
54
|
+
changedTableNames: ReadonlySet<string>;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export declare function createKtxScanDescriptionResumeStore(deps: {
|
|
58
|
+
project: KtxLocalProject;
|
|
59
|
+
connectionId: string;
|
|
60
|
+
syncId: string;
|
|
61
|
+
driver: KtxConnectionDriver;
|
|
62
|
+
}): KtxScanDescriptionResumeStore;
|
|
31
63
|
export declare function writeLocalScanManifestShards(input: WriteLocalScanManifestShardsInput): Promise<WriteLocalScanManifestShardsResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Promote the descriptions + embeddings into the queryable `_schema` manifest
|
|
66
|
+
* (and the raw enrichment artifacts) before relationship detection runs. The
|
|
67
|
+
* generated joins and the relationship diagnostics are deliberately left to the
|
|
68
|
+
* final write, so an interrupted relationship stage never loses the paid LLM
|
|
69
|
+
* enrichment and never emits empty relationship diagnostics.
|
|
70
|
+
*/
|
|
71
|
+
export declare function writeLocalScanEnrichmentCheckpoint(input: WriteLocalScanEnrichmentArtifactsInput): Promise<WriteLocalScanEnrichmentArtifactsResult>;
|
|
32
72
|
export declare function writeLocalScanEnrichmentArtifacts(input: WriteLocalScanEnrichmentArtifactsInput): Promise<WriteLocalScanEnrichmentArtifactsResult>;
|
|
73
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
|
-
import { buildLiveDatabaseManifestShards } from '../../context/ingest/adapters/live-database/manifest.js';
|
|
2
|
+
import { buildLiveDatabaseManifestShards, buildTableRef } from '../../context/ingest/adapters/live-database/manifest.js';
|
|
3
3
|
import { isSlYamlPath } from '../../context/sl/source-files.js';
|
|
4
4
|
import { deriveFederatedConnection } from '../connections/federation.js';
|
|
5
|
+
import { tableRefKey } from './table-ref.js';
|
|
5
6
|
import { buildKtxRelationshipArtifacts, buildKtxRelationshipDiagnostics, emptyKtxRelationshipProfileArtifact, } from './relationship-diagnostics.js';
|
|
6
7
|
const LIVE_DATABASE_ADAPTER = 'live-database';
|
|
7
8
|
const LOCAL_AUTHOR = 'ktx';
|
|
@@ -19,8 +20,7 @@ function artifactDir(connectionId, syncId) {
|
|
|
19
20
|
function schemaDir(connectionId) {
|
|
20
21
|
return `${SL_DIR_PREFIX}/${connectionId}/${SCHEMA_DIR}`;
|
|
21
22
|
}
|
|
22
|
-
function tableDescription(table,
|
|
23
|
-
const update = descriptionUpdates.find((candidate) => candidate.table.name === table.name);
|
|
23
|
+
function tableDescription(table, update) {
|
|
24
24
|
const descriptions = {};
|
|
25
25
|
if (table.comment) {
|
|
26
26
|
descriptions.db = table.comment;
|
|
@@ -30,8 +30,7 @@ function tableDescription(table, descriptionUpdates = []) {
|
|
|
30
30
|
}
|
|
31
31
|
return Object.keys(descriptions).length > 0 ? descriptions : undefined;
|
|
32
32
|
}
|
|
33
|
-
function columnDescription(
|
|
34
|
-
const update = descriptionUpdates.find((candidate) => candidate.table.name === table.name);
|
|
33
|
+
function columnDescription(column, update) {
|
|
35
34
|
const aiDescription = update?.columnDescriptions[column.name] ?? null;
|
|
36
35
|
const descriptions = {};
|
|
37
36
|
if (column.comment) {
|
|
@@ -43,19 +42,25 @@ function columnDescription(table, column, descriptionUpdates = []) {
|
|
|
43
42
|
return Object.keys(descriptions).length > 0 ? descriptions : undefined;
|
|
44
43
|
}
|
|
45
44
|
function snapshotTablesToManifestData(snapshot, descriptionUpdates = []) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
name:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
// Resolve a table's descriptions by full identity: two same-named tables in
|
|
46
|
+
// different schemas must not collapse onto one update.
|
|
47
|
+
const updateByRef = new Map(descriptionUpdates.map((update) => [tableRefKey(update.table), update]));
|
|
48
|
+
return snapshot.tables.map((table) => {
|
|
49
|
+
const update = updateByRef.get(tableRefKey({ catalog: table.catalog, db: table.db, name: table.name }));
|
|
50
|
+
return {
|
|
51
|
+
name: table.name,
|
|
52
|
+
catalog: table.catalog,
|
|
53
|
+
db: table.db,
|
|
54
|
+
descriptions: tableDescription(table, update),
|
|
55
|
+
columns: table.columns.map((column) => ({
|
|
56
|
+
name: column.name,
|
|
57
|
+
type: column.dimensionType,
|
|
58
|
+
...(column.primaryKey ? { pk: true } : {}),
|
|
59
|
+
...(column.nullable === false ? { nullable: false } : {}),
|
|
60
|
+
descriptions: columnDescription(column, update),
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
59
64
|
}
|
|
60
65
|
function formalJoins(snapshot) {
|
|
61
66
|
const joins = [];
|
|
@@ -163,12 +168,15 @@ async function loadExistingManifestState(project, connectionId, snapshot, siblin
|
|
|
163
168
|
if (!validTableNames.has(tableName)) {
|
|
164
169
|
continue;
|
|
165
170
|
}
|
|
166
|
-
|
|
171
|
+
// Descriptions/usage key on the fully-qualified `entry.table` ref so two
|
|
172
|
+
// same-named tables across schemas stay distinct; joins remain keyed by
|
|
173
|
+
// bare name to match the bare-name join graph.
|
|
174
|
+
descriptions.set(entry.table, {
|
|
167
175
|
table: entry.descriptions ? { ...entry.descriptions } : undefined,
|
|
168
176
|
columns: new Map((entry.columns ?? []).flatMap((column) => column.descriptions ? [[column.name, { ...column.descriptions }]] : [])),
|
|
169
177
|
});
|
|
170
178
|
if (entry.usage) {
|
|
171
|
-
usage.set(
|
|
179
|
+
usage.set(entry.table, { ...entry.usage });
|
|
172
180
|
}
|
|
173
181
|
const joins = (entry.joins ?? []).filter((join) => {
|
|
174
182
|
return ((join.source === 'manual' || join.source === 'inferred') &&
|
|
@@ -186,6 +194,76 @@ async function loadExistingManifestState(project, connectionId, snapshot, siblin
|
|
|
186
194
|
}
|
|
187
195
|
return { descriptions, preservedJoins, usage };
|
|
188
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Reconstructs the descriptions already persisted in the on-disk `_schema` as
|
|
199
|
+
* the in-memory `descriptionUpdates` shape, so a stage-selective run that skips
|
|
200
|
+
* the descriptions stage (e.g. `--stages relationships`/`--stages embeddings`)
|
|
201
|
+
* can still feed embeddings + relationships the prior AI descriptions. Tables or
|
|
202
|
+
* columns with no AI description carry `null`.
|
|
203
|
+
*/
|
|
204
|
+
export async function loadOnDiskDescriptionUpdates(project, connectionId, snapshot) {
|
|
205
|
+
const siblingTargets = await federatedSiblingTargets(project, connectionId);
|
|
206
|
+
const existing = await loadExistingManifestState(project, connectionId, snapshot, siblingTargets);
|
|
207
|
+
return snapshot.tables.map((table) => {
|
|
208
|
+
const entry = existing.descriptions.get(buildTableRef(table.name, table.catalog, table.db));
|
|
209
|
+
const columnDescriptions = {};
|
|
210
|
+
for (const column of table.columns) {
|
|
211
|
+
columnDescriptions[column.name] = entry?.columns.get(column.name)?.ai ?? null;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
table: { catalog: table.catalog, db: table.db, name: table.name },
|
|
215
|
+
tableDescription: entry?.table?.ai ?? null,
|
|
216
|
+
columnDescriptions,
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// The incremental descriptions resume record. It lives at a stable, NON-syncId
|
|
221
|
+
// path: a from-scratch interruption gets a fresh syncId on the next run, so a
|
|
222
|
+
// syncId-scoped record would be unreachable on resume. The manifest already lives
|
|
223
|
+
// at the same stable per-connection scope.
|
|
224
|
+
function descriptionsProgressPath(connectionId) {
|
|
225
|
+
return `raw-sources/${connectionId}/${LIVE_DATABASE_ADAPTER}/enrichment-progress/descriptions.json`;
|
|
226
|
+
}
|
|
227
|
+
export function createKtxScanDescriptionResumeStore(deps) {
|
|
228
|
+
const path = descriptionsProgressPath(deps.connectionId);
|
|
229
|
+
return {
|
|
230
|
+
async load(inputHash) {
|
|
231
|
+
let content;
|
|
232
|
+
try {
|
|
233
|
+
({ content } = await deps.project.fileStore.readFile(path));
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const record = JSON.parse(content);
|
|
240
|
+
// A changed inputHash (schema or enrichment settings changed) ignores the
|
|
241
|
+
// prior record and recomputes — spec-19's inputHash-gated resume semantics.
|
|
242
|
+
if (!record || record.inputHash !== inputHash || !Array.isArray(record.descriptions)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return record.descriptions;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
async flush({ inputHash, snapshot, descriptionUpdates, changedTableNames }) {
|
|
252
|
+
const record = { inputHash, descriptions: descriptionUpdates };
|
|
253
|
+
await writeJsonArtifact(deps.project, path, record, `scan(${LIVE_DATABASE_ADAPTER}): flush enrichment descriptions progress syncId=${deps.syncId}`);
|
|
254
|
+
await writeLocalScanManifestShards({
|
|
255
|
+
project: deps.project,
|
|
256
|
+
connectionId: deps.connectionId,
|
|
257
|
+
syncId: deps.syncId,
|
|
258
|
+
driver: deps.driver,
|
|
259
|
+
snapshot,
|
|
260
|
+
descriptionUpdates,
|
|
261
|
+
dryRun: false,
|
|
262
|
+
onlyChangedTableNames: changedTableNames,
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
189
267
|
async function writeJsonArtifact(project, path, value, commitMessage) {
|
|
190
268
|
await project.fileStore.writeFile(path, `${JSON.stringify(value, null, 2)}\n`, LOCAL_AUTHOR, LOCAL_AUTHOR_EMAIL, commitMessage);
|
|
191
269
|
}
|
|
@@ -210,6 +288,9 @@ export async function writeLocalScanManifestShards(input) {
|
|
|
210
288
|
});
|
|
211
289
|
const manifestShards = [];
|
|
212
290
|
for (const [shardKey, shard] of [...shards.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
291
|
+
if (input.onlyChangedTableNames && !Object.keys(shard.tables).some((table) => input.onlyChangedTableNames.has(table))) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
213
294
|
const path = `${schemaDir(input.connectionId)}/${shardKey}.yaml`;
|
|
214
295
|
await input.project.fileStore.writeFile(path, YAML.stringify(shard, { indent: 2, lineWidth: 0, version: '1.1' }), LOCAL_AUTHOR, LOCAL_AUTHOR_EMAIL, `scan(${LIVE_DATABASE_ADAPTER}): write manifest shard ${shardKey} syncId=${input.syncId}`);
|
|
215
296
|
manifestShards.push(path);
|
|
@@ -219,6 +300,53 @@ export async function writeLocalScanManifestShards(input) {
|
|
|
219
300
|
manifestShardsWritten: manifestShards.length,
|
|
220
301
|
};
|
|
221
302
|
}
|
|
303
|
+
async function writeEnrichmentDescriptionArtifacts(input) {
|
|
304
|
+
const descriptionsArtifact = `${input.enrichmentRoot}/descriptions.json`;
|
|
305
|
+
const embeddingsArtifact = `${input.enrichmentRoot}/embeddings.json`;
|
|
306
|
+
const enrichmentArtifacts = [];
|
|
307
|
+
if (input.enrichment.summary.tableDescriptions === 'completed' ||
|
|
308
|
+
input.enrichment.summary.columnDescriptions === 'completed') {
|
|
309
|
+
enrichmentArtifacts.push(descriptionsArtifact);
|
|
310
|
+
await writeJsonArtifact(input.project, descriptionsArtifact, input.enrichment.descriptionUpdates, `scan(${LIVE_DATABASE_ADAPTER}): write enrichment descriptions syncId=${input.syncId}`);
|
|
311
|
+
}
|
|
312
|
+
if (input.enrichment.summary.embeddings === 'completed') {
|
|
313
|
+
enrichmentArtifacts.push(embeddingsArtifact);
|
|
314
|
+
await writeJsonArtifact(input.project, embeddingsArtifact, input.enrichment.embeddingUpdates, `scan(${LIVE_DATABASE_ADAPTER}): write enrichment embeddings syncId=${input.syncId}`);
|
|
315
|
+
}
|
|
316
|
+
return enrichmentArtifacts;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Promote the descriptions + embeddings into the queryable `_schema` manifest
|
|
320
|
+
* (and the raw enrichment artifacts) before relationship detection runs. The
|
|
321
|
+
* generated joins and the relationship diagnostics are deliberately left to the
|
|
322
|
+
* final write, so an interrupted relationship stage never loses the paid LLM
|
|
323
|
+
* enrichment and never emits empty relationship diagnostics.
|
|
324
|
+
*/
|
|
325
|
+
export async function writeLocalScanEnrichmentCheckpoint(input) {
|
|
326
|
+
if (input.dryRun) {
|
|
327
|
+
return { enrichmentArtifacts: [], manifestShards: [], manifestShardsWritten: 0 };
|
|
328
|
+
}
|
|
329
|
+
const enrichmentArtifacts = await writeEnrichmentDescriptionArtifacts({
|
|
330
|
+
project: input.project,
|
|
331
|
+
enrichmentRoot: artifactDir(input.connectionId, input.syncId),
|
|
332
|
+
syncId: input.syncId,
|
|
333
|
+
enrichment: input.enrichment,
|
|
334
|
+
});
|
|
335
|
+
const manifestResult = await writeLocalScanManifestShards({
|
|
336
|
+
project: input.project,
|
|
337
|
+
connectionId: input.connectionId,
|
|
338
|
+
syncId: input.syncId,
|
|
339
|
+
driver: input.driver,
|
|
340
|
+
snapshot: input.enrichment.snapshot,
|
|
341
|
+
descriptionUpdates: input.enrichment.descriptionUpdates,
|
|
342
|
+
dryRun: false,
|
|
343
|
+
});
|
|
344
|
+
return {
|
|
345
|
+
enrichmentArtifacts,
|
|
346
|
+
manifestShards: manifestResult.manifestShards,
|
|
347
|
+
manifestShardsWritten: manifestResult.manifestShardsWritten,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
222
350
|
export async function writeLocalScanEnrichmentArtifacts(input) {
|
|
223
351
|
if (input.dryRun) {
|
|
224
352
|
return {
|
|
@@ -228,21 +356,15 @@ export async function writeLocalScanEnrichmentArtifacts(input) {
|
|
|
228
356
|
};
|
|
229
357
|
}
|
|
230
358
|
const enrichmentRoot = artifactDir(input.connectionId, input.syncId);
|
|
231
|
-
const descriptionsArtifact = `${enrichmentRoot}/descriptions.json`;
|
|
232
|
-
const embeddingsArtifact = `${enrichmentRoot}/embeddings.json`;
|
|
233
359
|
const relationshipsArtifact = `${enrichmentRoot}/relationships.json`;
|
|
234
360
|
const relationshipProfileArtifact = `${enrichmentRoot}/relationship-profile.json`;
|
|
235
361
|
const relationshipDiagnosticsArtifact = `${enrichmentRoot}/relationship-diagnostics.json`;
|
|
236
|
-
const enrichmentArtifacts =
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
if (input.enrichment.summary.embeddings === 'completed') {
|
|
243
|
-
enrichmentArtifacts.push(embeddingsArtifact);
|
|
244
|
-
await writeJsonArtifact(input.project, embeddingsArtifact, input.enrichment.embeddingUpdates, `scan(${LIVE_DATABASE_ADAPTER}): write enrichment embeddings syncId=${input.syncId}`);
|
|
245
|
-
}
|
|
362
|
+
const enrichmentArtifacts = await writeEnrichmentDescriptionArtifacts({
|
|
363
|
+
project: input.project,
|
|
364
|
+
enrichmentRoot,
|
|
365
|
+
syncId: input.syncId,
|
|
366
|
+
enrichment: input.enrichment,
|
|
367
|
+
});
|
|
246
368
|
enrichmentArtifacts.push(relationshipsArtifact, relationshipProfileArtifact, relationshipDiagnosticsArtifact);
|
|
247
369
|
const hasResolvedRelationships = input.enrichment.resolvedRelationships !== null;
|
|
248
370
|
const relationshipArtifacts = buildKtxRelationshipArtifacts({
|
|
@@ -267,6 +389,7 @@ export async function writeLocalScanEnrichmentArtifacts(input) {
|
|
|
267
389
|
artifacts: relationshipArtifacts,
|
|
268
390
|
profile: relationshipProfile,
|
|
269
391
|
warnings: input.enrichment.warnings,
|
|
392
|
+
partial: input.enrichment.relationshipPartial,
|
|
270
393
|
thresholds: input.relationshipSettings
|
|
271
394
|
? {
|
|
272
395
|
acceptThreshold: input.relationshipSettings.acceptThreshold,
|