@kaelio/ktx 0.8.0 → 0.10.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.
Files changed (183) hide show
  1. package/assets/python/{kaelio_ktx-0.8.0-py3-none-any.whl → kaelio_ktx-0.10.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/clack.d.ts +6 -0
  5. package/dist/clack.js +17 -2
  6. package/dist/cli-program.d.ts +3 -0
  7. package/dist/cli-program.js +42 -2
  8. package/dist/cli-runtime.d.ts +3 -0
  9. package/dist/cli-runtime.js +94 -3
  10. package/dist/commands/setup-commands.js +3 -4
  11. package/dist/connection-recovery.d.ts +34 -0
  12. package/dist/connection-recovery.js +82 -0
  13. package/dist/connection.js +26 -2
  14. package/dist/connectors/bigquery/connector.d.ts +2 -5
  15. package/dist/connectors/bigquery/connector.js +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +2 -5
  17. package/dist/connectors/clickhouse/connector.js +2 -2
  18. package/dist/connectors/mysql/connector.d.ts +7 -6
  19. package/dist/connectors/mysql/connector.js +25 -5
  20. package/dist/connectors/mysql/dialect.d.ts +1 -1
  21. package/dist/connectors/mysql/dialect.js +12 -2
  22. package/dist/connectors/postgres/connector.d.ts +2 -5
  23. package/dist/connectors/postgres/connector.js +2 -2
  24. package/dist/connectors/snowflake/connector.d.ts +2 -5
  25. package/dist/connectors/snowflake/connector.js +2 -2
  26. package/dist/connectors/sqlite/connector.d.ts +2 -5
  27. package/dist/connectors/sqlite/connector.js +2 -2
  28. package/dist/connectors/sqlserver/connector.d.ts +2 -5
  29. package/dist/connectors/sqlserver/connector.js +2 -2
  30. package/dist/context/connections/drivers.d.ts +0 -1
  31. package/dist/context/connections/drivers.js +0 -7
  32. package/dist/context/connections/query-executor.d.ts +2 -1
  33. package/dist/context/core/abort.d.ts +9 -0
  34. package/dist/context/core/abort.js +36 -0
  35. package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
  36. package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
  37. package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
  38. package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
  39. package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
  40. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +30 -0
  41. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +194 -0
  42. package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
  43. package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
  44. package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
  45. package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
  46. package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
  47. package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
  48. package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
  49. package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
  50. package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
  51. package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
  52. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  53. package/dist/context/ingest/final-gate-repair.d.ts +1 -0
  54. package/dist/context/ingest/final-gate-repair.js +1 -0
  55. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  56. package/dist/context/ingest/ingest-bundle.runner.js +127 -53
  57. package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
  58. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
  59. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
  60. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  61. package/dist/context/ingest/local-adapters.js +21 -4
  62. package/dist/context/ingest/local-bundle-runtime.js +13 -5
  63. package/dist/context/ingest/local-ingest.d.ts +1 -0
  64. package/dist/context/ingest/local-ingest.js +13 -3
  65. package/dist/context/ingest/memory-flow/events.js +1 -1
  66. package/dist/context/ingest/memory-flow/schema.js +8 -3
  67. package/dist/context/ingest/memory-flow/types.d.ts +7 -3
  68. package/dist/context/ingest/ports.d.ts +3 -5
  69. package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
  70. package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
  71. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
  72. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  73. package/dist/context/ingest/types.d.ts +1 -0
  74. package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
  75. package/dist/context/llm/ai-sdk-runtime.js +152 -16
  76. package/dist/context/llm/claude-code-runtime.d.ts +6 -4
  77. package/dist/context/llm/claude-code-runtime.js +127 -48
  78. package/dist/context/llm/codex-exec-events.d.ts +20 -0
  79. package/dist/context/llm/codex-exec-events.js +155 -0
  80. package/dist/context/llm/codex-isolation.d.ts +3 -0
  81. package/dist/context/llm/codex-isolation.js +5 -0
  82. package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
  83. package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
  84. package/dist/context/llm/codex-models.d.ts +2 -0
  85. package/dist/context/llm/codex-models.js +17 -0
  86. package/dist/context/llm/codex-runtime-config.d.ts +16 -0
  87. package/dist/context/llm/codex-runtime-config.js +19 -0
  88. package/dist/context/llm/codex-runtime.d.ts +37 -0
  89. package/dist/context/llm/codex-runtime.js +347 -0
  90. package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
  91. package/dist/context/llm/codex-sdk-runner.js +63 -0
  92. package/dist/context/llm/local-config.d.ts +16 -4
  93. package/dist/context/llm/local-config.js +18 -2
  94. package/dist/context/llm/rate-limit-governor.d.ts +103 -0
  95. package/dist/context/llm/rate-limit-governor.js +285 -0
  96. package/dist/context/llm/runtime-port.d.ts +3 -6
  97. package/dist/context/mcp/context-tools.js +43 -13
  98. package/dist/context/project/config.d.ts +14 -0
  99. package/dist/context/project/config.js +37 -2
  100. package/dist/context/scan/types.d.ts +15 -2
  101. package/dist/context/scan/types.js +12 -0
  102. package/dist/context/sl/description-normalization.js +4 -14
  103. package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
  104. package/dist/context/sql-analysis/ports.d.ts +12 -2
  105. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  106. package/dist/context-build-view.d.ts +13 -0
  107. package/dist/context-build-view.js +63 -32
  108. package/dist/demo-metrics.d.ts +0 -2
  109. package/dist/demo-metrics.js +1 -11
  110. package/dist/ingest.d.ts +1 -0
  111. package/dist/ingest.js +32 -3
  112. package/dist/io/buffered-command-io.d.ts +11 -0
  113. package/dist/io/buffered-command-io.js +28 -0
  114. package/dist/io/symbols.d.ts +2 -0
  115. package/dist/io/symbols.js +2 -0
  116. package/dist/llm/types.d.ts +1 -1
  117. package/dist/local-adapters.d.ts +10 -2
  118. package/dist/local-adapters.js +19 -3
  119. package/dist/memory-flow-hud.js +8 -16
  120. package/dist/next-steps.js +1 -2
  121. package/dist/progress-port-adapter.d.ts +6 -0
  122. package/dist/progress-port-adapter.js +18 -0
  123. package/dist/public-ingest.d.ts +20 -1
  124. package/dist/public-ingest.js +228 -42
  125. package/dist/reveal-password-prompt.d.ts +24 -0
  126. package/dist/reveal-password-prompt.js +78 -0
  127. package/dist/scan.js +21 -3
  128. package/dist/setup-context.d.ts +2 -0
  129. package/dist/setup-context.js +133 -27
  130. package/dist/setup-databases.d.ts +18 -1
  131. package/dist/setup-databases.js +378 -249
  132. package/dist/setup-demo-tour.js +1 -0
  133. package/dist/setup-embeddings.js +1 -1
  134. package/dist/setup-models.d.ts +11 -15
  135. package/dist/setup-models.js +140 -276
  136. package/dist/setup-prompts.js +3 -2
  137. package/dist/setup-ready-menu.d.ts +16 -2
  138. package/dist/setup-ready-menu.js +37 -5
  139. package/dist/setup-sources.js +115 -35
  140. package/dist/setup.d.ts +1 -1
  141. package/dist/setup.js +23 -11
  142. package/dist/sl.d.ts +2 -2
  143. package/dist/sl.js +20 -4
  144. package/dist/sql.js +18 -2
  145. package/dist/star-prompt/cache.d.ts +16 -0
  146. package/dist/star-prompt/cache.js +45 -0
  147. package/dist/star-prompt/star-count.d.ts +7 -0
  148. package/dist/star-prompt/star-count.js +66 -0
  149. package/dist/star-prompt/star-line.d.ts +12 -0
  150. package/dist/star-prompt/star-line.js +26 -0
  151. package/dist/status-project.d.ts +11 -0
  152. package/dist/status-project.js +50 -1
  153. package/dist/telemetry/command-hook.d.ts +1 -0
  154. package/dist/telemetry/command-hook.js +3 -1
  155. package/dist/telemetry/emitter.d.ts +10 -0
  156. package/dist/telemetry/emitter.js +31 -0
  157. package/dist/telemetry/events.d.ts +35 -6
  158. package/dist/telemetry/events.js +25 -2
  159. package/dist/telemetry/exception.d.ts +18 -0
  160. package/dist/telemetry/exception.js +162 -0
  161. package/dist/telemetry/identity.d.ts +0 -1
  162. package/dist/telemetry/identity.js +6 -6
  163. package/dist/telemetry/index.d.ts +15 -2
  164. package/dist/telemetry/index.js +15 -3
  165. package/dist/telemetry/redaction-secrets.d.ts +11 -0
  166. package/dist/telemetry/redaction-secrets.js +92 -0
  167. package/dist/telemetry/scrubber.d.ts +10 -0
  168. package/dist/telemetry/scrubber.js +20 -0
  169. package/dist/update-check/cache.d.ts +21 -0
  170. package/dist/update-check/cache.js +38 -0
  171. package/dist/update-check/channel.d.ts +15 -0
  172. package/dist/update-check/channel.js +30 -0
  173. package/dist/update-check/registry.d.ts +1 -0
  174. package/dist/update-check/registry.js +45 -0
  175. package/dist/update-check/update-check.d.ts +43 -0
  176. package/dist/update-check/update-check.js +116 -0
  177. package/package.json +12 -4
  178. package/dist/context/connections/local-query-executor.d.ts +0 -6
  179. package/dist/context/connections/local-query-executor.js +0 -39
  180. package/dist/context/connections/postgres-query-executor.d.ts +0 -25
  181. package/dist/context/connections/postgres-query-executor.js +0 -53
  182. package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
  183. package/dist/context/connections/sqlite-query-executor.js +0 -74
@@ -6,7 +6,7 @@ import { getDialectForDriver } from '../../context/connections/dialects.js';
6
6
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
7
7
  import { constraintDiscoveryWarning, tryConstraintQuery, } from '../../context/scan/constraint-discovery.js';
8
8
  import { scopedTableNames } from '../../context/scan/table-ref.js';
9
- import { createKtxConnectorCapabilities, } from '../../context/scan/types.js';
9
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
10
10
  class DefaultMysqlPoolFactory {
11
11
  createPool(config) {
12
12
  return mysql.createPool(config);
@@ -185,7 +185,7 @@ export class KtxMysqlScanConnector {
185
185
  capabilities = createKtxConnectorCapabilities({
186
186
  tableSampling: true,
187
187
  columnSampling: true,
188
- columnStats: false,
188
+ columnStats: true,
189
189
  readOnlySql: true,
190
190
  nestedAnalysis: true,
191
191
  formalForeignKeys: true,
@@ -219,7 +219,7 @@ export class KtxMysqlScanConnector {
219
219
  return { success: true };
220
220
  }
221
221
  catch (error) {
222
- return { success: false, error: error instanceof Error ? error.message : String(error) };
222
+ return connectorTestFailure(error);
223
223
  }
224
224
  }
225
225
  async introspect(input, _ctx) {
@@ -324,8 +324,28 @@ export class KtxMysqlScanConnector {
324
324
  const values = result.rows.filter((row) => row.length > 0 && row[0] !== null).map((row) => row[0]);
325
325
  return { values, nullCount: null, distinctCount: null };
326
326
  }
327
- async columnStats(_input, _ctx) {
328
- return null;
327
+ async columnStats(input, _ctx) {
328
+ const stats = await this.getColumnStatistics(input.table);
329
+ const value = stats?.cardinalityByColumn.get(input.column);
330
+ return value === undefined
331
+ ? null
332
+ : { min: null, max: null, average: null, nullCount: null, distinctCount: value };
333
+ }
334
+ async getColumnStatistics(table) {
335
+ const schema = table.db ?? this.poolConfig.database;
336
+ const sql = this.dialect.generateColumnStatisticsQuery(schema, table.name);
337
+ if (!sql) {
338
+ return null;
339
+ }
340
+ const rows = await this.queryRaw(sql);
341
+ const cardinalityByColumn = new Map();
342
+ for (const row of rows) {
343
+ const cardinality = Number(row.estimated_cardinality);
344
+ if (Number.isFinite(cardinality) && cardinality >= 0) {
345
+ cardinalityByColumn.set(row.column_name, cardinality);
346
+ }
347
+ }
348
+ return cardinalityByColumn.size > 0 ? { cardinalityByColumn } : null;
329
349
  }
330
350
  async executeReadOnly(input, _ctx) {
331
351
  this.assertConnection(input.connectionId);
@@ -25,7 +25,7 @@ export declare class KtxMysqlDialect implements KtxDialect {
25
25
  getSampleValueAggregation(innerSql: string): string;
26
26
  generateCardinalitySampleQuery(tableName: string, columnName: string, sampleSize: number): string;
27
27
  generateDistinctValuesQuery(tableName: string, columnName: string, limit: number): string;
28
- generateColumnStatisticsQuery(_schemaName: string, _tableName: string): string | null;
28
+ generateColumnStatisticsQuery(schemaName: string, tableName: string): string | null;
29
29
  generateRandomizedCardinalitySampleQuery(tableName: string, columnName: string, sampleSize: number): string;
30
30
  }
31
31
  export {};
@@ -135,8 +135,18 @@ export class KtxMysqlDialect {
135
135
  LIMIT ${limit}
136
136
  `;
137
137
  }
138
- generateColumnStatisticsQuery(_schemaName, _tableName) {
139
- return null;
138
+ generateColumnStatisticsQuery(schemaName, tableName) {
139
+ return `
140
+ SELECT
141
+ COLUMN_NAME AS column_name,
142
+ MAX(CARDINALITY) AS estimated_cardinality
143
+ FROM INFORMATION_SCHEMA.STATISTICS
144
+ WHERE TABLE_SCHEMA = '${schemaName.replace(/'/g, "''")}'
145
+ AND TABLE_NAME = '${tableName.replace(/'/g, "''")}'
146
+ AND CARDINALITY IS NOT NULL
147
+ AND SEQ_IN_INDEX = 1
148
+ GROUP BY COLUMN_NAME
149
+ `;
140
150
  }
141
151
  generateRandomizedCardinalitySampleQuery(tableName, columnName, sampleSize) {
142
152
  return `
@@ -1,4 +1,4 @@
1
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
1
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
2
  export interface KtxPostgresConnectionConfig {
3
3
  driver?: string;
4
4
  host?: string;
@@ -117,10 +117,7 @@ export declare class KtxPostgresScanConnector implements KtxScanConnector {
117
117
  private lastIdlePoolError;
118
118
  private resolvedEndpoint;
119
119
  constructor(options: KtxPostgresScanConnectorOptions);
120
- testConnection(): Promise<{
121
- success: boolean;
122
- error?: string;
123
- }>;
120
+ testConnection(): Promise<KtxConnectorTestResult>;
124
121
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
125
122
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxPostgresTableSampleResult>;
126
123
  sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult>;
@@ -5,7 +5,7 @@ import { getDialectForDriver } from '../../context/connections/dialects.js';
5
5
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
6
6
  import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
7
7
  import { scopedTableNames } from '../../context/scan/table-ref.js';
8
- import { createKtxConnectorCapabilities, } from '../../context/scan/types.js';
8
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
9
9
  import { Pool } from 'pg';
10
10
  const PG_OID_TYPE_MAP = {
11
11
  16: 'boolean',
@@ -231,7 +231,7 @@ export class KtxPostgresScanConnector {
231
231
  return { success: true };
232
232
  }
233
233
  catch (error) {
234
- return { success: false, error: error instanceof Error ? error.message : String(error) };
234
+ return connectorTestFailure(error);
235
235
  }
236
236
  }
237
237
  async introspect(input, _ctx) {
@@ -1,4 +1,4 @@
1
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
1
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
2
  export interface KtxSnowflakeConnectionConfig {
3
3
  driver?: string;
4
4
  authMethod?: 'password' | 'rsa';
@@ -117,10 +117,7 @@ export declare class KtxSnowflakeScanConnector implements KtxScanConnector {
117
117
  private readonly now;
118
118
  private driverInstance;
119
119
  constructor(options: KtxSnowflakeScanConnectorOptions);
120
- testConnection(): Promise<{
121
- success: boolean;
122
- error?: string;
123
- }>;
120
+ testConnection(): Promise<KtxConnectorTestResult>;
124
121
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
125
122
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxTableSampleResult>;
126
123
  sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult>;
@@ -6,7 +6,7 @@ import { getDialectForDriver } from '../../context/connections/dialects.js';
6
6
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
7
7
  import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
8
8
  import { scopedTableNames } from '../../context/scan/table-ref.js';
9
- import { createKtxConnectorCapabilities, } from '../../context/scan/types.js';
9
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
10
10
  import snowflake from 'snowflake-sdk';
11
11
  import { assertSafeSnowflakeIdentifier, quoteSnowflakeIdentifier } from './identifiers.js';
12
12
  import { configureSnowflakeSdkLogger } from './sdk-logger.js';
@@ -282,7 +282,7 @@ class SnowflakeSdkDriver {
282
282
  return { success: true };
283
283
  }
284
284
  catch (error) {
285
- return { success: false, error: error instanceof Error ? error.message : String(error) };
285
+ return connectorTestFailure(error);
286
286
  }
287
287
  }
288
288
  async getPool() {
@@ -1,4 +1,4 @@
1
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
1
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
2
  export interface KtxSqliteConnectionConfig {
3
3
  driver?: string;
4
4
  path?: string;
@@ -39,10 +39,7 @@ export declare class KtxSqliteScanConnector implements KtxScanConnector {
39
39
  private readonly dialect;
40
40
  private db;
41
41
  constructor(options: KtxSqliteScanConnectorOptions);
42
- testConnection(): Promise<{
43
- success: boolean;
44
- error?: string;
45
- }>;
42
+ testConnection(): Promise<KtxConnectorTestResult>;
46
43
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
47
44
  listSchemas(): Promise<string[]>;
48
45
  listTables(_schemas?: string[]): Promise<KtxTableListEntry[]>;
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
6
6
  import { getDialectForDriver } from '../../context/connections/dialects.js';
7
7
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
8
8
  import { normalizeQueryRows } from '../../context/connections/query-executor.js';
9
- import { createKtxConnectorCapabilities } from '../../context/scan/types.js';
9
+ import { connectorTestFailure, createKtxConnectorCapabilities } from '../../context/scan/types.js';
10
10
  import { scopedTableNames } from '../../context/scan/table-ref.js';
11
11
  function stringConfigValue(connection, key) {
12
12
  const value = connection?.[key];
@@ -106,7 +106,7 @@ export class KtxSqliteScanConnector {
106
106
  return { success: true };
107
107
  }
108
108
  catch (error) {
109
- return { success: false, error: error instanceof Error ? error.message : String(error) };
109
+ return connectorTestFailure(error);
110
110
  }
111
111
  }
112
112
  async introspect(input, _ctx) {
@@ -1,4 +1,4 @@
1
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
1
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
2
  export interface KtxSqlServerConnectionConfig {
3
3
  driver?: string;
4
4
  host?: string;
@@ -113,10 +113,7 @@ export declare class KtxSqlServerScanConnector implements KtxScanConnector {
113
113
  private pool;
114
114
  private resolvedEndpoint;
115
115
  constructor(options: KtxSqlServerScanConnectorOptions);
116
- testConnection(): Promise<{
117
- success: boolean;
118
- error?: string;
119
- }>;
116
+ testConnection(): Promise<KtxConnectorTestResult>;
120
117
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
121
118
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxSqlServerTableSampleResult>;
122
119
  sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult>;
@@ -2,7 +2,7 @@ import { assertReadOnlySql } from '../../context/connections/read-only-sql.js';
2
2
  import { getDialectForDriver } from '../../context/connections/dialects.js';
3
3
  import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
4
4
  import { scopedTableNames } from '../../context/scan/table-ref.js';
5
- import { createKtxConnectorCapabilities, } from '../../context/scan/types.js';
5
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
6
6
  import { readFileSync } from 'node:fs';
7
7
  import { homedir } from 'node:os';
8
8
  import { resolve } from 'node:path';
@@ -235,7 +235,7 @@ export class KtxSqlServerScanConnector {
235
235
  return { success: true };
236
236
  }
237
237
  catch (error) {
238
- return { success: false, error: error instanceof Error ? error.message : String(error) };
238
+ return connectorTestFailure(error);
239
239
  }
240
240
  }
241
241
  async introspect(input, _ctx) {
@@ -14,7 +14,6 @@ export interface KtxDriverRegistration {
14
14
  readonly driver: KtxConnectionDriver;
15
15
  readonly scopeConfigKey: KtxScopeConfigKey | null;
16
16
  readonly hasHistoricSqlReader: boolean;
17
- readonly hasLocalQueryExecutor: boolean;
18
17
  load(): Promise<KtxDriverConnectorModule>;
19
18
  }
20
19
  /** @internal */
@@ -7,7 +7,6 @@ export const driverRegistrations = {
7
7
  driver: 'bigquery',
8
8
  scopeConfigKey: 'dataset_ids',
9
9
  hasHistoricSqlReader: true,
10
- hasLocalQueryExecutor: false,
11
10
  load: async () => {
12
11
  const m = await import('../../connectors/bigquery/connector.js');
13
12
  return {
@@ -29,7 +28,6 @@ export const driverRegistrations = {
29
28
  driver: 'clickhouse',
30
29
  scopeConfigKey: 'databases',
31
30
  hasHistoricSqlReader: false,
32
- hasLocalQueryExecutor: false,
33
31
  load: async () => {
34
32
  const m = await import('../../connectors/clickhouse/connector.js');
35
33
  return {
@@ -51,7 +49,6 @@ export const driverRegistrations = {
51
49
  driver: 'mysql',
52
50
  scopeConfigKey: 'schemas',
53
51
  hasHistoricSqlReader: false,
54
- hasLocalQueryExecutor: false,
55
52
  load: async () => {
56
53
  const m = await import('../../connectors/mysql/connector.js');
57
54
  return {
@@ -73,7 +70,6 @@ export const driverRegistrations = {
73
70
  driver: 'postgres',
74
71
  scopeConfigKey: 'schemas',
75
72
  hasHistoricSqlReader: true,
76
- hasLocalQueryExecutor: true,
77
73
  load: async () => {
78
74
  const m = await import('../../connectors/postgres/connector.js');
79
75
  return {
@@ -95,7 +91,6 @@ export const driverRegistrations = {
95
91
  driver: 'sqlite',
96
92
  scopeConfigKey: null,
97
93
  hasHistoricSqlReader: false,
98
- hasLocalQueryExecutor: true,
99
94
  load: async () => {
100
95
  const m = await import('../../connectors/sqlite/connector.js');
101
96
  return {
@@ -117,7 +112,6 @@ export const driverRegistrations = {
117
112
  driver: 'snowflake',
118
113
  scopeConfigKey: 'schema_names',
119
114
  hasHistoricSqlReader: true,
120
- hasLocalQueryExecutor: false,
121
115
  load: async () => {
122
116
  const m = await import('../../connectors/snowflake/connector.js');
123
117
  return {
@@ -139,7 +133,6 @@ export const driverRegistrations = {
139
133
  driver: 'sqlserver',
140
134
  scopeConfigKey: 'schemas',
141
135
  hasHistoricSqlReader: false,
142
- hasLocalQueryExecutor: false,
143
136
  load: async () => {
144
137
  const m = await import('../../connectors/sqlserver/connector.js');
145
138
  return {
@@ -6,7 +6,7 @@ export interface KtxSqlQueryExecutionInput {
6
6
  sql: string;
7
7
  maxRows?: number;
8
8
  }
9
- export interface KtxSqlQueryExecutionResult {
9
+ interface KtxSqlQueryExecutionResult {
10
10
  headers: string[];
11
11
  rows: unknown[][];
12
12
  totalRows: number;
@@ -17,3 +17,4 @@ export interface KtxSqlQueryExecutorPort {
17
17
  execute(input: KtxSqlQueryExecutionInput): Promise<KtxSqlQueryExecutionResult>;
18
18
  }
19
19
  export declare function normalizeQueryRows(rows: unknown[]): unknown[][];
20
+ export {};
@@ -0,0 +1,9 @@
1
+ /** @internal */
2
+ export declare function createAbortError(message?: string): DOMException;
3
+ export declare function isAbortError(error: unknown): boolean;
4
+ /** @internal */
5
+ export declare function throwIfAborted(signal?: AbortSignal): void;
6
+ export declare function linkAbortSignal(parent?: AbortSignal): {
7
+ controller: AbortController;
8
+ dispose: () => void;
9
+ };
@@ -0,0 +1,36 @@
1
+ /** @internal */
2
+ export function createAbortError(message = 'Aborted') {
3
+ return new DOMException(message, 'AbortError');
4
+ }
5
+ export function isAbortError(error) {
6
+ if (error instanceof DOMException && error.name === 'AbortError') {
7
+ return true;
8
+ }
9
+ if (!error || typeof error !== 'object') {
10
+ return false;
11
+ }
12
+ const record = error;
13
+ return record.name === 'AbortError' || record.code === 'ABORT_ERR';
14
+ }
15
+ /** @internal */
16
+ export function throwIfAborted(signal) {
17
+ if (signal?.aborted) {
18
+ throw createAbortError();
19
+ }
20
+ }
21
+ export function linkAbortSignal(parent) {
22
+ const controller = new AbortController();
23
+ if (!parent) {
24
+ return { controller, dispose: () => undefined };
25
+ }
26
+ if (parent.aborted) {
27
+ controller.abort(createAbortError());
28
+ return { controller, dispose: () => undefined };
29
+ }
30
+ const onAbort = () => controller.abort(createAbortError());
31
+ parent.addEventListener('abort', onAbort, { once: true });
32
+ return {
33
+ controller,
34
+ dispose: () => parent.removeEventListener('abort', onAbort),
35
+ };
36
+ }
@@ -154,27 +154,78 @@ export class BigQueryHistoricSqlQueryHistoryReader {
154
154
  }
155
155
  async *fetchAggregated(client, window, config) {
156
156
  const sql = `
157
+ WITH filtered_jobs AS (
158
+ SELECT
159
+ COALESCE(query_info.query_hashes.normalized_literals, TO_HEX(SHA256(query))) AS template_id,
160
+ query,
161
+ user_email,
162
+ creation_time,
163
+ end_time,
164
+ error_result
165
+ FROM ${this.viewPath}
166
+ WHERE job_type = 'QUERY'
167
+ AND statement_type IN ('SELECT', 'MERGE')
168
+ AND creation_time >= ${timestampExpression(window.start)}
169
+ AND creation_time < ${timestampExpression(window.end)}
170
+ AND query IS NOT NULL
171
+ ),
172
+ template_stats AS (
173
+ SELECT
174
+ template_id,
175
+ MIN(query) AS canonical_sql,
176
+ COUNT(*) AS executions,
177
+ COUNT(DISTINCT user_email) AS distinct_users,
178
+ MIN(creation_time) AS first_seen,
179
+ MAX(creation_time) AS last_seen,
180
+ APPROX_QUANTILES(TIMESTAMP_DIFF(end_time, creation_time, MILLISECOND), 100)[OFFSET(50)] AS p50_ms,
181
+ APPROX_QUANTILES(TIMESTAMP_DIFF(end_time, creation_time, MILLISECOND), 100)[OFFSET(95)] AS p95_ms,
182
+ SAFE_DIVIDE(COUNTIF(error_result IS NOT NULL), COUNT(*)) AS error_rate,
183
+ CAST(NULL AS INT64) AS rows_produced
184
+ FROM filtered_jobs
185
+ GROUP BY template_id
186
+ HAVING COUNT(*) >= ${config.minExecutions}
187
+ ),
188
+ template_users AS (
189
+ SELECT
190
+ template_id,
191
+ user_email AS user,
192
+ COUNT(*) AS executions,
193
+ MAX(creation_time) AS last_seen
194
+ FROM filtered_jobs
195
+ GROUP BY template_id, user_email
196
+ )
157
197
  SELECT
158
- query_hash AS template_id,
159
- MIN(query) AS canonical_sql,
160
- COUNT(*) AS executions,
161
- COUNT(DISTINCT user_email) AS distinct_users,
162
- MIN(creation_time) AS first_seen,
163
- MAX(creation_time) AS last_seen,
164
- APPROX_QUANTILES(TIMESTAMP_DIFF(end_time, creation_time, MILLISECOND), 100)[OFFSET(50)] AS p50_ms,
165
- APPROX_QUANTILES(TIMESTAMP_DIFF(end_time, creation_time, MILLISECOND), 100)[OFFSET(95)] AS p95_ms,
166
- SAFE_DIVIDE(COUNTIF(error_result IS NOT NULL), COUNT(*)) AS error_rate,
167
- CAST(NULL AS INT64) AS rows_produced,
168
- TO_JSON_STRING(ARRAY_AGG(STRUCT(user_email AS user, 1 AS executions) ORDER BY creation_time DESC LIMIT 5)) AS top_users
169
- FROM ${this.viewPath}
170
- WHERE job_type = 'QUERY'
171
- AND statement_type IN ('SELECT', 'MERGE')
172
- AND creation_time >= ${timestampExpression(window.start)}
173
- AND creation_time < ${timestampExpression(window.end)}
174
- AND query IS NOT NULL
175
- GROUP BY query_hash
176
- HAVING COUNT(*) >= ${config.minExecutions}
177
- ORDER BY executions DESC`.trim();
198
+ stats.template_id,
199
+ stats.canonical_sql,
200
+ stats.executions,
201
+ stats.distinct_users,
202
+ stats.first_seen,
203
+ stats.last_seen,
204
+ stats.p50_ms,
205
+ stats.p95_ms,
206
+ stats.error_rate,
207
+ stats.rows_produced,
208
+ TO_JSON_STRING(
209
+ ARRAY_AGG(
210
+ STRUCT(users.user AS user, users.executions AS executions)
211
+ ORDER BY users.executions DESC, users.last_seen DESC
212
+ )
213
+ ) AS top_users
214
+ FROM template_stats AS stats
215
+ JOIN template_users AS users
216
+ ON users.template_id = stats.template_id
217
+ GROUP BY
218
+ stats.template_id,
219
+ stats.canonical_sql,
220
+ stats.executions,
221
+ stats.distinct_users,
222
+ stats.first_seen,
223
+ stats.last_seen,
224
+ stats.p50_ms,
225
+ stats.p95_ms,
226
+ stats.error_rate,
227
+ stats.rows_produced
228
+ ORDER BY stats.executions DESC`.trim();
178
229
  const result = await queryClient(client).executeQuery(sql);
179
230
  if (result.error) {
180
231
  throw grantsError(result.error);
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { readFile, readdir } from 'node:fs/promises';
3
3
  import { join, relative } from 'node:path';
4
+ import { tableRefKey } from '../../../scan/table-ref.js';
4
5
  import { isHistoricSqlPatternInputShardPath } from './pattern-inputs.js';
5
6
  import { stagedManifestSchema, stagedPatternsInputSchema, stagedTableInputSchema } from './types.js';
6
7
  async function walk(root) {
@@ -30,7 +31,7 @@ export async function chunkHistoricSqlUnifiedStagedDir(stagedDir, diffSet) {
30
31
  }
31
32
  const table = stagedTableInputSchema.parse(await readJson(stagedDir, path));
32
33
  workUnits.push({
33
- unitKey: `historic-sql-table-${safeUnitKey(table.table)}`,
34
+ unitKey: `historic-sql-table-${safeUnitKey(tableRefKey(table.tableRef))}`,
34
35
  displayLabel: `Historic SQL usage: ${table.table}`,
35
36
  rawFiles: [path],
36
37
  dependencyPaths: ['manifest.json'],
@@ -1,5 +1,14 @@
1
1
  import type { HistoricSqlDialect } from './types.js';
2
2
  export declare function isQueryHistoryEnabled(connection: unknown): boolean;
3
+ /**
4
+ * Resolves the query-history dialect from the connection's driver capability
5
+ * alone, ignoring whether query history is enabled in ktx.yaml. Use this on the
6
+ * adapter-registration path when query history has been explicitly requested
7
+ * for the run (e.g. via `--query-history`, which is itself the opt-in): the
8
+ * persisted `context.queryHistory.enabled` flag must not gate registration.
9
+ * Returns null when the connection's driver has no query-history reader.
10
+ */
11
+ export declare function historicSqlDialectForConnectionDriver(connection: unknown): HistoricSqlDialect | null;
3
12
  /**
4
13
  * Resolves the query-history dialect for a connection. Returns null when
5
14
  * query history is disabled, or when the connection's driver has no
@@ -18,6 +18,20 @@ function historicSqlDialectForDriver(driver) {
18
18
  export function isQueryHistoryEnabled(connection) {
19
19
  return queryHistoryRecord(connection)?.enabled === true;
20
20
  }
21
+ /**
22
+ * Resolves the query-history dialect from the connection's driver capability
23
+ * alone, ignoring whether query history is enabled in ktx.yaml. Use this on the
24
+ * adapter-registration path when query history has been explicitly requested
25
+ * for the run (e.g. via `--query-history`, which is itself the opt-in): the
26
+ * persisted `context.queryHistory.enabled` flag must not gate registration.
27
+ * Returns null when the connection's driver has no query-history reader.
28
+ */
29
+ export function historicSqlDialectForConnectionDriver(connection) {
30
+ const conn = recordOrNull(connection);
31
+ const driver = String(conn?.driver ?? '').toLowerCase();
32
+ const registration = getDriverRegistration(driver);
33
+ return registration?.hasHistoricSqlReader ? historicSqlDialectForDriver(registration.driver) : null;
34
+ }
21
35
  /**
22
36
  * Resolves the query-history dialect for a connection. Returns null when
23
37
  * query history is disabled, or when the connection's driver has no
@@ -27,8 +41,5 @@ export function queryHistoryDialectForConnection(connection) {
27
41
  if (!isQueryHistoryEnabled(connection)) {
28
42
  return null;
29
43
  }
30
- const conn = recordOrNull(connection);
31
- const driver = String(conn?.driver ?? '').toLowerCase();
32
- const registration = getDriverRegistration(driver);
33
- return registration?.hasHistoricSqlReader ? historicSqlDialectForDriver(registration.driver) : null;
44
+ return historicSqlDialectForConnectionDriver(connection);
34
45
  }
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from 'node:buffer';
2
+ import { tableRefKey } from '../../../scan/table-ref.js';
2
3
  const HISTORIC_SQL_PATTERN_WORKUNIT_DIR = 'patterns-input';
3
4
  /** @internal */
4
5
  export const HISTORIC_SQL_PATTERN_WORKUNIT_MAX_BYTES = 110_000;
@@ -19,12 +20,17 @@ function sortedAuditTemplates(templates) {
19
20
  function sortedPatternCandidates(templates) {
20
21
  return [...templates]
21
22
  .filter((template) => template.tablesTouched.length >= 2)
22
- .map((template) => ({ ...template, tablesTouched: [...template.tablesTouched].sort() }))
23
+ .map((template) => ({
24
+ ...template,
25
+ tablesTouched: [...template.tablesTouched].sort((left, right) => tableRefKey(left).localeCompare(tableRefKey(right))),
26
+ }))
23
27
  .sort((left, right) => {
24
28
  const cardinality = right.tablesTouched.length - left.tablesTouched.length;
25
29
  if (cardinality !== 0)
26
30
  return cardinality;
27
- const tableSignature = left.tablesTouched.join('\0').localeCompare(right.tablesTouched.join('\0'));
31
+ const leftSignature = left.tablesTouched.map(tableRefKey).join('\0');
32
+ const rightSignature = right.tablesTouched.map(tableRefKey).join('\0');
33
+ const tableSignature = leftSignature.localeCompare(rightSignature);
28
34
  if (tableSignature !== 0)
29
35
  return tableSignature;
30
36
  return left.id.localeCompare(right.id);
@@ -0,0 +1,30 @@
1
+ import type { KtxLlmRuntimePort } from '../../../../context/llm/runtime-port.js';
2
+ import type { SqlAnalysisPort } from '../../../../context/sql-analysis/ports.js';
3
+ import { type HistoricSqlDialect, type HistoricSqlReader } from './types.js';
4
+ export interface QueryHistoryFilterProposal {
5
+ excludedRoles: Array<{
6
+ role: string;
7
+ reason: string;
8
+ pattern: string;
9
+ }>;
10
+ consideredRoleCount: number;
11
+ skipped: {
12
+ reason: 'no-llm' | 'no-daemon' | 'no-in-scope-history' | 'user-block-present';
13
+ } | null;
14
+ warnings: string[];
15
+ parseFailedTemplateIds: string[];
16
+ }
17
+ export interface ProposeQueryHistoryServiceAccountFiltersInput {
18
+ connectionId: string;
19
+ dialect: HistoricSqlDialect;
20
+ queryClient: unknown;
21
+ reader: HistoricSqlReader;
22
+ sqlAnalysis: SqlAnalysisPort;
23
+ llmRuntime: KtxLlmRuntimePort | null;
24
+ pullConfig: unknown;
25
+ now?: Date;
26
+ userServiceAccountsPresent?: boolean;
27
+ }
28
+ /** @internal */
29
+ export declare function regexEscapeForExactRolePattern(role: string): string;
30
+ export declare function proposeQueryHistoryServiceAccountFilters(input: ProposeQueryHistoryServiceAccountFiltersInput): Promise<QueryHistoryFilterProposal>;