@kaelio/ktx 0.1.1 → 0.3.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 (146) hide show
  1. package/assets/python/{kaelio_ktx-0.1.1-py3-none-any.whl → kaelio_ktx-0.3.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/admin-reindex.d.ts +15 -0
  4. package/dist/admin-reindex.js +161 -0
  5. package/dist/admin-reindex.test.js +116 -0
  6. package/dist/{dev.d.ts → admin.d.ts} +1 -1
  7. package/dist/{dev.js → admin.js} +14 -12
  8. package/dist/{dev.test.js → admin.test.js} +36 -31
  9. package/dist/cli-program.js +7 -7
  10. package/dist/cli-program.test.js +1 -3
  11. package/dist/cli-project.d.ts +18 -0
  12. package/dist/cli-project.js +52 -0
  13. package/dist/cli-project.test.d.ts +1 -0
  14. package/dist/cli-project.test.js +149 -0
  15. package/dist/cli-runtime.d.ts +2 -2
  16. package/dist/cli-runtime.js +2 -8
  17. package/dist/commands/connection-commands.js +11 -10
  18. package/dist/commands/connection-selection.d.ts +11 -0
  19. package/dist/commands/connection-selection.js +9 -0
  20. package/dist/commands/ingest-commands.js +32 -26
  21. package/dist/commands/knowledge-commands.js +17 -28
  22. package/dist/commands/mcp-commands.js +17 -11
  23. package/dist/commands/runtime-commands.js +2 -2
  24. package/dist/commands/sl-commands.js +27 -32
  25. package/dist/context-build-view.js +1 -1
  26. package/dist/doctor.test.js +4 -4
  27. package/dist/example-smoke.test.js +3 -3
  28. package/dist/index.test.js +97 -85
  29. package/dist/ingest.js +9 -2
  30. package/dist/ingest.test.js +27 -3
  31. package/dist/io/print-list.test.js +4 -4
  32. package/dist/knowledge.js +1 -1
  33. package/dist/managed-local-embeddings.d.ts +0 -2
  34. package/dist/managed-local-embeddings.js +2 -5
  35. package/dist/managed-local-embeddings.test.js +5 -8
  36. package/dist/managed-python-command.js +2 -2
  37. package/dist/managed-python-command.test.js +3 -3
  38. package/dist/managed-python-daemon.js +2 -2
  39. package/dist/managed-python-daemon.test.js +1 -1
  40. package/dist/managed-python-http.js +3 -3
  41. package/dist/managed-python-http.test.js +6 -6
  42. package/dist/managed-python-runtime.d.ts +1 -1
  43. package/dist/managed-python-runtime.js +3 -3
  44. package/dist/managed-python-runtime.test.js +2 -2
  45. package/dist/memory-flow-tui.test.js +2 -2
  46. package/dist/next-steps.d.ts +6 -6
  47. package/dist/next-steps.js +4 -4
  48. package/dist/next-steps.test.js +5 -5
  49. package/dist/print-command-tree.js +0 -2
  50. package/dist/print-command-tree.test.js +1 -1
  51. package/dist/public-ingest.d.ts +4 -2
  52. package/dist/public-ingest.js +12 -8
  53. package/dist/public-ingest.test.js +7 -3
  54. package/dist/release-version.d.ts +1 -5
  55. package/dist/release-version.js +2 -39
  56. package/dist/runtime-requirements.js +1 -1
  57. package/dist/runtime.js +6 -6
  58. package/dist/runtime.test.js +8 -8
  59. package/dist/scan.js +7 -2
  60. package/dist/scan.test.js +3 -3
  61. package/dist/setup-agents.js +3 -3
  62. package/dist/setup-agents.test.js +1 -1
  63. package/dist/setup-embeddings.js +2 -2
  64. package/dist/setup-embeddings.test.js +5 -5
  65. package/dist/setup-runtime.test.js +3 -3
  66. package/dist/sl.js +1 -1
  67. package/dist/standalone-smoke.test.js +6 -2
  68. package/node_modules/@ktx/context/dist/core/git.service.d.ts +1 -0
  69. package/node_modules/@ktx/context/dist/core/git.service.js +12 -0
  70. package/node_modules/@ktx/context/dist/index-sync/index.d.ts +2 -0
  71. package/node_modules/@ktx/context/dist/index-sync/index.js +1 -0
  72. package/node_modules/@ktx/context/dist/index-sync/reindex.d.ts +20 -0
  73. package/node_modules/@ktx/context/dist/index-sync/reindex.js +141 -0
  74. package/node_modules/@ktx/context/dist/index-sync/reindex.test.d.ts +1 -0
  75. package/node_modules/@ktx/context/dist/index-sync/reindex.test.js +139 -0
  76. package/node_modules/@ktx/context/dist/index-sync/types.d.ts +29 -0
  77. package/node_modules/@ktx/context/dist/index-sync/types.js +1 -0
  78. package/node_modules/@ktx/context/dist/index.d.ts +1 -0
  79. package/node_modules/@ktx/context/dist/index.js +1 -0
  80. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +2 -1
  81. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +18 -0
  82. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +6 -6
  83. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +5 -0
  84. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +48 -0
  85. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +83 -0
  86. package/node_modules/@ktx/context/dist/ingest/adapters/live-database/daemon-introspection.js +4 -1
  87. package/node_modules/@ktx/context/dist/ingest/adapters/live-database/daemon-introspection.test.js +32 -0
  88. package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +22 -0
  89. package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +95 -0
  90. package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.d.ts +1 -0
  91. package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +114 -0
  92. package/node_modules/@ktx/context/dist/ingest/index.d.ts +1 -2
  93. package/node_modules/@ktx/context/dist/ingest/index.js +0 -1
  94. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +2 -0
  95. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +166 -0
  96. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +235 -45
  97. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +193 -38
  98. package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +22 -3
  99. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +3 -4
  100. package/node_modules/@ktx/context/dist/ingest/local-ingest.js +0 -7
  101. package/node_modules/@ktx/context/dist/ingest/local-stage-ingest.js +15 -5
  102. package/node_modules/@ktx/context/dist/ingest/local-stage-ingest.test.js +29 -0
  103. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +4 -4
  104. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
  105. package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
  106. package/node_modules/@ktx/context/dist/ingest/ports.d.ts +1 -20
  107. package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +73 -2
  108. package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +27 -0
  109. package/node_modules/@ktx/context/dist/ingest/reports.d.ts +23 -5
  110. package/node_modules/@ktx/context/dist/ingest/reports.js +7 -24
  111. package/node_modules/@ktx/context/dist/ingest/types.d.ts +33 -0
  112. package/node_modules/@ktx/context/dist/llm/index.d.ts +1 -1
  113. package/node_modules/@ktx/context/dist/llm/index.js +1 -1
  114. package/node_modules/@ktx/context/dist/llm/local-config.d.ts +0 -1
  115. package/node_modules/@ktx/context/dist/llm/local-config.js +2 -12
  116. package/node_modules/@ktx/context/dist/llm/local-config.test.js +2 -23
  117. package/node_modules/@ktx/context/dist/memory/local-memory.js +9 -3
  118. package/node_modules/@ktx/context/dist/package-exports.test.js +2 -2
  119. package/node_modules/@ktx/context/dist/project/config.d.ts +16 -0
  120. package/node_modules/@ktx/context/dist/project/driver-schemas.d.ts +8 -0
  121. package/node_modules/@ktx/context/dist/project/driver-schemas.js +4 -0
  122. package/node_modules/@ktx/context/dist/scan/enabled-tables.d.ts +3 -0
  123. package/node_modules/@ktx/context/dist/scan/enabled-tables.js +15 -0
  124. package/node_modules/@ktx/context/dist/scan/local-scan.d.ts +2 -4
  125. package/node_modules/@ktx/context/dist/scan/local-scan.js +2 -15
  126. package/node_modules/@ktx/context/dist/sl/ports.d.ts +3 -3
  127. package/node_modules/@ktx/context/dist/sl/sl-search.service.d.ts +3 -2
  128. package/node_modules/@ktx/context/dist/sl/sl-search.service.js +47 -45
  129. package/node_modules/@ktx/context/dist/sl/sl-search.service.test.js +61 -0
  130. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.d.ts +4 -3
  131. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.js +15 -5
  132. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.test.js +24 -0
  133. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.d.ts +3 -2
  134. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.js +62 -51
  135. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.test.js +59 -3
  136. package/node_modules/@ktx/context/dist/wiki/ports.d.ts +3 -3
  137. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.d.ts +33 -0
  138. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.js +155 -2
  139. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.test.js +26 -0
  140. package/node_modules/@ktx/context/package.json +5 -0
  141. package/package.json +1 -1
  142. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +0 -4
  143. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +0 -38
  144. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +0 -63
  145. /package/dist/{dev.test.d.ts → admin-reindex.test.d.ts} +0 -0
  146. /package/{node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.d.ts → dist/admin.test.d.ts} +0 -0
@@ -58,6 +58,7 @@ export class SqliteKnowledgeIndex {
58
58
  path TEXT PRIMARY KEY,
59
59
  key TEXT NOT NULL,
60
60
  scope TEXT NOT NULL,
61
+ scope_id TEXT,
61
62
  summary TEXT NOT NULL,
62
63
  content TEXT NOT NULL,
63
64
  tags TEXT NOT NULL,
@@ -81,6 +82,9 @@ export class SqliteKnowledgeIndex {
81
82
  if (!columnNames.has('embedding_json')) {
82
83
  this.db.exec('ALTER TABLE knowledge_pages ADD COLUMN embedding_json TEXT');
83
84
  }
85
+ if (!columnNames.has('scope_id')) {
86
+ this.db.exec('ALTER TABLE knowledge_pages ADD COLUMN scope_id TEXT');
87
+ }
84
88
  }
85
89
  sync(pages) {
86
90
  const keepPaths = pages.map((page) => page.path);
@@ -91,11 +95,12 @@ export class SqliteKnowledgeIndex {
91
95
  ? this.db.prepare('DELETE FROM knowledge_pages_fts')
92
96
  : this.db.prepare(`DELETE FROM knowledge_pages_fts WHERE path NOT IN (${keepPaths.map(() => '?').join(', ')})`);
93
97
  const upsertPage = this.db.prepare(`
94
- INSERT INTO knowledge_pages (path, key, scope, summary, content, tags, search_text, embedding_json)
95
- VALUES (@path, @key, @scope, @summary, @content, @tags, @searchText, @embeddingJson)
98
+ INSERT INTO knowledge_pages (path, key, scope, scope_id, summary, content, tags, search_text, embedding_json)
99
+ VALUES (@path, @key, @scope, @scopeId, @summary, @content, @tags, @searchText, @embeddingJson)
96
100
  ON CONFLICT(path) DO UPDATE SET
97
101
  key = excluded.key,
98
102
  scope = excluded.scope,
103
+ scope_id = excluded.scope_id,
99
104
  summary = excluded.summary,
100
105
  content = excluded.content,
101
106
  tags = excluded.tags,
@@ -116,6 +121,7 @@ export class SqliteKnowledgeIndex {
116
121
  path: page.path,
117
122
  key: page.key,
118
123
  scope: page.scope,
124
+ scopeId: page.scopeId ?? null,
119
125
  summary: page.summary,
120
126
  content: searchText,
121
127
  tags: page.tags.join(' '),
@@ -205,4 +211,151 @@ export class SqliteKnowledgeIndex {
205
211
  score: scoreFromRank(row.rawScore),
206
212
  }));
207
213
  }
214
+ pathForPage(scope, scopeId, pageKey) {
215
+ return scope === 'GLOBAL' ? `wiki/global/${pageKey}.md` : `wiki/user/${scopeId ?? 'local'}/${pageKey}.md`;
216
+ }
217
+ async upsertPage(params) {
218
+ const path = this.pathForPage(params.scope, params.scopeId, params.pageKey);
219
+ const row = {
220
+ path,
221
+ key: params.pageKey,
222
+ scope: params.scope,
223
+ scopeId: params.scopeId,
224
+ summary: params.summary,
225
+ content: params.searchText,
226
+ tags: '',
227
+ searchText: params.searchText,
228
+ embeddingJson: params.embedding && params.embedding.length > 0 ? JSON.stringify(params.embedding) : null,
229
+ };
230
+ const write = this.db.transaction(() => {
231
+ this.db
232
+ .prepare(`
233
+ INSERT INTO knowledge_pages (path, key, scope, scope_id, summary, content, tags, search_text, embedding_json)
234
+ VALUES (@path, @key, @scope, @scopeId, @summary, @content, @tags, @searchText, @embeddingJson)
235
+ ON CONFLICT(path) DO UPDATE SET
236
+ key = excluded.key,
237
+ scope = excluded.scope,
238
+ scope_id = excluded.scope_id,
239
+ summary = excluded.summary,
240
+ content = excluded.content,
241
+ tags = excluded.tags,
242
+ search_text = excluded.search_text,
243
+ embedding_json = excluded.embedding_json
244
+ `)
245
+ .run(row);
246
+ this.db.prepare('DELETE FROM knowledge_pages_fts WHERE path = @path').run(row);
247
+ this.db
248
+ .prepare(`
249
+ INSERT INTO knowledge_pages_fts (path, key, summary, content, tags)
250
+ VALUES (@path, @key, @summary, @content, @tags)
251
+ `)
252
+ .run(row);
253
+ });
254
+ write();
255
+ }
256
+ async getExistingSearchTexts(scope, scopeId) {
257
+ const rows = this.db
258
+ .prepare(`
259
+ SELECT key, search_text, embedding_json
260
+ FROM knowledge_pages
261
+ WHERE scope = ?
262
+ AND scope_id IS ?
263
+ ORDER BY key ASC
264
+ `)
265
+ .all(scope, scopeId);
266
+ return new Map(rows.map((row) => [row.key, { searchText: row.search_text, hasEmbedding: row.embedding_json !== null }]));
267
+ }
268
+ async deleteStale(scope, scopeId, keepKeys) {
269
+ if (keepKeys.length === 0) {
270
+ return this.deleteByScope(scope, scopeId);
271
+ }
272
+ const placeholders = keepKeys.map(() => '?').join(', ');
273
+ const stale = this.db
274
+ .prepare(`
275
+ SELECT key
276
+ FROM knowledge_pages
277
+ WHERE scope = ?
278
+ AND scope_id IS ?
279
+ AND key NOT IN (${placeholders})
280
+ `)
281
+ .all(scope, scopeId, ...keepKeys);
282
+ for (const row of stale) {
283
+ await this.deleteByKey(scope, scopeId, row.key);
284
+ }
285
+ return stale.length;
286
+ }
287
+ async deleteByScope(scope, scopeId) {
288
+ return this.clear(scope, scopeId);
289
+ }
290
+ async deleteByKey(scope, scopeId, pageKey) {
291
+ const path = this.pathForPage(scope, scopeId, pageKey);
292
+ const remove = this.db.transaction(() => {
293
+ this.db.prepare('DELETE FROM knowledge_pages_fts WHERE path = ?').run(path);
294
+ const result = this.db.prepare('DELETE FROM knowledge_pages WHERE path = ?').run(path);
295
+ return Number(result.changes);
296
+ });
297
+ return remove();
298
+ }
299
+ clear(scope, scopeId) {
300
+ const rows = this.db
301
+ .prepare('SELECT path FROM knowledge_pages WHERE scope = ? AND scope_id IS ?')
302
+ .all(scope, scopeId);
303
+ const remove = this.db.transaction((paths) => {
304
+ for (const path of paths) {
305
+ this.db.prepare('DELETE FROM knowledge_pages_fts WHERE path = ?').run(path);
306
+ this.db.prepare('DELETE FROM knowledge_pages WHERE path = ?').run(path);
307
+ }
308
+ });
309
+ remove(rows.map((row) => row.path));
310
+ return rows.length;
311
+ }
312
+ async applyDiffTransactional(params) {
313
+ void params.runId;
314
+ for (const page of params.upserts) {
315
+ await this.upsertPage(page);
316
+ }
317
+ for (const page of params.deletes) {
318
+ await this.deleteByKey(page.scope, page.scopeId, page.pageKey);
319
+ }
320
+ }
321
+ async findPageByKey(scope, scopeId, pageKey) {
322
+ const path = this.pathForPage(scope, scopeId, pageKey);
323
+ const row = this.db.prepare('SELECT path, key FROM knowledge_pages WHERE path = ?').get(path);
324
+ return row ? { id: row.path, page_key: row.key } : null;
325
+ }
326
+ async listPagesForUser(userId) {
327
+ const rows = this.db
328
+ .prepare(`
329
+ SELECT path, key, scope, scope_id, summary, tags
330
+ FROM knowledge_pages
331
+ WHERE scope = 'GLOBAL'
332
+ OR (scope = 'USER' AND scope_id = ?)
333
+ ORDER BY scope ASC, key ASC
334
+ `)
335
+ .all(userId);
336
+ return rows.map((row) => ({
337
+ id: row.path,
338
+ page_key: row.key,
339
+ summary: row.summary,
340
+ scope: row.scope,
341
+ scope_id: row.scope_id,
342
+ tags: row.tags.split(/\s+/).filter(Boolean),
343
+ }));
344
+ }
345
+ async getUserPageCount(userId) {
346
+ const row = this.db
347
+ .prepare("SELECT COUNT(*) AS count FROM knowledge_pages WHERE scope = 'USER' AND scope_id = ?")
348
+ .get(userId);
349
+ return row.count;
350
+ }
351
+ async incrementUsageCount() { }
352
+ async searchRRF(userId, _embedding, queryText, limit) {
353
+ const allowedPages = new Map((await this.listPagesForUser(userId)).map((page) => [page.id, page]));
354
+ return this.search(queryText, limit)
355
+ .map((row) => {
356
+ const page = allowedPages.get(row.path);
357
+ return page ? { pageKey: page.page_key, summary: page.summary, rrfScore: row.score } : null;
358
+ })
359
+ .filter((row) => row !== null);
360
+ }
208
361
  }
@@ -54,6 +54,32 @@ describe('SqliteKnowledgeIndex', () => {
54
54
  index.rebuild([page()]);
55
55
  expect(index.search('churn', 10)).toEqual([]);
56
56
  });
57
+ it('clear removes one wiki scope and leaves other scopes intact', async () => {
58
+ const index = new SqliteKnowledgeIndex({ dbPath });
59
+ index.sync([
60
+ page({ path: 'wiki/global/revenue.md', key: 'revenue', scope: 'GLOBAL', scopeId: null }),
61
+ page({
62
+ path: 'wiki/user/local/revenue.md',
63
+ key: 'revenue',
64
+ scope: 'USER',
65
+ scopeId: 'local',
66
+ summary: 'Local revenue',
67
+ content: 'Local revenue notes.',
68
+ }),
69
+ page({
70
+ path: 'wiki/user/alex/revenue.md',
71
+ key: 'revenue',
72
+ scope: 'USER',
73
+ scopeId: 'alex',
74
+ summary: 'Alex revenue',
75
+ content: 'Alex revenue notes.',
76
+ }),
77
+ ]);
78
+ expect(index.clear('USER', 'local')).toBe(1);
79
+ expect(index.search('Local', 10)).toEqual([]);
80
+ expect(index.search('Alex', 10)).toEqual([expect.objectContaining({ path: 'wiki/user/alex/revenue.md' })]);
81
+ expect(index.search('definition', 10)).toEqual([expect.objectContaining({ path: 'wiki/global/revenue.md' })]);
82
+ });
57
83
  it('exposes existing search text and embedding state for incremental refresh', () => {
58
84
  const index = new SqliteKnowledgeIndex({ dbPath });
59
85
  index.sync([page({ path: 'wiki/global/revenue.md', key: 'revenue', embedding: [1, 0] })]);
@@ -46,6 +46,11 @@
46
46
  "import": "./dist/ingest/metabase-mapping.js",
47
47
  "default": "./dist/ingest/metabase-mapping.js"
48
48
  },
49
+ "./index-sync": {
50
+ "types": "./dist/index-sync/index.d.ts",
51
+ "import": "./dist/index-sync/index.js",
52
+ "default": "./dist/index-sync/index.js"
53
+ },
49
54
  "./scan": {
50
55
  "types": "./dist/scan/index.d.ts",
51
56
  "import": "./dist/scan/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaelio/ktx",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Standalone KTX context layer for database agents",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1,4 +0,0 @@
1
- import type { IngestBundlePostProcessorInput, IngestBundlePostProcessorPort, IngestBundlePostProcessorResult } from '../../ports.js';
2
- export declare class HistoricSqlProjectionPostProcessor implements IngestBundlePostProcessorPort {
3
- run(input: IngestBundlePostProcessorInput): Promise<IngestBundlePostProcessorResult>;
4
- }
@@ -1,38 +0,0 @@
1
- import { createSimpleGit } from '../../../core/git-env.js';
2
- import { projectHistoricSqlEvidence } from './projection.js';
3
- async function commitProjectionChanges(workdir) {
4
- const git = createSimpleGit(workdir);
5
- if (!(await git.checkIsRepo().catch(() => false))) {
6
- return;
7
- }
8
- const status = await git.status();
9
- const paths = status.files
10
- .map((file) => file.path)
11
- .filter((path) => path.startsWith('semantic-layer/') || path.startsWith('wiki/global/historic-sql'));
12
- if (paths.length === 0) {
13
- return;
14
- }
15
- await git.add(paths);
16
- const staged = await git.diff(['--cached', '--name-only']);
17
- if (!staged.trim()) {
18
- return;
19
- }
20
- await git.commit('Project historic SQL evidence', { '--author': 'System User <system@example.com>' });
21
- }
22
- export class HistoricSqlProjectionPostProcessor {
23
- async run(input) {
24
- const projection = await projectHistoricSqlEvidence({
25
- workdir: input.workdir,
26
- connectionId: input.connectionId,
27
- syncId: input.syncId,
28
- runId: input.runId,
29
- });
30
- await commitProjectionChanges(input.workdir);
31
- return {
32
- result: projection,
33
- warnings: projection.warnings,
34
- errors: [],
35
- touchedSources: projection.touchedSources,
36
- };
37
- }
38
- }
@@ -1,63 +0,0 @@
1
- import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import YAML from 'yaml';
5
- import { describe, expect, it } from 'vitest';
6
- import { HistoricSqlProjectionPostProcessor } from './post-processor.js';
7
- async function tempWorkdir() {
8
- return mkdtemp(join(tmpdir(), 'historic-sql-post-processor-'));
9
- }
10
- async function writeJson(root, relPath, value) {
11
- const target = join(root, relPath);
12
- await mkdir(join(target, '..'), { recursive: true });
13
- await writeFile(target, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
14
- }
15
- describe('HistoricSqlProjectionPostProcessor', () => {
16
- it('projects current run evidence before the ingest squash commit', async () => {
17
- const workdir = await tempWorkdir();
18
- await mkdir(join(workdir, 'semantic-layer/warehouse/_schema'), { recursive: true });
19
- await writeFile(join(workdir, 'semantic-layer/warehouse/_schema/public.yaml'), YAML.stringify({ tables: { orders: { table: 'public.orders', columns: [{ name: 'id', type: 'string' }] } } }), 'utf-8');
20
- await writeJson(workdir, 'raw-sources/warehouse/historic-sql/sync-1/manifest.json', {
21
- source: 'historic-sql',
22
- connectionId: 'warehouse',
23
- dialect: 'postgres',
24
- fetchedAt: '2026-05-11T00:00:00.000Z',
25
- windowStart: '2026-02-10T00:00:00.000Z',
26
- windowEnd: '2026-05-11T00:00:00.000Z',
27
- snapshotRowCount: 1,
28
- touchedTableCount: 1,
29
- parseFailures: 0,
30
- warnings: [],
31
- probeWarnings: [],
32
- staleArchiveAfterDays: 90,
33
- });
34
- await writeJson(workdir, 'raw-sources/warehouse/historic-sql/sync-1/tables/public.orders.json', { table: 'public.orders' });
35
- await writeJson(workdir, '.ktx/ingest-evidence/historic-sql/run-1/orders.json', {
36
- kind: 'table_usage',
37
- connectionId: 'warehouse',
38
- table: 'public.orders',
39
- rawPath: 'tables/public.orders.json',
40
- usage: {
41
- narrative: 'Orders are repeatedly queried by lifecycle status.',
42
- frequencyTier: 'high',
43
- commonFilters: ['status'],
44
- commonJoins: [],
45
- staleSince: null,
46
- },
47
- });
48
- const result = await new HistoricSqlProjectionPostProcessor().run({
49
- connectionId: 'warehouse',
50
- sourceKey: 'historic-sql',
51
- syncId: 'sync-1',
52
- jobId: 'job-1',
53
- runId: 'run-1',
54
- workdir,
55
- parseArtifacts: null,
56
- });
57
- expect(result.errors).toEqual([]);
58
- expect(result.warnings).toEqual([]);
59
- expect(result.touchedSources).toEqual([{ connectionId: 'warehouse', sourceName: 'orders' }]);
60
- expect(result.result).toMatchObject({ tableUsageMerged: 1 });
61
- await expect(readFile(join(workdir, 'semantic-layer/warehouse/_schema/public.yaml'), 'utf-8')).resolves.toContain('Orders are repeatedly queried by lifecycle status.');
62
- });
63
- });
File without changes