@kaelio/ktx 0.11.0 → 0.13.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 (212) hide show
  1. package/assets/python/kaelio_ktx-0.13.0-py3-none-any.whl +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/admin.js +1 -1
  5. package/dist/clack.d.ts +16 -0
  6. package/dist/clack.js +37 -6
  7. package/dist/claude-code-prompt-caching.js +1 -1
  8. package/dist/cli-program.js +3 -3
  9. package/dist/cli-runtime.js +2 -2
  10. package/dist/commands/connection-commands.js +1 -1
  11. package/dist/commands/ingest-commands.js +4 -4
  12. package/dist/commands/mcp-commands.js +12 -12
  13. package/dist/commands/runtime-commands.js +4 -4
  14. package/dist/commands/setup-commands.js +19 -5
  15. package/dist/commands/sl-commands.js +1 -1
  16. package/dist/commands/sql-commands.js +1 -1
  17. package/dist/commands/status-commands.js +1 -1
  18. package/dist/connection.js +15 -3
  19. package/dist/connectors/bigquery/connector.js +1 -14
  20. package/dist/connectors/clickhouse/connector.js +2 -16
  21. package/dist/connectors/duckdb/federated-attach.d.ts +7 -0
  22. package/dist/connectors/duckdb/federated-attach.js +86 -0
  23. package/dist/connectors/duckdb/federated-executor.d.ts +5 -0
  24. package/dist/connectors/duckdb/federated-executor.js +59 -0
  25. package/dist/connectors/mysql/connector.js +2 -16
  26. package/dist/connectors/postgres/connector.js +1 -14
  27. package/dist/connectors/shared/string-reference.d.ts +6 -0
  28. package/dist/connectors/shared/string-reference.js +19 -0
  29. package/dist/connectors/snowflake/connector.d.ts +1 -1
  30. package/dist/connectors/snowflake/connector.js +1 -14
  31. package/dist/connectors/sqlite/connector.js +2 -25
  32. package/dist/connectors/sqlserver/connector.js +4 -17
  33. package/dist/context/connections/connection-type.d.ts +1 -1
  34. package/dist/context/connections/federation.d.ts +33 -0
  35. package/dist/context/connections/federation.js +51 -0
  36. package/dist/context/connections/local-warehouse-descriptor.d.ts +2 -0
  37. package/dist/context/connections/project-sql-executor.d.ts +18 -0
  38. package/dist/context/connections/project-sql-executor.js +39 -0
  39. package/dist/context/connections/query-executor.d.ts +2 -2
  40. package/dist/context/connections/read-only-sql.d.ts +1 -0
  41. package/dist/context/connections/read-only-sql.js +119 -4
  42. package/dist/context/connections/resolve-connection.d.ts +12 -0
  43. package/dist/context/connections/resolve-connection.js +37 -0
  44. package/dist/context/core/git-env.d.ts +4 -0
  45. package/dist/context/core/git-env.js +5 -1
  46. package/dist/context/core/git.service.d.ts +23 -0
  47. package/dist/context/core/git.service.js +71 -8
  48. package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
  49. package/dist/context/ingest/adapters/live-database/manifest.d.ts +3 -0
  50. package/dist/context/ingest/adapters/live-database/manifest.js +19 -11
  51. package/dist/context/ingest/adapters/looker/client.js +7 -2
  52. package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
  53. package/dist/context/ingest/adapters/looker/factory.js +9 -0
  54. package/dist/context/ingest/adapters/looker/mapping.js +1 -1
  55. package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
  56. package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
  57. package/dist/context/ingest/adapters/metabase/client.js +1 -1
  58. package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
  59. package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
  60. package/dist/context/ingest/artifact-gates.d.ts +2 -6
  61. package/dist/context/ingest/artifact-gates.js +5 -47
  62. package/dist/context/ingest/constrained-repair.d.ts +55 -0
  63. package/dist/context/ingest/constrained-repair.js +167 -0
  64. package/dist/context/ingest/final-gate-repair.d.ts +9 -11
  65. package/dist/context/ingest/final-gate-repair.js +40 -128
  66. package/dist/context/ingest/finalization-scope.d.ts +1 -1
  67. package/dist/context/ingest/finalization-scope.js +15 -15
  68. package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
  69. package/dist/context/ingest/ingest-bundle.runner.js +101 -67
  70. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
  71. package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
  72. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
  73. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
  74. package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
  75. package/dist/context/ingest/local-bundle-runtime.js +9 -10
  76. package/dist/context/ingest/local-ingest.d.ts +2 -0
  77. package/dist/context/ingest/local-ingest.js +2 -0
  78. package/dist/context/ingest/memory-flow/view-model.js +1 -1
  79. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
  80. package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
  81. package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
  82. package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
  83. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
  84. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
  85. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
  86. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
  87. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
  88. package/dist/context/llm/ai-sdk-runtime.js +2 -2
  89. package/dist/context/llm/claude-code-runtime.js +19 -3
  90. package/dist/context/llm/local-config.js +1 -1
  91. package/dist/context/llm/runtime-tools.js +2 -2
  92. package/dist/context/mcp/context-tools.js +33 -8
  93. package/dist/context/mcp/local-project-ports.js +63 -89
  94. package/dist/context/mcp/types.d.ts +2 -0
  95. package/dist/context/memory/local-memory.js +4 -1
  96. package/dist/context/memory/memory-agent.service.js +1 -1
  97. package/dist/context/project/config.d.ts +11 -4
  98. package/dist/context/project/config.js +85 -30
  99. package/dist/context/project/driver-schemas.js +1 -1
  100. package/dist/context/project/mappings-yaml-schema.js +2 -2
  101. package/dist/context/project/project.js +12 -4
  102. package/dist/context/scan/description-generation.js +4 -4
  103. package/dist/context/scan/local-enrichment-artifacts.js +33 -4
  104. package/dist/context/scan/local-scan.js +2 -2
  105. package/dist/context/scan/local-structural-artifacts.js +5 -5
  106. package/dist/context/scan/relationship-benchmark-report.js +1 -1
  107. package/dist/context/scan/relationship-discovery.js +3 -3
  108. package/dist/context/scan/relationship-llm-proposal.js +3 -3
  109. package/dist/context/sl/local-query.js +31 -44
  110. package/dist/context/sl/local-sl.d.ts +0 -8
  111. package/dist/context/sl/local-sl.js +71 -70
  112. package/dist/context/sl/semantic-layer.service.d.ts +25 -8
  113. package/dist/context/sl/semantic-layer.service.js +109 -56
  114. package/dist/context/sl/source-files.d.ts +48 -0
  115. package/dist/context/sl/source-files.js +138 -0
  116. package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
  117. package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
  118. package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
  119. package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
  120. package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
  121. package/dist/context/sql-analysis/dialect.d.ts +2 -0
  122. package/dist/context/sql-analysis/dialect.js +20 -0
  123. package/dist/context/tools/base-tool.d.ts +6 -19
  124. package/dist/context/tools/base-tool.js +0 -14
  125. package/dist/context-build-view.js +5 -5
  126. package/dist/database-tree-picker.js +18 -3
  127. package/dist/demo-assets.js +0 -1
  128. package/dist/doctor.d.ts +1 -1
  129. package/dist/doctor.js +31 -23
  130. package/dist/errors.d.ts +31 -0
  131. package/dist/errors.js +44 -0
  132. package/dist/ingest-query-executor.d.ts +2 -0
  133. package/dist/ingest-query-executor.js +8 -22
  134. package/dist/ingest.d.ts +1 -1
  135. package/dist/ingest.js +8 -2
  136. package/dist/io/symbols.d.ts +2 -0
  137. package/dist/io/symbols.js +2 -0
  138. package/dist/io/tty.d.ts +8 -0
  139. package/dist/io/tty.js +16 -0
  140. package/dist/llm/embedding-health.js +1 -1
  141. package/dist/llm/embedding-provider.js +3 -3
  142. package/dist/llm/model-provider.js +1 -1
  143. package/dist/local-adapters.d.ts +1 -0
  144. package/dist/local-adapters.js +2 -2
  145. package/dist/local-scan-connectors.js +1 -1
  146. package/dist/managed-local-embeddings.js +17 -8
  147. package/dist/managed-mcp-daemon.js +3 -3
  148. package/dist/managed-python-command.d.ts +7 -0
  149. package/dist/managed-python-command.js +34 -8
  150. package/dist/managed-python-daemon.js +2 -2
  151. package/dist/managed-python-http.js +3 -3
  152. package/dist/managed-python-runtime.d.ts +30 -1
  153. package/dist/managed-python-runtime.js +134 -18
  154. package/dist/managed-uv-release.d.ts +7 -0
  155. package/dist/managed-uv-release.js +11 -0
  156. package/dist/mcp-http-server.js +4 -4
  157. package/dist/mcp-server-factory.js +3 -3
  158. package/dist/mcp-stdio-server.js +1 -1
  159. package/dist/memory-flow-hud.js +2 -2
  160. package/dist/next-steps.js +2 -2
  161. package/dist/prompt-navigation.d.ts +17 -0
  162. package/dist/prompt-navigation.js +49 -3
  163. package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
  164. package/dist/prompts/memory_agent_external_ingest.md +2 -2
  165. package/dist/public-ingest-copy.js +1 -1
  166. package/dist/public-ingest.js +3 -3
  167. package/dist/release-version.js +1 -1
  168. package/dist/runtime-requirements.js +1 -1
  169. package/dist/runtime.js +9 -9
  170. package/dist/scan.js +1 -1
  171. package/dist/setup-agents.d.ts +21 -15
  172. package/dist/setup-agents.js +143 -66
  173. package/dist/setup-banner.d.ts +20 -0
  174. package/dist/setup-banner.js +39 -0
  175. package/dist/setup-context.js +24 -15
  176. package/dist/setup-databases.d.ts +3 -0
  177. package/dist/setup-databases.js +47 -59
  178. package/dist/setup-demo-tour.js +12 -8
  179. package/dist/setup-embeddings.js +9 -9
  180. package/dist/setup-interrupt.js +1 -1
  181. package/dist/setup-models.d.ts +4 -1
  182. package/dist/setup-models.js +54 -28
  183. package/dist/setup-project.js +29 -5
  184. package/dist/setup-prompts.js +16 -1
  185. package/dist/setup-ready-menu.js +1 -1
  186. package/dist/setup-sources.js +28 -12
  187. package/dist/setup.d.ts +1 -0
  188. package/dist/setup.js +14 -13
  189. package/dist/skills/analytics/SKILL.md +3 -3
  190. package/dist/skills/dbt_ingest/SKILL.md +3 -3
  191. package/dist/skills/looker_ingest/SKILL.md +3 -3
  192. package/dist/skills/lookml_ingest/SKILL.md +7 -7
  193. package/dist/skills/metabase_ingest/SKILL.md +4 -4
  194. package/dist/skills/metricflow_ingest/SKILL.md +15 -15
  195. package/dist/skills/notion_synthesize/SKILL.md +1 -1
  196. package/dist/skills/sl/SKILL.md +3 -3
  197. package/dist/skills/sl_capture/SKILL.md +1 -1
  198. package/dist/skills/wiki_capture/SKILL.md +1 -1
  199. package/dist/source-mapping.js +1 -1
  200. package/dist/sql.d.ts +2 -0
  201. package/dist/sql.js +35 -53
  202. package/dist/startup-profile.js +1 -1
  203. package/dist/status-project.d.ts +0 -2
  204. package/dist/status-project.js +4 -6
  205. package/dist/telemetry/events.d.ts +3 -2
  206. package/dist/telemetry/events.js +11 -1
  207. package/dist/telemetry/exception.js +14 -0
  208. package/dist/text-ingest.js +1 -1
  209. package/dist/tree-picker-tui.d.ts +0 -1
  210. package/dist/tree-picker-tui.js +2 -3
  211. package/package.json +2 -1
  212. package/assets/python/kaelio_ktx-0.11.0-py3-none-any.whl +0 -0
