@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
|
@@ -45,6 +45,9 @@ function profileForColumn(profile, table, column) {
|
|
|
45
45
|
function rowCountForTable(profile, table) {
|
|
46
46
|
return profile.tables.find((item) => item.table.name.toLowerCase() === table.ref.name.toLowerCase())?.rowCount ?? null;
|
|
47
47
|
}
|
|
48
|
+
function resolvedDescription(descriptions) {
|
|
49
|
+
return descriptions.ai ?? descriptions.db ?? null;
|
|
50
|
+
}
|
|
48
51
|
function buildEvidencePacket(schema, profile, settings) {
|
|
49
52
|
return {
|
|
50
53
|
connectionId: schema.connectionId,
|
|
@@ -52,33 +55,39 @@ function buildEvidencePacket(schema, profile, settings) {
|
|
|
52
55
|
tables: schema.tables
|
|
53
56
|
.filter((table) => table.enabled)
|
|
54
57
|
.slice(0, settings.maxTablesPerBatch)
|
|
55
|
-
.map((table) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
58
|
+
.map((table) => {
|
|
59
|
+
const tableDescription = resolvedDescription(table.descriptions);
|
|
60
|
+
return {
|
|
61
|
+
name: table.ref.name,
|
|
62
|
+
catalog: table.ref.catalog,
|
|
63
|
+
db: table.ref.db,
|
|
64
|
+
rowCount: rowCountForTable(profile, table),
|
|
65
|
+
...(tableDescription ? { description: tableDescription } : {}),
|
|
66
|
+
columns: table.columns.slice(0, settings.maxColumnsPerTable).map((column) => {
|
|
67
|
+
const columnProfile = profileForColumn(profile, table, column);
|
|
68
|
+
const columnDescription = resolvedDescription(column.descriptions);
|
|
69
|
+
return {
|
|
70
|
+
name: column.name,
|
|
71
|
+
nativeType: column.nativeType,
|
|
72
|
+
normalizedType: column.normalizedType,
|
|
73
|
+
dimensionType: column.dimensionType,
|
|
74
|
+
nullable: column.nullable,
|
|
75
|
+
declaredPrimaryKey: column.primaryKey,
|
|
76
|
+
...(columnDescription ? { description: columnDescription } : {}),
|
|
77
|
+
profile: columnProfile
|
|
78
|
+
? {
|
|
79
|
+
rowCount: columnProfile.rowCount,
|
|
80
|
+
nullCount: columnProfile.nullCount,
|
|
81
|
+
distinctCount: columnProfile.distinctCount,
|
|
82
|
+
uniquenessRatio: columnProfile.uniquenessRatio,
|
|
83
|
+
nullRate: columnProfile.nullRate,
|
|
84
|
+
sampleValues: columnProfile.sampleValues.slice(0, settings.maxSampleValuesPerColumn),
|
|
85
|
+
}
|
|
86
|
+
: null,
|
|
87
|
+
};
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
}),
|
|
82
91
|
};
|
|
83
92
|
}
|
|
84
93
|
function pkProposalKey(table, column) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { KtxSqlDialect } from '../connections/dialects.js';
|
|
2
2
|
import type { KtxEnrichedSchema } from './enrichment-types.js';
|
|
3
|
-
import
|
|
3
|
+
import { type KtxRelationshipDetectionBudget } from './relationship-detection-budget.js';
|
|
4
|
+
import type { KtxConnectionDriver, KtxProgressPort, KtxQueryResult, KtxReadOnlyQueryInput, KtxScanContext, KtxTableRef } from './types.js';
|
|
4
5
|
export interface KtxRelationshipReadOnlyExecutor {
|
|
5
6
|
executeReadOnly(input: KtxReadOnlyQueryInput, ctx: KtxScanContext): Promise<KtxQueryResult>;
|
|
6
7
|
}
|
|
@@ -42,7 +43,8 @@ export interface KtxRelationshipProfileCache {
|
|
|
42
43
|
}
|
|
43
44
|
export interface ProfileKtxRelationshipSchemaInput {
|
|
44
45
|
connectionId: string;
|
|
45
|
-
|
|
46
|
+
driver: KtxConnectionDriver;
|
|
47
|
+
dialect: KtxSqlDialect | null;
|
|
46
48
|
schema: KtxEnrichedSchema;
|
|
47
49
|
executor: KtxRelationshipReadOnlyExecutor | null;
|
|
48
50
|
ctx: KtxScanContext;
|
|
@@ -50,6 +52,8 @@ export interface ProfileKtxRelationshipSchemaInput {
|
|
|
50
52
|
profileSampleRows?: number;
|
|
51
53
|
profileConcurrency?: number;
|
|
52
54
|
cache?: KtxRelationshipProfileCache;
|
|
55
|
+
budget?: KtxRelationshipDetectionBudget;
|
|
56
|
+
progress?: KtxProgressPort;
|
|
53
57
|
}
|
|
54
58
|
export declare function createKtxRelationshipProfileCache(): KtxRelationshipProfileCache;
|
|
55
59
|
export declare function profileKtxRelationshipSchema(input: ProfileKtxRelationshipSchemaInput): Promise<KtxRelationshipProfileArtifact>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mapWithBudget } from './relationship-detection-budget.js';
|
|
2
2
|
export function createKtxRelationshipProfileCache() {
|
|
3
3
|
return { tableProfiles: new Map() };
|
|
4
4
|
}
|
|
@@ -180,10 +180,10 @@ async function queryTableProfile(input) {
|
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
export async function profileKtxRelationshipSchema(input) {
|
|
183
|
-
if (!input.executor) {
|
|
183
|
+
if (!input.executor || !input.dialect) {
|
|
184
184
|
return {
|
|
185
185
|
connectionId: input.connectionId,
|
|
186
|
-
driver: input.
|
|
186
|
+
driver: input.driver,
|
|
187
187
|
sqlAvailable: false,
|
|
188
188
|
queryCount: 0,
|
|
189
189
|
tables: [],
|
|
@@ -196,51 +196,63 @@ export async function profileKtxRelationshipSchema(input) {
|
|
|
196
196
|
const columns = {};
|
|
197
197
|
const warnings = [];
|
|
198
198
|
const executor = input.executor;
|
|
199
|
+
const dialect = input.dialect;
|
|
199
200
|
const enabledTables = input.schema.tables.filter((candidate) => candidate.enabled);
|
|
200
|
-
const tableResults = await
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
sampleValuesPerColumn
|
|
209
|
-
profileSampleRows
|
|
210
|
-
|
|
211
|
-
const cached = input.cache?.tableProfiles.get(cacheKey);
|
|
212
|
-
if (cached) {
|
|
213
|
-
return { cached, queryCount: 0 };
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
const tableProfile = await queryTableProfile({
|
|
201
|
+
const { results: tableResults } = await mapWithBudget({
|
|
202
|
+
inputs: enabledTables,
|
|
203
|
+
concurrency: input.profileConcurrency ?? 4,
|
|
204
|
+
budget: input.budget,
|
|
205
|
+
onStart: async (index, total) => {
|
|
206
|
+
await input.progress?.update((index + 1) / total, `Profiling table ${index + 1}/${total}`, { transient: true });
|
|
207
|
+
},
|
|
208
|
+
mapOne: async (table) => {
|
|
209
|
+
const sampleValuesPerColumn = input.sampleValuesPerColumn ?? 5;
|
|
210
|
+
const profileSampleRows = input.profileSampleRows ?? 10000;
|
|
211
|
+
const cacheKey = tableProfileCacheKey({
|
|
217
212
|
connectionId: input.connectionId,
|
|
218
|
-
dialect
|
|
219
|
-
table,
|
|
220
|
-
executor,
|
|
213
|
+
dialect,
|
|
221
214
|
ctx: input.ctx,
|
|
215
|
+
table: table.ref,
|
|
222
216
|
sampleValuesPerColumn,
|
|
223
217
|
profileSampleRows,
|
|
224
218
|
});
|
|
225
|
-
input.cache?.tableProfiles.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
219
|
+
const cached = input.cache?.tableProfiles.get(cacheKey);
|
|
220
|
+
if (cached) {
|
|
221
|
+
return { cached, queryCount: 0 };
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const tableProfile = await queryTableProfile({
|
|
225
|
+
connectionId: input.connectionId,
|
|
226
|
+
dialect,
|
|
227
|
+
table,
|
|
228
|
+
executor,
|
|
229
|
+
ctx: input.ctx,
|
|
230
|
+
sampleValuesPerColumn,
|
|
231
|
+
profileSampleRows,
|
|
232
|
+
});
|
|
233
|
+
input.cache?.tableProfiles.set(cacheKey, {
|
|
234
|
+
table: tableProfile.table,
|
|
235
|
+
columns: tableProfile.columns,
|
|
236
|
+
warnings: [],
|
|
237
|
+
});
|
|
238
|
+
return { tableProfile };
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
const failureWarning = `profile_failed:${table.ref.name}:${error instanceof Error ? error.message : String(error)}`;
|
|
242
|
+
const cachedFailure = {
|
|
243
|
+
table: { table: table.ref, rowCount: 0 },
|
|
244
|
+
columns: {},
|
|
245
|
+
warnings: [failureWarning],
|
|
246
|
+
};
|
|
247
|
+
input.cache?.tableProfiles.set(cacheKey, cachedFailure);
|
|
248
|
+
return { cached: cachedFailure, queryCount: 0 };
|
|
249
|
+
}
|
|
250
|
+
},
|
|
242
251
|
});
|
|
243
252
|
for (const result of tableResults) {
|
|
253
|
+
if (!result) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
244
256
|
if ('tableProfile' in result) {
|
|
245
257
|
queryTotal += result.tableProfile.queryCount;
|
|
246
258
|
tables.push(result.tableProfile.table);
|
|
@@ -255,7 +267,7 @@ export async function profileKtxRelationshipSchema(input) {
|
|
|
255
267
|
}
|
|
256
268
|
return {
|
|
257
269
|
connectionId: input.connectionId,
|
|
258
|
-
driver: input.
|
|
270
|
+
driver: input.driver,
|
|
259
271
|
sqlAvailable: true,
|
|
260
272
|
queryCount: queryTotal,
|
|
261
273
|
tables,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { KtxSqlDialect } from '../connections/dialects.js';
|
|
2
2
|
import { type KtxRelationshipValidationBudget } from './relationship-budget.js';
|
|
3
3
|
import type { KtxRelationshipDiscoveryCandidate } from './relationship-candidates.js';
|
|
4
|
+
import { type KtxRelationshipDetectionBudget } from './relationship-detection-budget.js';
|
|
4
5
|
import { type KtxRelationshipProfileArtifact, type KtxRelationshipReadOnlyExecutor } from './relationship-profiling.js';
|
|
5
|
-
import type { KtxScanContext } from './types.js';
|
|
6
|
+
import type { KtxProgressPort, KtxScanContext } from './types.js';
|
|
6
7
|
type KtxValidatedRelationshipStatus = 'accepted' | 'review' | 'rejected';
|
|
7
8
|
interface KtxRelationshipValidationSettings {
|
|
8
9
|
acceptThreshold: number;
|
|
@@ -34,14 +35,15 @@ export interface KtxValidatedRelationshipDiscoveryCandidate extends Omit<KtxRela
|
|
|
34
35
|
}
|
|
35
36
|
export interface ValidateKtxRelationshipDiscoveryCandidatesInput {
|
|
36
37
|
connectionId: string;
|
|
37
|
-
dialect:
|
|
38
|
+
dialect: KtxSqlDialect | null;
|
|
38
39
|
candidates: readonly KtxRelationshipDiscoveryCandidate[];
|
|
39
40
|
profiles: KtxRelationshipProfileArtifact;
|
|
40
41
|
executor: KtxRelationshipReadOnlyExecutor | null;
|
|
41
42
|
ctx: KtxScanContext;
|
|
42
43
|
tableCount?: number;
|
|
43
44
|
settings?: Partial<KtxRelationshipValidationSettings>;
|
|
45
|
+
budget?: KtxRelationshipDetectionBudget;
|
|
46
|
+
progress?: KtxProgressPort;
|
|
44
47
|
}
|
|
45
|
-
export declare function mapWithConcurrency<TInput, TOutput>(inputs: readonly TInput[], concurrency: number, mapOne: (input: TInput) => Promise<TOutput>): Promise<TOutput[]>;
|
|
46
48
|
export declare function validateKtxRelationshipDiscoveryCandidates(input: ValidateKtxRelationshipDiscoveryCandidatesInput): Promise<KtxValidatedRelationshipDiscoveryCandidate[]>;
|
|
47
49
|
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { KtxQueryError } from '../../errors.js';
|
|
1
2
|
import { applyKtxRelationshipValidationBudget } from './relationship-budget.js';
|
|
3
|
+
import { mapWithBudget } from './relationship-detection-budget.js';
|
|
2
4
|
const DEFAULT_SETTINGS = {
|
|
3
5
|
acceptThreshold: 0.85,
|
|
4
6
|
reviewThreshold: 0.55,
|
|
@@ -89,20 +91,6 @@ function statusFor(input) {
|
|
|
89
91
|
}
|
|
90
92
|
return 'rejected';
|
|
91
93
|
}
|
|
92
|
-
export async function mapWithConcurrency(inputs, concurrency, mapOne) {
|
|
93
|
-
const safeConcurrency = Math.max(1, Math.floor(concurrency));
|
|
94
|
-
const outputs = new Array(inputs.length);
|
|
95
|
-
let nextIndex = 0;
|
|
96
|
-
async function worker() {
|
|
97
|
-
while (nextIndex < inputs.length) {
|
|
98
|
-
const index = nextIndex;
|
|
99
|
-
nextIndex += 1;
|
|
100
|
-
outputs[index] = await mapOne(inputs[index]);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
await Promise.all(Array.from({ length: Math.min(safeConcurrency, inputs.length) }, () => worker()));
|
|
104
|
-
return outputs;
|
|
105
|
-
}
|
|
106
94
|
function reviewWithoutValidation(candidate, profiles, reason) {
|
|
107
95
|
const sourceColumn = singleRelationshipColumn(candidate.from);
|
|
108
96
|
const targetColumn = singleRelationshipColumn(candidate.to);
|
|
@@ -129,10 +117,11 @@ function reviewWithoutValidation(candidate, profiles, reason) {
|
|
|
129
117
|
}
|
|
130
118
|
export async function validateKtxRelationshipDiscoveryCandidates(input) {
|
|
131
119
|
const settings = mergeSettings(input.settings);
|
|
132
|
-
if (!input.executor || !input.profiles.sqlAvailable) {
|
|
120
|
+
if (!input.executor || !input.profiles.sqlAvailable || !input.dialect) {
|
|
133
121
|
return input.candidates.map((candidate) => reviewWithoutValidation(candidate, input.profiles, 'validation_unavailable'));
|
|
134
122
|
}
|
|
135
123
|
const executor = input.executor;
|
|
124
|
+
const dialect = input.dialect;
|
|
136
125
|
async function validateCandidate(candidate) {
|
|
137
126
|
const sourceColumn = singleRelationshipColumn(candidate.from);
|
|
138
127
|
const targetColumn = singleRelationshipColumn(candidate.to);
|
|
@@ -141,18 +130,31 @@ export async function validateKtxRelationshipDiscoveryCandidates(input) {
|
|
|
141
130
|
if (!sourceProfile || !targetProfile) {
|
|
142
131
|
return reviewWithoutValidation(candidate, input.profiles, 'profile_unavailable');
|
|
143
132
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
133
|
+
let result;
|
|
134
|
+
try {
|
|
135
|
+
result = await executor.executeReadOnly({
|
|
136
|
+
connectionId: input.connectionId,
|
|
137
|
+
sql: buildCoverageSql({
|
|
138
|
+
dialect,
|
|
139
|
+
childTable: candidate.from.table,
|
|
140
|
+
childColumn: sourceColumn,
|
|
141
|
+
parentTable: candidate.to.table,
|
|
142
|
+
parentColumn: targetColumn,
|
|
143
|
+
maxDistinctSourceValues: settings.maxDistinctSourceValues,
|
|
144
|
+
}),
|
|
145
|
+
maxRows: 1,
|
|
146
|
+
}, input.ctx);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// A bounded-query timeout (or other query rejection) on this one coverage
|
|
150
|
+
// probe is best-effort: skip the candidate to review rather than aborting
|
|
151
|
+
// the whole validation pass.
|
|
152
|
+
if (error instanceof KtxQueryError) {
|
|
153
|
+
input.ctx.logger?.warn(`relationship validation query skipped for ${candidate.from.table.name}.${sourceColumn} -> ${candidate.to.table.name}.${targetColumn}: ${error.message}`);
|
|
154
|
+
return reviewWithoutValidation(candidate, input.profiles, 'validation_query_failed');
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
156
158
|
const childDistinct = numberAt(result, 'child_distinct');
|
|
157
159
|
const parentDistinct = numberAt(result, 'parent_distinct');
|
|
158
160
|
const overlap = numberAt(result, 'overlap');
|
|
@@ -208,14 +210,26 @@ export async function validateKtxRelationshipDiscoveryCandidates(input) {
|
|
|
208
210
|
budget: settings.validationBudget,
|
|
209
211
|
score: (candidate) => candidate.confidence,
|
|
210
212
|
});
|
|
211
|
-
const validated = await
|
|
213
|
+
const { results: validated } = await mapWithBudget({
|
|
214
|
+
inputs: budgeted.toValidate,
|
|
215
|
+
concurrency: settings.concurrency,
|
|
216
|
+
budget: input.budget,
|
|
217
|
+
onStart: async (index, total) => {
|
|
218
|
+
await input.progress?.update((index + 1) / total, `Validating candidate ${index + 1}/${total}`, {
|
|
219
|
+
transient: true,
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
mapOne: (entry) => validateCandidate(entry.candidate),
|
|
223
|
+
});
|
|
212
224
|
const byOriginalIndex = new Map();
|
|
213
225
|
for (let index = 0; index < budgeted.toValidate.length; index += 1) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
byOriginalIndex.set(originalIndex, candidate);
|
|
226
|
+
const entry = budgeted.toValidate[index];
|
|
227
|
+
if (!entry) {
|
|
228
|
+
continue;
|
|
218
229
|
}
|
|
230
|
+
// A candidate left unvalidated by the wall-clock budget degrades to the
|
|
231
|
+
// same review status as one deferred by the validation count budget.
|
|
232
|
+
byOriginalIndex.set(entry.originalIndex, validated[index] ?? reviewWithoutValidation(entry.candidate, input.profiles, 'validation_unattempted'));
|
|
219
233
|
}
|
|
220
234
|
for (const entry of budgeted.deferred) {
|
|
221
235
|
byOriginalIndex.set(entry.originalIndex, reviewWithoutValidation(entry.candidate, input.profiles, 'validation_unattempted'));
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import type { ContentResultCache } from '../cache/content-result-cache.js';
|
|
1
2
|
import type { KtxScanEnrichmentCompletedStage, KtxScanEnrichmentFailedStage, KtxScanEnrichmentStageLookup, KtxScanEnrichmentStageRecord, KtxScanEnrichmentStateStore } from './enrichment-state.js';
|
|
3
|
+
import type { KtxScanEnrichmentStage } from './types.js';
|
|
2
4
|
export interface SqliteLocalScanEnrichmentStateStoreOptions {
|
|
3
5
|
dbPath: string;
|
|
6
|
+
cache?: ContentResultCache;
|
|
4
7
|
}
|
|
5
8
|
export declare class SqliteLocalScanEnrichmentStateStore implements KtxScanEnrichmentStateStore {
|
|
6
|
-
private readonly
|
|
9
|
+
private readonly cache;
|
|
7
10
|
constructor(options: SqliteLocalScanEnrichmentStateStoreOptions);
|
|
8
11
|
findCompletedStage<TOutput = unknown>(input: KtxScanEnrichmentStageLookup): Promise<KtxScanEnrichmentCompletedStage<TOutput> | null>;
|
|
12
|
+
findLatestCompletedStage(input: {
|
|
13
|
+
connectionId: string;
|
|
14
|
+
stage: KtxScanEnrichmentStage;
|
|
15
|
+
}): Promise<KtxScanEnrichmentCompletedStage | null>;
|
|
9
16
|
saveCompletedStage<TOutput = unknown>(input: Omit<KtxScanEnrichmentCompletedStage<TOutput>, 'status' | 'errorMessage'>): Promise<void>;
|
|
10
17
|
saveFailedStage(input: Omit<KtxScanEnrichmentFailedStage, 'status' | 'output'>): Promise<void>;
|
|
11
18
|
listRunStages(runId: string): Promise<KtxScanEnrichmentStageRecord[]>;
|
|
@@ -1,190 +1,102 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
function
|
|
5
|
-
|
|
1
|
+
import { SqliteContentResultCache } from '../cache/sqlite-content-result-cache.js';
|
|
2
|
+
import { KTX_SCAN_ENRICHMENT_STAGES } from './enrichment-state.js';
|
|
3
|
+
import { KTX_SCAN_MODES } from './types.js';
|
|
4
|
+
function namespace(stage) {
|
|
5
|
+
return `scan:${stage}`;
|
|
6
|
+
}
|
|
7
|
+
function metadataFor(input) {
|
|
8
|
+
return {
|
|
9
|
+
connectionId: input.connectionId,
|
|
10
|
+
syncId: input.syncId,
|
|
11
|
+
mode: input.mode,
|
|
12
|
+
stage: input.stage,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function isScanMode(value) {
|
|
16
|
+
return typeof value === 'string' && KTX_SCAN_MODES.includes(value);
|
|
17
|
+
}
|
|
18
|
+
function isScanEnrichmentStage(value) {
|
|
19
|
+
return typeof value === 'string' && KTX_SCAN_ENRICHMENT_STAGES.includes(value);
|
|
20
|
+
}
|
|
21
|
+
function parseMetadata(record) {
|
|
22
|
+
const { connectionId, syncId, mode, stage } = record.metadata;
|
|
23
|
+
if (typeof connectionId !== 'string' || typeof syncId !== 'string' || !isScanMode(mode) || !isScanEnrichmentStage(stage)) {
|
|
24
|
+
throw new Error(`Invalid scan enrichment cache metadata for ${record.namespace}/${record.scopeKey}`);
|
|
25
|
+
}
|
|
26
|
+
return { connectionId, syncId, mode, stage };
|
|
27
|
+
}
|
|
28
|
+
function toScanRecord(record) {
|
|
29
|
+
const metadata = parseMetadata(record);
|
|
30
|
+
const base = {
|
|
31
|
+
runId: record.runId,
|
|
32
|
+
connectionId: metadata.connectionId,
|
|
33
|
+
syncId: metadata.syncId,
|
|
34
|
+
mode: metadata.mode,
|
|
35
|
+
stage: metadata.stage,
|
|
36
|
+
inputHash: record.inputHash,
|
|
37
|
+
updatedAt: record.updatedAt,
|
|
38
|
+
};
|
|
39
|
+
if (record.status === 'completed') {
|
|
6
40
|
return {
|
|
7
|
-
|
|
8
|
-
connectionId: row.connection_id,
|
|
9
|
-
syncId: row.sync_id,
|
|
10
|
-
mode: row.mode,
|
|
11
|
-
stage: row.stage,
|
|
12
|
-
inputHash: row.input_hash,
|
|
41
|
+
...base,
|
|
13
42
|
status: 'completed',
|
|
14
|
-
output:
|
|
43
|
+
output: record.output,
|
|
15
44
|
errorMessage: null,
|
|
16
|
-
updatedAt: row.updated_at,
|
|
17
45
|
};
|
|
18
46
|
}
|
|
19
47
|
return {
|
|
20
|
-
|
|
21
|
-
connectionId: row.connection_id,
|
|
22
|
-
syncId: row.sync_id,
|
|
23
|
-
mode: row.mode,
|
|
24
|
-
stage: row.stage,
|
|
25
|
-
inputHash: row.input_hash,
|
|
48
|
+
...base,
|
|
26
49
|
status: 'failed',
|
|
27
50
|
output: null,
|
|
28
|
-
errorMessage:
|
|
29
|
-
updatedAt: row.updated_at,
|
|
51
|
+
errorMessage: record.errorMessage,
|
|
30
52
|
};
|
|
31
53
|
}
|
|
32
|
-
function isSafeRunId(runId) {
|
|
33
|
-
return /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(runId);
|
|
34
|
-
}
|
|
35
54
|
export class SqliteLocalScanEnrichmentStateStore {
|
|
36
|
-
|
|
55
|
+
cache;
|
|
37
56
|
constructor(options) {
|
|
38
|
-
|
|
39
|
-
this.db = new Database(options.dbPath);
|
|
40
|
-
this.db.pragma('journal_mode = WAL');
|
|
41
|
-
this.db.exec(`
|
|
42
|
-
CREATE TABLE IF NOT EXISTS local_scan_enrichment_stages (
|
|
43
|
-
run_id TEXT NOT NULL,
|
|
44
|
-
stage TEXT NOT NULL,
|
|
45
|
-
input_hash TEXT NOT NULL,
|
|
46
|
-
connection_id TEXT NOT NULL,
|
|
47
|
-
sync_id TEXT NOT NULL,
|
|
48
|
-
mode TEXT NOT NULL,
|
|
49
|
-
status TEXT NOT NULL,
|
|
50
|
-
output_json TEXT,
|
|
51
|
-
error_message TEXT,
|
|
52
|
-
updated_at TEXT NOT NULL,
|
|
53
|
-
PRIMARY KEY (run_id, stage)
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
CREATE INDEX IF NOT EXISTS local_scan_enrichment_stages_run_idx
|
|
57
|
-
ON local_scan_enrichment_stages (run_id, updated_at, stage);
|
|
58
|
-
`);
|
|
57
|
+
this.cache = options.cache ?? new SqliteContentResultCache({ dbPath: options.dbPath });
|
|
59
58
|
}
|
|
60
59
|
async findCompletedStage(input) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!row) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
const parsed = parseStageRow(row);
|
|
78
|
-
return parsed.status === 'completed' ? parsed : null;
|
|
60
|
+
const record = await this.cache.findCompletedResult({
|
|
61
|
+
namespace: namespace(input.stage),
|
|
62
|
+
scopeKey: input.connectionId,
|
|
63
|
+
inputHash: input.inputHash,
|
|
64
|
+
});
|
|
65
|
+
return record ? toScanRecord(record) : null;
|
|
66
|
+
}
|
|
67
|
+
async findLatestCompletedStage(input) {
|
|
68
|
+
const record = await this.cache.findLatestCompletedResult({
|
|
69
|
+
namespace: namespace(input.stage),
|
|
70
|
+
scopeKey: input.connectionId,
|
|
71
|
+
});
|
|
72
|
+
return record ? toScanRecord(record) : null;
|
|
79
73
|
}
|
|
80
74
|
async saveCompletedStage(input) {
|
|
81
|
-
this.
|
|
82
|
-
.prepare(`
|
|
83
|
-
INSERT INTO local_scan_enrichment_stages (
|
|
84
|
-
run_id,
|
|
85
|
-
stage,
|
|
86
|
-
input_hash,
|
|
87
|
-
connection_id,
|
|
88
|
-
sync_id,
|
|
89
|
-
mode,
|
|
90
|
-
status,
|
|
91
|
-
output_json,
|
|
92
|
-
error_message,
|
|
93
|
-
updated_at
|
|
94
|
-
)
|
|
95
|
-
VALUES (
|
|
96
|
-
@runId,
|
|
97
|
-
@stage,
|
|
98
|
-
@inputHash,
|
|
99
|
-
@connectionId,
|
|
100
|
-
@syncId,
|
|
101
|
-
@mode,
|
|
102
|
-
'completed',
|
|
103
|
-
@outputJson,
|
|
104
|
-
NULL,
|
|
105
|
-
@updatedAt
|
|
106
|
-
)
|
|
107
|
-
ON CONFLICT(run_id, stage) DO UPDATE SET
|
|
108
|
-
input_hash = excluded.input_hash,
|
|
109
|
-
connection_id = excluded.connection_id,
|
|
110
|
-
sync_id = excluded.sync_id,
|
|
111
|
-
mode = excluded.mode,
|
|
112
|
-
status = excluded.status,
|
|
113
|
-
output_json = excluded.output_json,
|
|
114
|
-
error_message = excluded.error_message,
|
|
115
|
-
updated_at = excluded.updated_at
|
|
116
|
-
`)
|
|
117
|
-
.run({
|
|
75
|
+
await this.cache.saveCompletedResult({
|
|
118
76
|
runId: input.runId,
|
|
119
|
-
|
|
77
|
+
namespace: namespace(input.stage),
|
|
78
|
+
scopeKey: input.connectionId,
|
|
120
79
|
inputHash: input.inputHash,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
mode: input.mode,
|
|
124
|
-
outputJson: JSON.stringify(input.output),
|
|
80
|
+
output: input.output,
|
|
81
|
+
metadata: metadataFor(input),
|
|
125
82
|
updatedAt: input.updatedAt,
|
|
126
83
|
});
|
|
127
84
|
}
|
|
128
85
|
async saveFailedStage(input) {
|
|
129
|
-
this.
|
|
130
|
-
.prepare(`
|
|
131
|
-
INSERT INTO local_scan_enrichment_stages (
|
|
132
|
-
run_id,
|
|
133
|
-
stage,
|
|
134
|
-
input_hash,
|
|
135
|
-
connection_id,
|
|
136
|
-
sync_id,
|
|
137
|
-
mode,
|
|
138
|
-
status,
|
|
139
|
-
output_json,
|
|
140
|
-
error_message,
|
|
141
|
-
updated_at
|
|
142
|
-
)
|
|
143
|
-
VALUES (
|
|
144
|
-
@runId,
|
|
145
|
-
@stage,
|
|
146
|
-
@inputHash,
|
|
147
|
-
@connectionId,
|
|
148
|
-
@syncId,
|
|
149
|
-
@mode,
|
|
150
|
-
'failed',
|
|
151
|
-
NULL,
|
|
152
|
-
@errorMessage,
|
|
153
|
-
@updatedAt
|
|
154
|
-
)
|
|
155
|
-
ON CONFLICT(run_id, stage) DO UPDATE SET
|
|
156
|
-
input_hash = excluded.input_hash,
|
|
157
|
-
connection_id = excluded.connection_id,
|
|
158
|
-
sync_id = excluded.sync_id,
|
|
159
|
-
mode = excluded.mode,
|
|
160
|
-
status = excluded.status,
|
|
161
|
-
output_json = excluded.output_json,
|
|
162
|
-
error_message = excluded.error_message,
|
|
163
|
-
updated_at = excluded.updated_at
|
|
164
|
-
`)
|
|
165
|
-
.run({
|
|
86
|
+
await this.cache.saveFailedResult({
|
|
166
87
|
runId: input.runId,
|
|
167
|
-
|
|
88
|
+
namespace: namespace(input.stage),
|
|
89
|
+
scopeKey: input.connectionId,
|
|
168
90
|
inputHash: input.inputHash,
|
|
169
|
-
connectionId: input.connectionId,
|
|
170
|
-
syncId: input.syncId,
|
|
171
|
-
mode: input.mode,
|
|
172
91
|
errorMessage: input.errorMessage,
|
|
92
|
+
metadata: metadataFor(input),
|
|
173
93
|
updatedAt: input.updatedAt,
|
|
174
94
|
});
|
|
175
95
|
}
|
|
176
96
|
async listRunStages(runId) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.prepare(`
|
|
182
|
-
SELECT *
|
|
183
|
-
FROM local_scan_enrichment_stages
|
|
184
|
-
WHERE run_id = ?
|
|
185
|
-
ORDER BY updated_at ASC, stage ASC
|
|
186
|
-
`)
|
|
187
|
-
.all(runId);
|
|
188
|
-
return rows.map((row) => parseStageRow(row));
|
|
97
|
+
const records = await this.cache.listRunResults(runId);
|
|
98
|
+
return records
|
|
99
|
+
.filter((record) => record.namespace.startsWith('scan:'))
|
|
100
|
+
.map((record) => toScanRecord(record));
|
|
189
101
|
}
|
|
190
102
|
}
|