@@ -4,6 +4,7 @@ import pLimit from 'p-limit';
4
4
  import { z } from 'zod';
5
5
  import { noopLogger } from '../../context/core/config.js';
6
6
  import { createRuntimeToolDescriptorFromAiTool } from '../../context/llm/runtime-tools.js';
7
+ import { isSlYamlPath, slSourceFilePath, slSourceNameForFile, sourceNameFromPath } from '../../context/sl/source-files.js';
7
8
  import { createTouchedSlSources } from '../../context/tools/touched-sl-sources.js';
8
9
  import { findDanglingWikiRefsForActions } from '../wiki/wiki-ref-validation.js';
9
10
  import { actionTargetConnectionId } from './action-identity.js';
@@ -351,7 +352,7 @@ export class IngestBundleRunner {
351
352
  const files = await this.deps.semanticLayerService.listFilesForConnection(connectionId);
352
353
  const names = files
353
354
  .filter((f) => !f.startsWith('_schema/'))
354
- .map((f) => f.replace(/\.yaml$/, ''))
355
+ .map((f) => sourceNameFromPath(f))
355
356
  .sort((left, right) => left.localeCompare(right));
356
357
  const body = names.length > 0 ? names.join('\n') : '(no sources yet)';
357
358
  return `## ${connectionId}\n${body}`;
@@ -584,14 +585,48 @@ export class IngestBundleRunner {
584
585
  .map((path) => path.slice('wiki/global/'.length, -'.md'.length))),
585
586
  ].sort();
586
587
  }
587
- touchedSlSourcesFromPaths(paths) {
588
- return paths
589
- .filter((path) => path.startsWith('semantic-layer/') && path.endsWith('.yaml') && !path.includes('/_schema/'))
590
- .map((path) => {
591
- const [, connectionId, fileName] = path.split('/');
592
- return { connectionId: connectionId ?? '', sourceName: (fileName ?? '').replace(/\.yaml$/, '') };
593
- })
594
- .filter((source) => source.connectionId.length > 0 && source.sourceName.length > 0);
588
+ async touchedSlSourcesFromPaths(worktree, paths, deletedFileSha) {
589
+ const sources = [];
590
+ for (const path of paths) {
591
+ if (!path.startsWith('semantic-layer/') || !isSlYamlPath(path) || path.includes('/_schema/')) {
592
+ continue;
593
+ }
594
+ const [, connectionId] = path.split('/');
595
+ if (!connectionId) {
596
+ continue;
597
+ }
598
+ // Source identity is the in-file `name:`, never the filename — an uppercase
599
+ // warehouse source like `WIDGET_SALES` lives in a hash-derived
600
+ // `widget_sales-<hash>.yaml`, so parsing the basename yields a phantom name.
601
+ // Read the live file; when it was deleted this run, recover its declared
602
+ // name from the pre-change commit the way `revertSourceToPreHead` resolves a
603
+ // gone file from history. The filename is a last resort only when the content
604
+ // is unrecoverable from both.
605
+ let content;
606
+ try {
607
+ content = await readFile(join(worktree.workdir, path), 'utf-8');
608
+ }
609
+ catch {
610
+ content = await worktree.git.getFileAtCommit(path, deletedFileSha).catch(() => null);
611
+ }
612
+ const sourceName = content === null ? sourceNameFromPath(path) : slSourceNameForFile(path, content);
613
+ if (sourceName.length > 0) {
614
+ sources.push({ connectionId, sourceName });
615
+ }
616
+ }
617
+ return sources;
618
+ }
619
+ // Inverse direction for commits and repair allowlists: resolve each touched
620
+ // source to its real on-disk path, falling back to the writer's derived
621
+ // filename when the file was deleted in this run.
622
+ async touchedSlSourcePaths(workdir, touched) {
623
+ const service = this.deps.semanticLayerService.forWorktree(workdir);
624
+ const paths = [];
625
+ for (const source of touched) {
626
+ const file = await service.readSourceFile(source.connectionId, source.sourceName);
627
+ paths.push(file?.path ?? slSourceFilePath(source.connectionId, source.sourceName));
628
+ }
629
+ return paths;
595
630
  }
596
631
  touchedSlSourcesFromActions(actions, fallbackConnectionId) {
597
632
  return actions
@@ -1153,7 +1188,7 @@ export class IngestBundleRunner {
1153
1188
  projectionTouchedSources = projection.touchedSources;
1154
1189
  projectionChangedWikiPageKeys = projection.changedWikiPageKeys;
1155
1190
  const projectionPaths = [
1156
- ...projection.touchedSources.map((source) => `semantic-layer/${source.connectionId}/${source.sourceName}.yaml`),
1191
+ ...(await this.touchedSlSourcePaths(sessionWorktree.workdir, projection.touchedSources)),
1157
1192
  ...projection.changedWikiPageKeys.map((pageKey) => `wiki/global/${pageKey}.md`),
1158
1193
  ];
1159
1194
  projectionTouchedPaths = projectionPaths;
@@ -1297,7 +1332,7 @@ export class IngestBundleRunner {
1297
1332
  await validateFinalIngestArtifacts({
1298
1333
  connectionIds: slConnectionIds,
1299
1334
  changedWikiPageKeys: this.wikiPageKeysFromPaths(touchedPaths),
1300
- touchedSlSources: this.touchedSlSourcesFromPaths(touchedPaths),
1335
+ touchedSlSources: await this.touchedSlSourcesFromPaths(sessionWorktree, touchedPaths, await sessionWorktree.git.revParseHead()),
1301
1336
  wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
1302
1337
  semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1303
1338
  validateTouchedSources: (touched) => validateWuTouchedSources({
@@ -1322,7 +1357,8 @@ export class IngestBundleRunner {
1322
1357
  touchedPaths: context.touchedPaths,
1323
1358
  trace: runTrace,
1324
1359
  reason: context.reason,
1325
- maxAttempts: 1,
1360
+ verify: context.verify,
1361
+ maxAttempts: 2,
1326
1362
  stepBudget: 12,
1327
1363
  abortSignal: ctx?.abortSignal,
1328
1364
  });
@@ -1340,7 +1376,8 @@ export class IngestBundleRunner {
1340
1376
  allowedPaths: context.touchedPaths,
1341
1377
  trace: runTrace,
1342
1378
  repairKind: 'patch_semantic_gate',
1343
- maxAttempts: 1,
1379
+ verify: context.verify,
1380
+ maxAttempts: 2,
1344
1381
  stepBudget: 16,
1345
1382
  abortSignal: ctx?.abortSignal,
1346
1383
  });
@@ -1748,17 +1785,24 @@ export class IngestBundleRunner {
1748
1785
  preFinalizationSha !== postFinalizationSha
1749
1786
  ? (await sessionWorktree.git.diffNameStatus(preFinalizationSha, postFinalizationSha)).map((entry) => entry.path)
1750
1787
  : [];
1751
- const changedConnectionIds = [
1752
- ...new Set([
1753
- ...slConnectionIds,
1754
- ...finalizationTouchedPaths
1755
- .filter((path) => path.startsWith('semantic-layer/'))
1756
- .map((path) => path.split('/')[1])
1757
- .filter((connectionId) => Boolean(connectionId)),
1758
- ]),
1759
- ].sort();
1760
- const postFinalizationSourcesByConnection = await this.loadSourcesByConnection(sessionWorktree.workdir, changedConnectionIds);
1761
- const scope = await deriveFinalizationTouchedSources({
1788
+ // Validate the write scope before deriving touched sources: attribution
1789
+ // by before/after diff is only defined for connections whose
1790
+ // pre-finalization snapshot was loaded (slConnectionIds), and an
1791
+ // out-of-scope write would otherwise surface downstream as a bogus
1792
+ // unresolved-path or declaration-mismatch failure instead of the real
1793
+ // policy violation.
1794
+ await traceTimed(runTrace, 'finalization', 'semantic_layer_target_policy', {
1795
+ sourceKey: job.sourceKey,
1796
+ allowedTargetConnectionIds: slConnectionIds,
1797
+ touchedPaths: [...new Set(finalizationTouchedPaths)].sort(),
1798
+ }, async () => {
1799
+ assertSemanticLayerTargetPathsAllowed({
1800
+ paths: finalizationTouchedPaths,
1801
+ allowedConnectionIds: new Set(slConnectionIds),
1802
+ });
1803
+ });
1804
+ const postFinalizationSourcesByConnection = await this.loadSourcesByConnection(sessionWorktree.workdir, slConnectionIds);
1805
+ const scope = deriveFinalizationTouchedSources({
1762
1806
  changedPaths: finalizationTouchedPaths,
1763
1807
  beforeSourcesByConnection: preFinalizationSourcesByConnection,
1764
1808
  afterSourcesByConnection: postFinalizationSourcesByConnection,
@@ -1876,7 +1920,7 @@ export class IngestBundleRunner {
1876
1920
  ...(isolatedDiffEnabled ? projectionTouchedSources : []),
1877
1921
  ...workUnitOutcomes.flatMap((outcome) => outcome.touchedSlSources),
1878
1922
  ...this.touchedSlSourcesFromActions(reconcileActions, job.connectionId),
1879
- ...this.touchedSlSourcesFromPaths(postReconciliationPaths),
1923
+ ...(await this.touchedSlSourcesFromPaths(sessionWorktree, postReconciliationPaths, preReconciliationSha)),
1880
1924
  ...finalizationTouchedSources,
1881
1925
  ]);
1882
1926
  const finalWikiGateScope = await this.wikiPageKeysForFinalGates({
@@ -1926,32 +1970,33 @@ export class IngestBundleRunner {
1926
1970
  activePhase = 'final_gates';
1927
1971
  activeFailureDetails = finalArtifactGateTraceData;
1928
1972
  emitStageProgress('final_gates', 89, 'Running final artifact gates');
1929
- try {
1930
- await traceTimed(runTrace, 'final_gates', 'final_artifact_gates', finalArtifactGateTraceData, async () => {
1931
- await validateFinalIngestArtifacts({
1932
- connectionIds: repairConnectionIds,
1933
- changedWikiPageKeys: finalChangedWikiPageKeys,
1934
- touchedSlSources: finalTouchedSlSources,
1935
- wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
1973
+ const runFinalArtifactGates = async () => {
1974
+ await validateFinalIngestArtifacts({
1975
+ connectionIds: repairConnectionIds,
1976
+ changedWikiPageKeys: finalChangedWikiPageKeys,
1977
+ touchedSlSources: finalTouchedSlSources,
1978
+ wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
1979
+ semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1980
+ validateTouchedSources: (touched) => validateWuTouchedSources({
1936
1981
  semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1937
- validateTouchedSources: (touched) => validateWuTouchedSources({
1938
- semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1939
- connections: this.deps.connections,
1940
- configService: sessionWorktree.config,
1941
- gitService: sessionWorktree.git,
1942
- slSourcesRepository: this.deps.slSourcesRepository,
1943
- probeRowCount: this.deps.settings.probeRowCount,
1944
- slValidator: this.deps.slValidator,
1945
- }, touched),
1946
- tableExists: (connectionId, tableRef) => this.tableRefExistsInSemanticLayer(this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir), [connectionId], tableRef),
1947
- });
1982
+ connections: this.deps.connections,
1983
+ configService: sessionWorktree.config,
1984
+ gitService: sessionWorktree.git,
1985
+ slSourcesRepository: this.deps.slSourcesRepository,
1986
+ probeRowCount: this.deps.settings.probeRowCount,
1987
+ slValidator: this.deps.slValidator,
1988
+ }, touched),
1989
+ tableExists: (connectionId, tableRef) => this.tableRefExistsInSemanticLayer(this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir), [connectionId], tableRef),
1948
1990
  });
1991
+ };
1992
+ try {
1993
+ await traceTimed(runTrace, 'final_gates', 'final_artifact_gates', finalArtifactGateTraceData, runFinalArtifactGates);
1949
1994
  }
1950
1995
  catch (error) {
1951
1996
  const gateError = this.errorMessage(error);
1952
1997
  const repairPaths = finalGateRepairPaths({
1953
1998
  changedWikiPageKeys: finalChangedWikiPageKeys,
1954
- touchedSlSources: finalTouchedSlSources,
1999
+ touchedSlSourcePaths: await this.touchedSlSourcePaths(sessionWorktree.workdir, finalTouchedSlSources),
1955
2000
  });
1956
2001
  emitStageProgress('final_gates', 89, 'Repairing final artifact gates');
1957
2002
  const gateRepair = await repairFinalGateFailure({
@@ -1961,7 +2006,16 @@ export class IngestBundleRunner {
1961
2006
  allowedPaths: repairPaths,
1962
2007
  trace: runTrace,
1963
2008
  repairKind: 'final_artifact_gate',
1964
- maxAttempts: 1,
2009
+ verify: async () => {
2010
+ try {
2011
+ await runFinalArtifactGates();
2012
+ return { ok: true };
2013
+ }
2014
+ catch (verifyError) {
2015
+ return { ok: false, reason: this.errorMessage(verifyError) };
2016
+ }
2017
+ },
2018
+ maxAttempts: 2,
1965
2019
  stepBudget: 16,
1966
2020
  abortSignal: ctx?.abortSignal,
1967
2021
  });
@@ -1975,29 +2029,9 @@ export class IngestBundleRunner {
1975
2029
  };
1976
2030
  throw new Error(`${gateError}\ngate repair failed: ${gateRepair.reason}`);
1977
2031
  }
2032
+ // The repair loop re-ran the gates via `verify` before reporting
2033
+ // success, so a repaired status here means the tree already passed.
1978
2034
  isolatedDiffSummary.gateRepairs += 1;
1979
- await traceTimed(runTrace, 'final_gates', 'final_artifact_gates_after_gate_repair', {
1980
- ...finalArtifactGateTraceData,
1981
- repairedPaths: gateRepair.changedPaths,
1982
- }, async () => {
1983
- await validateFinalIngestArtifacts({
1984
- connectionIds: repairConnectionIds,
1985
- changedWikiPageKeys: finalChangedWikiPageKeys,
1986
- touchedSlSources: finalTouchedSlSources,
1987
- wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
1988
- semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1989
- validateTouchedSources: (touched) => validateWuTouchedSources({
1990
- semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1991
- connections: this.deps.connections,
1992
- configService: sessionWorktree.config,
1993
- gitService: sessionWorktree.git,
1994
- slSourcesRepository: this.deps.slSourcesRepository,
1995
- probeRowCount: this.deps.settings.probeRowCount,
1996
- slValidator: this.deps.slValidator,
1997
- }, touched),
1998
- tableExists: (connectionId, tableRef) => this.tableRefExistsInSemanticLayer(this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir), [connectionId], tableRef),
1999
- });
2000
- });
2001
2035
  const repairCommit = await sessionWorktree.git.commitFiles(gateRepair.changedPaths, `ingest(${job.sourceKey}): repair final gates syncId=${syncId}`, this.deps.storage.systemGitAuthor.name, this.deps.storage.systemGitAuthor.email);
2002
2036
  if (!repairCommit.created) {
2003
2037
  isolatedDiffSummary.gateRepairFailures += 1;
@@ -1,33 +1,25 @@
1
1
  import type { GitService } from '../../../context/core/git.service.js';
2
+ import type { RepairVerification } from '../constrained-repair.js';
2
3
  import type { FinalGateRepairResult } from '../final-gate-repair.js';
3
4
  import type { IngestTraceWriter } from '../ingest-trace.js';
4
5
  import type { TextualConflictResolutionResult } from './textual-conflict-resolver.js';
5
- type PatchIntegrationTextualResolution = {
6
- status: 'repaired';
7
- attempts: number;
8
- changedPaths: string[];
9
- } | {
10
- status: 'failed';
11
- attempts: number;
12
- reason: string;
13
- };
14
6
  export type PatchIntegrationResult = {
15
7
  status: 'accepted';
16
8
  commitSha: string;
17
9
  touchedPaths: string[];
18
- textualResolution?: PatchIntegrationTextualResolution;
10
+ textualResolution?: TextualConflictResolutionResult;
19
11
  gateRepair?: FinalGateRepairResult;
20
12
  } | {
21
13
  status: 'textual_conflict';
22
14
  reason: string;
23
15
  touchedPaths: string[];
24
- textualResolution?: PatchIntegrationTextualResolution;
16
+ textualResolution?: TextualConflictResolutionResult;
25
17
  gateRepair?: FinalGateRepairResult;
26
18
  } | {
27
19
  status: 'semantic_conflict';
28
20
  reason: string;
29
21
  touchedPaths: string[];
30
- textualResolution?: PatchIntegrationTextualResolution;
22
+ textualResolution?: TextualConflictResolutionResult;
31
23
  gateRepair?: FinalGateRepairResult;
32
24
  };
33
25
  export interface IntegrateWorkUnitPatchInput {
@@ -47,13 +39,14 @@ export interface IntegrateWorkUnitPatchInput {
47
39
  patchPath: string;
48
40
  touchedPaths: string[];
49
41
  reason: string;
42
+ verify(changedPaths: string[]): Promise<RepairVerification>;
50
43
  }): Promise<TextualConflictResolutionResult>;
51
44
  repairGateFailure?(input: {
52
45
  unitKey: string;
53
46
  patchPath: string;
54
47
  touchedPaths: string[];
55
48
  reason: string;
49
+ verify(changedPaths: string[]): Promise<RepairVerification>;
56
50
  }): Promise<FinalGateRepairResult>;
57
51
  }
58
52
  export declare function integrateWorkUnitPatch(input: IntegrateWorkUnitPatchInput): Promise<PatchIntegrationResult>;
59
- export {};
@@ -38,6 +38,19 @@ export async function integrateWorkUnitPatch(input) {
38
38
  touchedPaths,
39
39
  };
40
40
  }
41
+ // Repair and resolution success is decided by this check, not by whether
42
+ // the repair agent edited files: the gates re-run over the union of the
43
+ // patch's paths and everything the agent changed.
44
+ const verifyAppliedTree = async (changedPaths) => {
45
+ const paths = [...new Set([...touchedPaths, ...changedPaths])].sort();
46
+ try {
47
+ await input.validateAppliedTree(paths);
48
+ return { ok: true };
49
+ }
50
+ catch (error) {
51
+ return { ok: false, reason: errorMessage(error) };
52
+ }
53
+ };
41
54
  try {
42
55
  await traceTimed(input.trace, 'integration', 'patch_apply', { unitKey: input.unitKey, patchPath: input.patchPath, touchedPaths }, async () => {
43
56
  await input.integrationGit.applyPatchFile3WayIndex(input.patchPath);
@@ -67,6 +80,7 @@ export async function integrateWorkUnitPatch(input) {
67
80
  patchPath: input.patchPath,
68
81
  touchedPaths,
69
82
  reason,
83
+ verify: verifyAppliedTree,
70
84
  });
71
85
  if (textualResolution.status === 'failed') {
72
86
  if (preApplyHead) {
@@ -79,113 +93,37 @@ export async function integrateWorkUnitPatch(input) {
79
93
  textualResolution,
80
94
  };
81
95
  }
82
- try {
83
- await traceTimed(input.trace, 'integration', 'semantic_gate_after_textual_resolution', { unitKey: input.unitKey, touchedPaths: textualResolution.changedPaths }, async () => {
84
- await input.validateAppliedTree(textualResolution.changedPaths);
85
- });
86
- }
87
- catch (semanticError) {
88
- const reason = errorMessage(semanticError);
89
- await input.trace.event('error', 'integration', 'patch_semantic_conflict_after_textual_resolution', {
96
+ if (textualResolution.changedPaths.length === 0) {
97
+ // The resolver declared the patch redundant and the gates verified the
98
+ // current tree: the integration worktree already represents this work
99
+ // unit's content (e.g. a duplicate page created by another work unit).
100
+ await input.trace.event('debug', 'integration', 'patch_subsumed_after_textual_resolution', {
90
101
  unitKey: input.unitKey,
91
102
  patchPath: input.patchPath,
92
- touchedPaths: textualResolution.changedPaths,
93
- reason,
103
+ touchedPaths,
104
+ attempts: textualResolution.attempts,
94
105
  });
95
- // A textual conflict and a semantic-gate failure can co-occur: the resolver
96
- // reconciles the text but can leave wiki sl_refs pointing at measures the
97
- // merged source no longer defines. Recover via the same gate repair the
98
- // clean-apply branch uses, instead of hard-failing the whole job.
99
- if (input.repairGateFailure) {
100
- const gateRepair = await input.repairGateFailure({
101
- unitKey: input.unitKey,
102
- patchPath: input.patchPath,
103
- touchedPaths: textualResolution.changedPaths,
104
- reason,
105
- });
106
- if (gateRepair.status !== 'failed') {
107
- // The resolver wrote its merge to the worktree (unstaged); the repair
108
- // edited a subset on top. Commit the union so neither is dropped.
109
- const resolvedAndRepairedPaths = [
110
- ...new Set([...textualResolution.changedPaths, ...gateRepair.changedPaths]),
111
- ].sort();
112
- try {
113
- await traceTimed(input.trace, 'integration', 'semantic_gate_after_gate_repair', { unitKey: input.unitKey, touchedPaths: gateRepair.changedPaths }, async () => {
114
- await input.validateAppliedTree(gateRepair.changedPaths);
115
- });
116
- const commit = await input.integrationGit.commitFiles(resolvedAndRepairedPaths, `ingest: resolve WorkUnit ${input.unitKey} conflict`, input.author.name, input.author.email);
117
- if (commit.created) {
118
- await input.trace.event('debug', 'integration', 'patch_accepted_after_textual_resolution', {
119
- unitKey: input.unitKey,
120
- commitSha: commit.commitHash,
121
- touchedPaths: resolvedAndRepairedPaths,
122
- attempts: textualResolution.attempts,
123
- gateRepairAttempts: gateRepair.attempts,
124
- });
125
- return {
126
- status: 'accepted',
127
- commitSha: commit.commitHash,
128
- touchedPaths: resolvedAndRepairedPaths,
129
- textualResolution,
130
- gateRepair,
131
- };
132
- }
133
- }
134
- catch (repairValidationError) {
135
- if (preApplyHead) {
136
- await input.integrationGit.resetHardTo(preApplyHead);
137
- }
138
- await input.trace.event('error', 'integration', 'patch_semantic_conflict_after_textual_resolution', {
139
- unitKey: input.unitKey,
140
- patchPath: input.patchPath,
141
- touchedPaths: gateRepair.changedPaths,
142
- reason: errorMessage(repairValidationError),
143
- });
144
- return {
145
- status: 'semantic_conflict',
146
- reason: errorMessage(repairValidationError),
147
- touchedPaths: gateRepair.changedPaths,
148
- textualResolution,
149
- gateRepair,
150
- };
151
- }
152
- }
153
- if (preApplyHead) {
154
- await input.integrationGit.resetHardTo(preApplyHead);
155
- }
156
- return {
157
- status: 'semantic_conflict',
158
- reason: gateRepair.status === 'failed' ? gateRepair.reason : reason,
159
- touchedPaths: textualResolution.changedPaths,
160
- textualResolution,
161
- gateRepair,
162
- };
163
- }
164
- if (preApplyHead) {
165
- await input.integrationGit.resetHardTo(preApplyHead);
166
- }
167
106
  return {
168
- status: 'semantic_conflict',
169
- reason,
170
- touchedPaths: textualResolution.changedPaths,
107
+ status: 'accepted',
108
+ commitSha: preApplyHead ?? '',
109
+ touchedPaths: [],
171
110
  textualResolution,
172
111
  };
173
112
  }
174
113
  const commit = await input.integrationGit.commitFiles(textualResolution.changedPaths, `ingest: resolve WorkUnit ${input.unitKey} conflict`, input.author.name, input.author.email);
175
114
  if (!commit.created) {
176
- if (preApplyHead) {
177
- await input.integrationGit.resetHardTo(preApplyHead);
178
- }
179
- const noChangeReason = 'textual resolver produced no committable changes';
180
- await input.trace.event('error', 'integration', 'textual_conflict_resolver_noop', {
115
+ // The resolver's writes left the tree byte-identical to the accepted
116
+ // state, and the gates verified it — the patch is represented already.
117
+ await input.trace.event('debug', 'integration', 'patch_subsumed_after_textual_resolution', {
181
118
  unitKey: input.unitKey,
182
119
  patchPath: input.patchPath,
183
120
  touchedPaths: textualResolution.changedPaths,
121
+ attempts: textualResolution.attempts,
184
122
  });
185
123
  return {
186
- status: 'textual_conflict',
187
- reason: noChangeReason,
188
- touchedPaths: textualResolution.changedPaths,
124
+ status: 'accepted',
125
+ commitSha: preApplyHead ?? '',
126
+ touchedPaths: [],
189
127
  textualResolution,
190
128
  };
191
129
  }
@@ -221,6 +159,7 @@ export async function integrateWorkUnitPatch(input) {
221
159
  patchPath: input.patchPath,
222
160
  touchedPaths,
223
161
  reason,
162
+ verify: verifyAppliedTree,
224
163
  });
225
164
  if (gateRepair.status === 'failed') {
226
165
  if (preApplyHead) {
@@ -233,22 +172,6 @@ export async function integrateWorkUnitPatch(input) {
233
172
  gateRepair,
234
173
  };
235
174
  }
236
- try {
237
- await traceTimed(input.trace, 'integration', 'semantic_gate_after_gate_repair', { unitKey: input.unitKey, touchedPaths: gateRepair.changedPaths }, async () => {
238
- await input.validateAppliedTree(gateRepair.changedPaths);
239
- });
240
- }
241
- catch (repairValidationError) {
242
- if (preApplyHead) {
243
- await input.integrationGit.resetHardTo(preApplyHead);
244
- }
245
- return {
246
- status: 'semantic_conflict',
247
- reason: errorMessage(repairValidationError),
248
- touchedPaths: gateRepair.changedPaths,
249
- gateRepair,
250
- };
251
- }
252
175
  const commit = await input.integrationGit.commitFiles(gateRepair.changedPaths, `ingest: repair WorkUnit ${input.unitKey} gates`, input.author.name, input.author.email);
253
176
  if (!commit.created) {
254
177
  if (preApplyHead) {
@@ -1,14 +1,7 @@
1
1
  import type { AgentRunnerPort } from '../../../context/llm/runtime-port.js';
2
+ import type { ConstrainedRepairResult, RepairVerification } from '../constrained-repair.js';
2
3
  import type { IngestTraceWriter } from '../ingest-trace.js';
3
- export type TextualConflictResolutionResult = {
4
- status: 'repaired';
5
- attempts: number;
6
- changedPaths: string[];
7
- } | {
8
- status: 'failed';
9
- attempts: number;
10
- reason: string;
11
- };
4
+ export type TextualConflictResolutionResult = ConstrainedRepairResult;
12
5
  export interface ResolveTextualConflictInput {
13
6
  agentRunner: AgentRunnerPort;
14
7
  workdir: string;
@@ -17,6 +10,12 @@ export interface ResolveTextualConflictInput {
17
10
  touchedPaths: string[];
18
11
  trace: IngestTraceWriter;
19
12
  reason: string;
13
+ /**
14
+ * Re-runs the artifact gates against the current worktree. A resolution —
15
+ * including an explicit no-change declaration for a redundant patch —
16
+ * counts as successful only when this passes.
17
+ */
18
+ verify(changedPaths: string[]): Promise<RepairVerification>;
20
19
  maxAttempts?: number;
21
20
  stepBudget?: number;
22
21
  abortSignal?: AbortSignal;