@kaelio/ktx 0.13.1 → 0.15.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 (261) hide show
  1. package/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.15.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-program.js +1 -1
  5. package/dist/commands/ingest-commands.d.ts +9 -0
  6. package/dist/commands/ingest-commands.js +37 -1
  7. package/dist/commands/knowledge-commands.js +3 -0
  8. package/dist/commands/setup-commands.js +15 -1
  9. package/dist/connection-drivers.d.ts +2 -0
  10. package/dist/connection-drivers.js +7 -3
  11. package/dist/connection.d.ts +3 -0
  12. package/dist/connection.js +27 -0
  13. package/dist/connectors/bigquery/connector.d.ts +17 -6
  14. package/dist/connectors/bigquery/connector.js +152 -76
  15. package/dist/connectors/bigquery/dialect.d.ts +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +1 -0
  17. package/dist/connectors/clickhouse/connector.js +45 -16
  18. package/dist/connectors/clickhouse/dialect.d.ts +2 -2
  19. package/dist/connectors/mongodb/connector.d.ts +69 -0
  20. package/dist/connectors/mongodb/connector.js +307 -0
  21. package/dist/connectors/mongodb/dialect.d.ts +17 -0
  22. package/dist/connectors/mongodb/dialect.js +50 -0
  23. package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
  24. package/dist/connectors/mongodb/live-database-introspection.js +24 -0
  25. package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
  26. package/dist/connectors/mongodb/schema-inference.js +110 -0
  27. package/dist/connectors/mysql/connector.d.ts +1 -0
  28. package/dist/connectors/mysql/connector.js +18 -2
  29. package/dist/connectors/mysql/dialect.d.ts +2 -2
  30. package/dist/connectors/postgres/connector.d.ts +1 -0
  31. package/dist/connectors/postgres/connector.js +20 -3
  32. package/dist/connectors/postgres/dialect.d.ts +2 -2
  33. package/dist/connectors/snowflake/connector.d.ts +1 -0
  34. package/dist/connectors/snowflake/connector.js +27 -3
  35. package/dist/connectors/snowflake/dialect.d.ts +2 -2
  36. package/dist/connectors/sqlite/connector.d.ts +11 -1
  37. package/dist/connectors/sqlite/connector.js +120 -17
  38. package/dist/connectors/sqlite/dialect.d.ts +2 -2
  39. package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
  40. package/dist/connectors/sqlite/read-query-child.js +23 -0
  41. package/dist/connectors/sqlserver/connector.d.ts +2 -0
  42. package/dist/connectors/sqlserver/connector.js +20 -3
  43. package/dist/connectors/sqlserver/dialect.d.ts +2 -2
  44. package/dist/context/cache/content-result-cache.d.ts +36 -0
  45. package/dist/context/cache/content-result-cache.js +14 -0
  46. package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
  47. package/dist/context/cache/sqlite-content-result-cache.js +223 -0
  48. package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
  49. package/dist/context/connections/bigquery-identifiers.js +7 -0
  50. package/dist/context/connections/configured-connections.d.ts +9 -0
  51. package/dist/context/connections/configured-connections.js +18 -0
  52. package/dist/context/connections/dialects.d.ts +30 -4
  53. package/dist/context/connections/dialects.js +38 -11
  54. package/dist/context/connections/drivers.js +21 -0
  55. package/dist/context/connections/gdrive-config.d.ts +19 -0
  56. package/dist/context/connections/gdrive-config.js +57 -0
  57. package/dist/context/connections/query-deadline.d.ts +31 -0
  58. package/dist/context/connections/query-deadline.js +37 -0
  59. package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
  60. package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
  61. package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
  62. package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
  63. package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
  64. package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
  65. package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
  66. package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
  67. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
  68. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
  69. package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
  70. package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
  71. package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
  72. package/dist/context/ingest/adapters/gdrive/types.js +36 -0
  73. package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
  74. package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
  75. package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
  76. package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
  77. package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
  78. package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
  79. package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
  80. package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
  81. package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
  82. package/dist/context/ingest/adapters/live-database/stage.js +5 -5
  83. package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
  84. package/dist/context/ingest/adapters/metabase/types.js +1 -1
  85. package/dist/context/ingest/adapters/sigma/chunk.d.ts +6 -0
  86. package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
  87. package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
  88. package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
  89. package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
  90. package/dist/context/ingest/adapters/sigma/client.js +176 -0
  91. package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
  92. package/dist/context/ingest/adapters/sigma/detect.js +23 -0
  93. package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
  94. package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
  95. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
  96. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
  97. package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
  98. package/dist/context/ingest/adapters/sigma/project.js +186 -0
  99. package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
  100. package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
  101. package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
  102. package/dist/context/ingest/adapters/sigma/types.js +82 -0
  103. package/dist/context/ingest/artifact-gates.d.ts +32 -1
  104. package/dist/context/ingest/artifact-gates.js +85 -18
  105. package/dist/context/ingest/final-gate-prune.d.ts +35 -0
  106. package/dist/context/ingest/final-gate-prune.js +229 -0
  107. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  108. package/dist/context/ingest/ingest-bundle.runner.js +459 -240
  109. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
  110. package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
  111. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  112. package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
  113. package/dist/context/ingest/local-adapters.d.ts +2 -1
  114. package/dist/context/ingest/local-adapters.js +28 -0
  115. package/dist/context/ingest/local-bundle-runtime.js +7 -2
  116. package/dist/context/ingest/local-ingest.js +1 -1
  117. package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
  118. package/dist/context/ingest/local-stage-ingest.js +3 -1
  119. package/dist/context/ingest/ports.d.ts +3 -0
  120. package/dist/context/ingest/report-snapshot.js +13 -3
  121. package/dist/context/ingest/reports.d.ts +3 -3
  122. package/dist/context/ingest/reports.js +3 -1
  123. package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
  124. package/dist/context/ingest/stages/build-wu-context.js +2 -1
  125. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
  126. package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
  127. package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
  128. package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
  129. package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
  130. package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
  131. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
  132. package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
  133. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
  134. package/dist/context/ingest/types.d.ts +3 -0
  135. package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
  136. package/dist/context/ingest/wiki-body-refs.js +37 -8
  137. package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
  138. package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
  139. package/dist/context/ingest/work-unit-cache.d.ts +67 -0
  140. package/dist/context/ingest/work-unit-cache.js +230 -0
  141. package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
  142. package/dist/context/llm/ai-sdk-runtime.js +5 -0
  143. package/dist/context/llm/claude-code-runtime.d.ts +4 -1
  144. package/dist/context/llm/claude-code-runtime.js +38 -8
  145. package/dist/context/llm/codex-runtime.d.ts +4 -1
  146. package/dist/context/llm/codex-runtime.js +48 -35
  147. package/dist/context/llm/runtime-port.d.ts +26 -0
  148. package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
  149. package/dist/context/llm/subprocess-generate-object-child.js +34 -0
  150. package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
  151. package/dist/context/llm/subprocess-generate-object.js +122 -0
  152. package/dist/context/mcp/context-tools.d.ts +2 -0
  153. package/dist/context/mcp/context-tools.js +111 -17
  154. package/dist/context/mcp/local-project-ports.d.ts +7 -0
  155. package/dist/context/mcp/local-project-ports.js +29 -0
  156. package/dist/context/mcp/logger.d.ts +24 -0
  157. package/dist/context/mcp/logger.js +49 -0
  158. package/dist/context/mcp/server.js +2 -0
  159. package/dist/context/mcp/types.d.ts +17 -0
  160. package/dist/context/memory/memory-agent.service.js +1 -1
  161. package/dist/context/project/config.d.ts +72 -0
  162. package/dist/context/project/config.js +5 -0
  163. package/dist/context/project/driver-schemas.d.ts +35 -0
  164. package/dist/context/project/driver-schemas.js +89 -1
  165. package/dist/context/project/project.js +1 -1
  166. package/dist/context/project/setup-config.js +1 -0
  167. package/dist/context/scan/description-generation.d.ts +4 -0
  168. package/dist/context/scan/description-generation.js +100 -13
  169. package/dist/context/scan/enabled-tables.d.ts +5 -0
  170. package/dist/context/scan/enabled-tables.js +13 -1
  171. package/dist/context/scan/enrichment-state.d.ts +50 -7
  172. package/dist/context/scan/enrichment-state.js +30 -13
  173. package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
  174. package/dist/context/scan/local-enrichment-artifacts.js +155 -32
  175. package/dist/context/scan/local-enrichment.d.ts +39 -4
  176. package/dist/context/scan/local-enrichment.js +345 -92
  177. package/dist/context/scan/local-scan.d.ts +4 -1
  178. package/dist/context/scan/local-scan.js +54 -13
  179. package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
  180. package/dist/context/scan/local-structural-artifacts.js +5 -0
  181. package/dist/context/scan/object-introspection.d.ts +21 -0
  182. package/dist/context/scan/object-introspection.js +35 -0
  183. package/dist/context/scan/relationship-benchmarks.js +5 -3
  184. package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
  185. package/dist/context/scan/relationship-composite-candidates.js +13 -1
  186. package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
  187. package/dist/context/scan/relationship-detection-budget.js +53 -0
  188. package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
  189. package/dist/context/scan/relationship-diagnostics.js +2 -0
  190. package/dist/context/scan/relationship-discovery.d.ts +9 -3
  191. package/dist/context/scan/relationship-discovery.js +27 -4
  192. package/dist/context/scan/relationship-llm-proposal.js +36 -27
  193. package/dist/context/scan/relationship-profiling.d.ts +7 -3
  194. package/dist/context/scan/relationship-profiling.js +53 -41
  195. package/dist/context/scan/relationship-validation.d.ts +6 -4
  196. package/dist/context/scan/relationship-validation.js +46 -32
  197. package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
  198. package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
  199. package/dist/context/scan/types.d.ts +5 -3
  200. package/dist/context/scan/types.js +2 -0
  201. package/dist/context/sl/local-query.js +5 -0
  202. package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
  203. package/dist/context/sl/semantic-layer.service.js +1 -1
  204. package/dist/context/sl/source-files.d.ts +5 -0
  205. package/dist/context/sl/source-files.js +17 -11
  206. package/dist/context/sl/tools/connection-id-schema.js +1 -1
  207. package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
  208. package/dist/context/sql-analysis/dialect-notes.js +40 -0
  209. package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
  210. package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
  211. package/dist/context/sql-analysis/dialects/mysql.md +9 -0
  212. package/dist/context/sql-analysis/dialects/postgres.md +10 -0
  213. package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
  214. package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
  215. package/dist/context/sql-analysis/dialects/tsql.md +10 -0
  216. package/dist/context/wiki/keys.js +1 -1
  217. package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
  218. package/dist/context/wiki/knowledge-wiki.service.js +2 -1
  219. package/dist/context/wiki/local-knowledge.d.ts +13 -0
  220. package/dist/context/wiki/local-knowledge.js +50 -6
  221. package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
  222. package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
  223. package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
  224. package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
  225. package/dist/context/wiki/types.d.ts +6 -0
  226. package/dist/context-build-view.d.ts +3 -0
  227. package/dist/context-build-view.js +1 -0
  228. package/dist/knowledge.d.ts +2 -0
  229. package/dist/knowledge.js +12 -1
  230. package/dist/local-adapters.js +11 -0
  231. package/dist/mcp-http-server.js +15 -1
  232. package/dist/mcp-server-factory.d.ts +2 -0
  233. package/dist/mcp-server-factory.js +15 -1
  234. package/dist/mcp-stdio-server.js +10 -2
  235. package/dist/notion-page-picker.js +1 -1
  236. package/dist/prompts/memory_agent_external_ingest.md +2 -0
  237. package/dist/public-ingest.d.ts +3 -1
  238. package/dist/public-ingest.js +4 -0
  239. package/dist/scan.d.ts +3 -1
  240. package/dist/scan.js +7 -0
  241. package/dist/setup-databases.d.ts +1 -1
  242. package/dist/setup-databases.js +43 -1
  243. package/dist/setup-sources.d.ts +6 -1
  244. package/dist/setup-sources.js +208 -48
  245. package/dist/setup.d.ts +3 -0
  246. package/dist/setup.js +6 -1
  247. package/dist/skills/analytics/SKILL.md +200 -2
  248. package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
  249. package/dist/skills/sigma_ingest/SKILL.md +189 -0
  250. package/dist/skills/wiki_capture/SKILL.md +24 -0
  251. package/dist/sql.js +4 -0
  252. package/dist/status-project.d.ts +4 -0
  253. package/dist/status-project.js +54 -2
  254. package/dist/telemetry/events.d.ts +2 -2
  255. package/dist/text-ingest.d.ts +4 -0
  256. package/dist/text-ingest.js +58 -29
  257. package/dist/verbatim-ingest.d.ts +46 -0
  258. package/dist/verbatim-ingest.js +193 -0
  259. package/package.json +5 -1
  260. package/dist/context/ingest/final-gate-repair.d.ts +0 -28
  261. package/dist/context/ingest/final-gate-repair.js +0 -91
@@ -9,15 +9,15 @@ import { createTouchedSlSources } from '../../context/tools/touched-sl-sources.j
9
9
  import { findDanglingWikiRefsForActions } from '../wiki/wiki-ref-validation.js';
10
10
  import { actionTargetConnectionId } from './action-identity.js';
11
11
  import { NOTION_DEFAULT_MAX_KNOWLEDGE_CREATES_PER_RUN } from './adapters/notion/types.js';
12
- import { validateFinalIngestArtifacts, validateProvenanceRawPaths } from './artifact-gates.js';
12
+ import { formatFinalArtifactGateFindings, isFinalArtifactGateFindingPruneable, validateFinalIngestArtifacts, validateProvenanceRawPaths, } from './artifact-gates.js';
13
13
  import { selectRelevantCanonicalPins } from './canonical-pins.js';
14
- import { finalGateRepairPaths, repairFinalGateFailure } from './final-gate-repair.js';
15
14
  import { compareFinalizationDeclarations, deriveFinalizationTouchedSources, deriveFinalizationWikiPageKeys, } from './finalization-scope.js';
15
+ import { pruneFinalGateFindings, } from './final-gate-prune.js';
16
16
  import { FileIngestTraceWriter, ingestTracePathForJob, traceTimed } from './ingest-trace.js';
17
17
  import { formatIngestProfile, formatIngestProfileJson, readIngestProfile, resolveIngestProfileMode } from './ingest-profile.js';
18
18
  import { integrateWorkUnitPatch } from './isolated-diff/patch-integrator.js';
19
19
  import { resolveTextualConflict } from './isolated-diff/textual-conflict-resolver.js';
20
- import { runIsolatedWorkUnit } from './isolated-diff/work-unit-executor.js';
20
+ import { runIsolatedWorkUnit, workUnitPatchFileName } from './isolated-diff/work-unit-executor.js';
21
21
  import { sanitizeMemoryFlowError } from './memory-flow/live-buffer.js';
22
22
  import { buildSyncId, rawSourcesDirForSync } from './raw-sources-paths.js';
23
23
  import { buildStageIndexFromReportBody, } from './reports.js';
@@ -28,6 +28,7 @@ import { executeWorkUnit } from './stages/stage-3-work-units.js';
28
28
  import { runReconciliationStage4 } from './stages/stage-4-reconciliation.js';
29
29
  import { validateWuTouchedSources } from './stages/validate-wu-sources.js';
30
30
  import { assertSemanticLayerTargetPathsAllowed } from './semantic-layer-target-policy.js';
31
+ import { computeIngestWorkUnitInputHash, computeIngestWorkUnitPromptFingerprint, INGEST_WORK_UNIT_CACHE_NAMESPACE, ingestWorkUnitCacheScopeKey, materializeCachedWorkUnitReplayPatch, } from './work-unit-cache.js';
31
32
  import { createEmitArtifactResolutionTool } from './tools/emit-artifact-resolution.tool.js';
32
33
  import { createEmitConflictResolutionTool } from './tools/emit-conflict-resolution.tool.js';
33
34
  import { createEmitEvictionDecisionTool } from './tools/emit-eviction-decision.tool.js';
@@ -107,6 +108,9 @@ function semanticSourceMatchesTableRef(source, tableRef) {
107
108
  function rawPathsForAction(action, fallbackRawPaths) {
108
109
  return action.rawPaths && action.rawPaths.length > 0 ? [...new Set(action.rawPaths)] : fallbackRawPaths;
109
110
  }
111
+ function isCachedWorkUnitOutcome(outcome) {
112
+ return 'cacheHit' in outcome && outcome.cacheHit === true;
113
+ }
110
114
  export class IngestBundleRunner {
111
115
  deps;
112
116
  logger;
@@ -115,6 +119,114 @@ export class IngestBundleRunner {
115
119
  this.deps = deps;
116
120
  this.logger = deps.logger ?? noopLogger;
117
121
  }
122
+ async cachedWorkUnitOutcome(input) {
123
+ const inputHash = await computeIngestWorkUnitInputHash({
124
+ stagedDir: input.stagedDir,
125
+ connectionId: input.connectionId,
126
+ sourceKey: input.sourceKey,
127
+ unit: input.unit,
128
+ cliVersion: this.deps.settings.cliVersion,
129
+ promptFingerprint: input.promptFingerprint,
130
+ modelRole: input.modelRole,
131
+ });
132
+ const cached = await this.deps.contentCache.findCompletedResult({
133
+ namespace: INGEST_WORK_UNIT_CACHE_NAMESPACE,
134
+ scopeKey: ingestWorkUnitCacheScopeKey(input),
135
+ inputHash,
136
+ });
137
+ if (!cached) {
138
+ await input.trace.event('trace', 'work_unit', 'work_unit_cache_miss', {
139
+ unitKey: input.unit.unitKey,
140
+ inputHash,
141
+ });
142
+ return { cacheInputHash: inputHash, cacheHit: false };
143
+ }
144
+ await mkdir(input.patchDir, { recursive: true });
145
+ const patchPath = join(input.patchDir, workUnitPatchFileName(input.unitIndex, input.unit.unitKey));
146
+ if (cached.output.schemaVersion !== 2 || !Array.isArray(cached.output.artifactFiles)) {
147
+ await this.deps.contentCache.deleteResult({
148
+ namespace: INGEST_WORK_UNIT_CACHE_NAMESPACE,
149
+ scopeKey: ingestWorkUnitCacheScopeKey(input),
150
+ inputHash,
151
+ });
152
+ return { cacheInputHash: inputHash, cacheHit: false };
153
+ }
154
+ const materialized = await materializeCachedWorkUnitReplayPatch({
155
+ sessionWorktreeService: this.deps.sessionWorktreeService,
156
+ baseSha: input.ingestionBaseSha,
157
+ jobId: input.runId,
158
+ unitKey: input.unit.unitKey,
159
+ patchPath,
160
+ artifactFiles: cached.output.artifactFiles,
161
+ author: this.deps.storage.systemGitAuthor,
162
+ trace: input.trace,
163
+ });
164
+ if (materialized === 'unsafe_drift') {
165
+ await this.deps.contentCache.deleteResult({
166
+ namespace: INGEST_WORK_UNIT_CACHE_NAMESPACE,
167
+ scopeKey: ingestWorkUnitCacheScopeKey(input),
168
+ inputHash,
169
+ });
170
+ await input.trace.event('debug', 'work_unit', 'work_unit_cache_unsafe_drift', {
171
+ unitKey: input.unit.unitKey,
172
+ inputHash,
173
+ });
174
+ return { cacheInputHash: inputHash, cacheHit: false };
175
+ }
176
+ await input.trace.event('debug', 'work_unit', 'work_unit_cache_hit', {
177
+ unitKey: input.unit.unitKey,
178
+ inputHash,
179
+ producerRunId: cached.runId,
180
+ artifactFileCount: cached.output.artifactFiles.length,
181
+ });
182
+ await input.trace.event('debug', 'work_unit', 'work_unit_cache_replayed', {
183
+ unitKey: input.unit.unitKey,
184
+ patchPath,
185
+ inputHash,
186
+ });
187
+ return {
188
+ unitKey: input.unit.unitKey,
189
+ status: 'success',
190
+ preSha: '',
191
+ postSha: '',
192
+ actions: cached.output.actions,
193
+ touchedSlSources: cached.output.touchedSlSources,
194
+ slDisallowed: cached.output.slDisallowed,
195
+ slDisallowedReason: cached.output.slDisallowedReason,
196
+ patchPath,
197
+ patchTouchedPaths: cached.output.patchTouchedPaths,
198
+ artifactFiles: cached.output.artifactFiles,
199
+ cacheInputHash: inputHash,
200
+ cacheHit: true,
201
+ };
202
+ }
203
+ async saveSuccessfulWorkUnitCache(input) {
204
+ if (input.outcome.status !== 'success' || !input.outcome.patchPath) {
205
+ return;
206
+ }
207
+ await this.deps.contentCache.saveCompletedResult({
208
+ runId: input.runId,
209
+ namespace: INGEST_WORK_UNIT_CACHE_NAMESPACE,
210
+ scopeKey: ingestWorkUnitCacheScopeKey(input),
211
+ inputHash: input.inputHash,
212
+ output: {
213
+ schemaVersion: 2,
214
+ unitKey: input.outcome.unitKey,
215
+ patchTouchedPaths: input.outcome.patchTouchedPaths ?? [],
216
+ artifactFiles: input.outcome.artifactFiles ?? [],
217
+ actions: input.outcome.actions,
218
+ touchedSlSources: input.outcome.touchedSlSources,
219
+ slDisallowed: input.outcome.slDisallowed,
220
+ slDisallowedReason: input.outcome.slDisallowedReason,
221
+ },
222
+ metadata: {
223
+ syncId: input.syncId,
224
+ connectionId: input.connectionId,
225
+ sourceKey: input.sourceKey,
226
+ },
227
+ updatedAt: new Date().toISOString(),
228
+ });
229
+ }
118
230
  async run(job, ctx) {
119
231
  const unsubscribeRateLimitGovernor = this.subscribeRateLimitGovernor({
120
232
  trace: this.createTrace(job),
@@ -661,6 +773,39 @@ export class IngestBundleRunner {
661
773
  removedWikiPageKeysFromActions(actions) {
662
774
  return this.uniqueWikiPageKeys(actions.filter((action) => action.target === 'wiki' && action.type === 'removed').map((action) => action.key));
663
775
  }
776
+ markFinalGateDroppedSourceWorkUnits(input) {
777
+ const unitFailures = new Map();
778
+ for (const dropped of input.droppedSources) {
779
+ for (const workUnit of input.stageIndex.workUnits) {
780
+ const producedByAction = workUnit.actions.some((action) => action.target === 'sl' &&
781
+ action.key === dropped.sourceName &&
782
+ actionTargetConnectionId(action, input.fallbackConnectionId) === dropped.connectionId);
783
+ const producedByTouchedSource = workUnit.touchedSlSources.some((source) => source.connectionId === dropped.connectionId && source.sourceName === dropped.sourceName);
784
+ if (!producedByAction && !producedByTouchedSource) {
785
+ continue;
786
+ }
787
+ const reasons = unitFailures.get(workUnit.unitKey) ?? [];
788
+ reasons.push(`${dropped.connectionId}:${dropped.sourceName} (${dropped.reason})`);
789
+ unitFailures.set(workUnit.unitKey, reasons);
790
+ }
791
+ }
792
+ for (const [unitKey, reasons] of unitFailures) {
793
+ const reason = `final artifact gate dropped invalid source(s): ${reasons.join(', ')}`;
794
+ const reportWorkUnit = input.stageIndex.workUnits.find((workUnit) => workUnit.unitKey === unitKey);
795
+ if (reportWorkUnit) {
796
+ reportWorkUnit.status = 'failed';
797
+ reportWorkUnit.reason = reason;
798
+ }
799
+ const outcome = input.workUnitOutcomes.find((workUnit) => workUnit.unitKey === unitKey);
800
+ if (outcome) {
801
+ outcome.status = 'failed';
802
+ outcome.reason = reason;
803
+ }
804
+ if (!input.failedWorkUnits.includes(unitKey)) {
805
+ input.failedWorkUnits.push(unitKey);
806
+ }
807
+ }
808
+ }
664
809
  finalGateActionOrigins(input) {
665
810
  const actionContext = (action, fallbackRawPaths) => ({
666
811
  target: action.target,
@@ -799,6 +944,7 @@ export class IngestBundleRunner {
799
944
  skillsPrompt: input.skillsPrompt,
800
945
  syncId: input.syncId,
801
946
  sourceKey: input.job.sourceKey,
947
+ connectionId: input.job.connectionId,
802
948
  canonicalPins: input.canonicalPins,
803
949
  });
804
950
  input.memoryFlow?.emit({
@@ -810,12 +956,6 @@ export class IngestBundleRunner {
810
956
  sessionWorktreeGit: input.worktree.git,
811
957
  agentRunner: this.deps.agentRunner,
812
958
  validateTouchedSources: (touched) => validateWuTouchedSources({ ...slValidationDeps, slValidator: this.deps.slValidator }, touched),
813
- validateWikiRefs: (actions) => findDanglingWikiRefsForActions({
814
- wikiService: input.scopedWikiService,
815
- scope: 'GLOBAL',
816
- scopeId: null,
817
- actions,
818
- }),
819
959
  resetHardTo: (targetSha) => input.worktree.git.resetHardTo(targetSha),
820
960
  buildSystemPrompt: () => systemPrompt,
821
961
  buildUserPrompt: (wuInner) => buildWuUserPrompt({
@@ -1115,6 +1255,16 @@ export class IngestBundleRunner {
1115
1255
  const wuSkills = await this.deps.skillsRegistry.listSkills(wuSkillNames, 'memory_agent');
1116
1256
  const skillsPrompt = this.deps.skillsRegistry.buildSkillsPrompt(wuSkills, 'memory_agent');
1117
1257
  const canonicalPins = await this.deps.canonicalPins.listPins(slConnectionIds);
1258
+ const workUnitModelRole = 'candidateExtraction';
1259
+ const workUnitPromptFingerprint = computeIngestWorkUnitPromptFingerprint({
1260
+ cliVersion: this.deps.settings.cliVersion,
1261
+ baseFraming,
1262
+ skillsPrompt,
1263
+ canonicalPins,
1264
+ sourceKey: job.sourceKey,
1265
+ connectionId: job.connectionId,
1266
+ skillNames: wuSkillNames,
1267
+ });
1118
1268
  const workUnitOutcomes = [];
1119
1269
  const failedWorkUnits = [];
1120
1270
  const stageIndex = overrideReport
@@ -1150,9 +1300,6 @@ export class IngestBundleRunner {
1150
1300
  resolverAttempts: 0,
1151
1301
  resolverRepairs: 0,
1152
1302
  resolverFailures: 0,
1153
- gateRepairAttempts: 0,
1154
- gateRepairs: 0,
1155
- gateRepairFailures: 0,
1156
1303
  };
1157
1304
  latestIsolatedDiffSummary = isolatedDiffSummary;
1158
1305
  const stage3 = ctx?.startPhase(0.6);
@@ -1220,50 +1367,75 @@ export class IngestBundleRunner {
1220
1367
  if (workUnits.length === 0) {
1221
1368
  await stage3?.updateProgress(1.0, '0 of 0 work units complete');
1222
1369
  }
1370
+ const runFreshIsolatedWorkUnit = async (wu, index) => runIsolatedWorkUnit({
1371
+ unitIndex: index,
1372
+ ingestionBaseSha,
1373
+ sessionWorktreeService: this.deps.sessionWorktreeService,
1374
+ patchDir,
1375
+ trace: runTrace,
1376
+ workUnit: wu,
1377
+ abortSignal: ctx?.abortSignal,
1378
+ afterSuccess: (child) => copyTransientIngestEvidence(child.workdir, sessionWorktree.workdir),
1379
+ run: async (child) => {
1380
+ const scopedWikiService = this.deps.wikiService.forWorktree(child.workdir);
1381
+ const scopedSemanticLayerService = this.deps.semanticLayerService.forWorktree(child.workdir);
1382
+ return this.runWorkUnitInWorktree({
1383
+ job,
1384
+ syncId,
1385
+ wu,
1386
+ worktree: child,
1387
+ stagedDir,
1388
+ contextReport,
1389
+ ingestToolMetadata,
1390
+ slConnectionIds,
1391
+ wikiIndex,
1392
+ slIndex,
1393
+ priorProvenance: await this.deps.provenance.findLatestArtifactsForRawPaths(job.connectionId, job.sourceKey, wu.rawFiles),
1394
+ scopedWikiService,
1395
+ scopedSemanticLayerService,
1396
+ baseFraming,
1397
+ skillsPrompt,
1398
+ canonicalPins,
1399
+ workUnitSettings,
1400
+ transcriptDir,
1401
+ transcriptSummaries,
1402
+ recordTranscriptEntry,
1403
+ stageIndex,
1404
+ includeContextEvidenceTools: adapter.evidenceIndexing === 'documents' && !!contextReport,
1405
+ currentTableExists: (tableRef) => this.tableRefExistsInSemanticLayer(scopedSemanticLayerService, slConnectionIds, tableRef),
1406
+ abortSignal: ctx?.abortSignal,
1407
+ memoryFlow,
1408
+ wuSkillNames,
1409
+ });
1410
+ },
1411
+ });
1223
1412
  try {
1224
1413
  await Promise.all(workUnits.map((wu, index) => limitWorkUnit(() => this.withRateLimitWorkSlot(ctx?.abortSignal, async () => {
1225
- const outcome = await runIsolatedWorkUnit({
1414
+ const cached = await this.cachedWorkUnitOutcome({
1415
+ runId: createdRunRow.id,
1416
+ syncId,
1417
+ connectionId: job.connectionId,
1418
+ sourceKey: job.sourceKey,
1419
+ stagedDir,
1420
+ unit: wu,
1226
1421
  unitIndex: index,
1227
- ingestionBaseSha,
1228
- sessionWorktreeService: this.deps.sessionWorktreeService,
1229
1422
  patchDir,
1423
+ ingestionBaseSha,
1424
+ promptFingerprint: workUnitPromptFingerprint,
1425
+ modelRole: workUnitModelRole,
1230
1426
  trace: runTrace,
1231
- workUnit: wu,
1232
- abortSignal: ctx?.abortSignal,
1233
- afterSuccess: (child) => copyTransientIngestEvidence(child.workdir, sessionWorktree.workdir),
1234
- run: async (child) => {
1235
- const scopedWikiService = this.deps.wikiService.forWorktree(child.workdir);
1236
- const scopedSemanticLayerService = this.deps.semanticLayerService.forWorktree(child.workdir);
1237
- return this.runWorkUnitInWorktree({
1238
- job,
1239
- syncId,
1240
- wu,
1241
- worktree: child,
1242
- stagedDir,
1243
- contextReport,
1244
- ingestToolMetadata,
1245
- slConnectionIds,
1246
- wikiIndex,
1247
- slIndex,
1248
- priorProvenance: await this.deps.provenance.findLatestArtifactsForRawPaths(job.connectionId, job.sourceKey, wu.rawFiles),
1249
- scopedWikiService,
1250
- scopedSemanticLayerService,
1251
- baseFraming,
1252
- skillsPrompt,
1253
- canonicalPins,
1254
- workUnitSettings,
1255
- transcriptDir,
1256
- transcriptSummaries,
1257
- recordTranscriptEntry,
1258
- stageIndex,
1259
- includeContextEvidenceTools: adapter.evidenceIndexing === 'documents' && !!contextReport,
1260
- currentTableExists: (tableRef) => this.tableRefExistsInSemanticLayer(scopedSemanticLayerService, slConnectionIds, tableRef),
1261
- abortSignal: ctx?.abortSignal,
1262
- memoryFlow,
1263
- wuSkillNames,
1264
- });
1265
- },
1266
1427
  });
1428
+ const outcome = cached.cacheHit ? cached : await runFreshIsolatedWorkUnit(wu, index);
1429
+ if (!cached.cacheHit) {
1430
+ await this.saveSuccessfulWorkUnitCache({
1431
+ runId: createdRunRow.id,
1432
+ syncId,
1433
+ connectionId: job.connectionId,
1434
+ sourceKey: job.sourceKey,
1435
+ inputHash: cached.cacheInputHash,
1436
+ outcome,
1437
+ });
1438
+ }
1267
1439
  workUnitOutcomesByIndex[index] = outcome;
1268
1440
  for (const action of outcome.actions) {
1269
1441
  memoryFlow?.emit({
@@ -1288,20 +1460,25 @@ export class IngestBundleRunner {
1288
1460
  await this.deps.runs.markFailed(runRow.id);
1289
1461
  throw error;
1290
1462
  }
1291
- workUnitOutcomes.push(...workUnitOutcomesByIndex.filter((outcome) => Boolean(outcome)));
1292
- failedWorkUnits.push(...workUnitOutcomes.filter((outcome) => outcome.status === 'failed').map((outcome) => outcome.unitKey));
1293
- latestWorkUnits = workUnitOutcomes;
1294
- latestFailedWorkUnits = failedWorkUnits;
1295
- stageIndex.workUnits = workUnitOutcomes.map((o) => ({
1296
- unitKey: o.unitKey,
1297
- rawFiles: workUnits.find((w) => w.unitKey === o.unitKey)?.rawFiles ?? [],
1298
- status: o.status,
1299
- reason: o.reason,
1300
- actions: o.actions,
1301
- touchedSlSources: o.touchedSlSources,
1302
- slDisallowed: o.slDisallowed,
1303
- slDisallowedReason: o.slDisallowedReason,
1304
- }));
1463
+ const refreshWorkUnitState = () => {
1464
+ workUnitOutcomes.length = 0;
1465
+ workUnitOutcomes.push(...workUnitOutcomesByIndex.filter((outcome) => Boolean(outcome)));
1466
+ failedWorkUnits.length = 0;
1467
+ failedWorkUnits.push(...workUnitOutcomes.filter((outcome) => outcome.status === 'failed').map((outcome) => outcome.unitKey));
1468
+ latestWorkUnits = workUnitOutcomes;
1469
+ latestFailedWorkUnits = failedWorkUnits;
1470
+ stageIndex.workUnits = workUnitOutcomes.map((o) => ({
1471
+ unitKey: o.unitKey,
1472
+ rawFiles: workUnits.find((w) => w.unitKey === o.unitKey)?.rawFiles ?? [],
1473
+ status: o.status,
1474
+ reason: o.reason,
1475
+ actions: o.actions,
1476
+ touchedSlSources: o.touchedSlSources,
1477
+ slDisallowed: o.slDisallowed,
1478
+ slDisallowedReason: o.slDisallowedReason,
1479
+ }));
1480
+ };
1481
+ refreshWorkUnitState();
1305
1482
  activePhase = 'integration';
1306
1483
  const integrablePatchCount = workUnitOutcomesByIndex.filter((outcome) => outcome?.status === 'success' && !!outcome.patchPath).length;
1307
1484
  let integratedPatchCount = 0;
@@ -1313,128 +1490,151 @@ export class IngestBundleRunner {
1313
1490
  if (!wu) {
1314
1491
  continue;
1315
1492
  }
1316
- const integrationFailureDetails = {
1317
- unitKey: outcome.unitKey,
1318
- patchPath: outcome.patchPath,
1319
- allowedTargetConnectionIds: slConnectionIds,
1320
- };
1321
- activeFailureDetails = integrationFailureDetails;
1322
- emitStageProgress('integration', 80, `Integrating ${integratedPatchCount + 1}/${integrablePatchCount} patches: ${outcome.unitKey}`);
1323
- const integration = await integrateWorkUnitPatch({
1324
- unitKey: outcome.unitKey,
1325
- patchPath: outcome.patchPath,
1326
- integrationGit: sessionWorktree.git,
1327
- trace: runTrace,
1328
- author: this.deps.storage.systemGitAuthor,
1329
- slDisallowed: wu.slDisallowed === true,
1330
- allowedTargetConnectionIds: new Set(slConnectionIds),
1331
- validateAppliedTree: async (touchedPaths) => {
1332
- await validateFinalIngestArtifacts({
1333
- connectionIds: slConnectionIds,
1334
- changedWikiPageKeys: this.wikiPageKeysFromPaths(touchedPaths),
1335
- touchedSlSources: await this.touchedSlSourcesFromPaths(sessionWorktree, touchedPaths, await sessionWorktree.git.revParseHead()),
1336
- wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
1337
- semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1338
- validateTouchedSources: (touched) => validateWuTouchedSources({
1493
+ let outcomeForIntegration = outcome;
1494
+ let recomputedCachedPatch = false;
1495
+ while (true) {
1496
+ const patchPath = outcomeForIntegration.patchPath;
1497
+ if (!patchPath) {
1498
+ activeFailureDetails = undefined;
1499
+ break;
1500
+ }
1501
+ const integrationFailureDetails = {
1502
+ unitKey: outcomeForIntegration.unitKey,
1503
+ patchPath,
1504
+ allowedTargetConnectionIds: slConnectionIds,
1505
+ };
1506
+ activeFailureDetails = integrationFailureDetails;
1507
+ emitStageProgress('integration', 80, `Integrating ${integratedPatchCount + 1}/${integrablePatchCount} patches: ${outcomeForIntegration.unitKey}`);
1508
+ const integration = await integrateWorkUnitPatch({
1509
+ unitKey: outcomeForIntegration.unitKey,
1510
+ patchPath,
1511
+ integrationGit: sessionWorktree.git,
1512
+ trace: runTrace,
1513
+ author: this.deps.storage.systemGitAuthor,
1514
+ slDisallowed: wu.slDisallowed === true,
1515
+ allowedTargetConnectionIds: new Set(slConnectionIds),
1516
+ validateAppliedTree: async (touchedPaths) => {
1517
+ const gate = await validateFinalIngestArtifacts({
1518
+ connectionIds: slConnectionIds,
1519
+ changedWikiPageKeys: this.wikiPageKeysFromPaths(touchedPaths),
1520
+ touchedSlSources: await this.touchedSlSourcesFromPaths(sessionWorktree, touchedPaths, await sessionWorktree.git.revParseHead()),
1521
+ wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
1339
1522
  semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1340
- connections: this.deps.connections,
1341
- configService: sessionWorktree.config,
1342
- gitService: sessionWorktree.git,
1343
- slSourcesRepository: this.deps.slSourcesRepository,
1344
- probeRowCount: this.deps.settings.probeRowCount,
1345
- slValidator: this.deps.slValidator,
1346
- }, touched),
1347
- tableExists: (connectionId, tableRef) => this.tableRefExistsInSemanticLayer(this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir), [connectionId], tableRef),
1523
+ validateTouchedSources: (touched) => validateWuTouchedSources({
1524
+ semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1525
+ connections: this.deps.connections,
1526
+ configService: sessionWorktree.config,
1527
+ gitService: sessionWorktree.git,
1528
+ slSourcesRepository: this.deps.slSourcesRepository,
1529
+ probeRowCount: this.deps.settings.probeRowCount,
1530
+ slValidator: this.deps.slValidator,
1531
+ }, touched),
1532
+ tableExists: (connectionId, tableRef) => this.tableRefExistsInSemanticLayer(this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir), [connectionId], tableRef),
1533
+ });
1534
+ if (!gate.ok) {
1535
+ const blocking = gate.findings.filter((finding) => !isFinalArtifactGateFindingPruneable(finding));
1536
+ if (blocking.length > 0) {
1537
+ throw new Error(formatFinalArtifactGateFindings(blocking));
1538
+ }
1539
+ await runTrace.event('debug', 'integration', 'patch_semantic_gate_deferred_to_final_prune', {
1540
+ unitKey: outcomeForIntegration.unitKey,
1541
+ findings: gate.findings,
1542
+ });
1543
+ }
1544
+ },
1545
+ resolveTextualConflict: async (context) => {
1546
+ emitStageProgress('integration', 81, `Resolving text conflict for ${context.unitKey}`);
1547
+ const result = await resolveTextualConflict({
1548
+ agentRunner: this.deps.agentRunner,
1549
+ workdir: sessionWorktree.workdir,
1550
+ unitKey: context.unitKey,
1551
+ patchPath: context.patchPath,
1552
+ touchedPaths: context.touchedPaths,
1553
+ trace: runTrace,
1554
+ reason: context.reason,
1555
+ verify: context.verify,
1556
+ maxAttempts: 2,
1557
+ stepBudget: 12,
1558
+ abortSignal: ctx?.abortSignal,
1559
+ });
1560
+ emitStageProgress('integration', 82, result.status === 'repaired'
1561
+ ? `Resolved text conflict for ${context.unitKey}`
1562
+ : `Text conflict resolver failed for ${context.unitKey}`);
1563
+ return result;
1564
+ },
1565
+ });
1566
+ if (integration.textualResolution) {
1567
+ isolatedDiffSummary.resolverAttempts += integration.textualResolution.attempts;
1568
+ if (integration.textualResolution.status === 'repaired') {
1569
+ isolatedDiffSummary.textualConflicts += 1;
1570
+ isolatedDiffSummary.resolverRepairs += 1;
1571
+ }
1572
+ else {
1573
+ isolatedDiffSummary.resolverFailures += 1;
1574
+ }
1575
+ }
1576
+ if (integration.status !== 'accepted' &&
1577
+ isCachedWorkUnitOutcome(outcomeForIntegration) &&
1578
+ !recomputedCachedPatch) {
1579
+ await this.deps.contentCache.deleteResult({
1580
+ namespace: INGEST_WORK_UNIT_CACHE_NAMESPACE,
1581
+ scopeKey: ingestWorkUnitCacheScopeKey({ connectionId: job.connectionId, sourceKey: job.sourceKey }),
1582
+ inputHash: outcomeForIntegration.cacheInputHash,
1348
1583
  });
1349
- },
1350
- resolveTextualConflict: async (context) => {
1351
- emitStageProgress('integration', 81, `Resolving text conflict for ${context.unitKey}`);
1352
- const result = await resolveTextualConflict({
1353
- agentRunner: this.deps.agentRunner,
1354
- workdir: sessionWorktree.workdir,
1355
- unitKey: context.unitKey,
1356
- patchPath: context.patchPath,
1357
- touchedPaths: context.touchedPaths,
1358
- trace: runTrace,
1359
- reason: context.reason,
1360
- verify: context.verify,
1361
- maxAttempts: 2,
1362
- stepBudget: 12,
1363
- abortSignal: ctx?.abortSignal,
1584
+ await runTrace.event('debug', 'integration', 'work_unit_cache_stale_recompute', {
1585
+ unitKey: outcomeForIntegration.unitKey,
1586
+ inputHash: outcomeForIntegration.cacheInputHash,
1587
+ reason: integration.reason,
1364
1588
  });
1365
- emitStageProgress('integration', 82, result.status === 'repaired'
1366
- ? `Resolved text conflict for ${context.unitKey}`
1367
- : `Text conflict resolver failed for ${context.unitKey}`);
1368
- return result;
1369
- },
1370
- repairGateFailure: async (context) => {
1371
- emitStageProgress('integration', 82, `Repairing semantic gate for ${context.unitKey}`);
1372
- const result = await repairFinalGateFailure({
1373
- agentRunner: this.deps.agentRunner,
1374
- workdir: sessionWorktree.workdir,
1375
- gateError: context.reason,
1376
- allowedPaths: context.touchedPaths,
1377
- trace: runTrace,
1378
- repairKind: 'patch_semantic_gate',
1379
- verify: context.verify,
1380
- maxAttempts: 2,
1381
- stepBudget: 16,
1382
- abortSignal: ctx?.abortSignal,
1589
+ const recomputed = await runFreshIsolatedWorkUnit(wu, index);
1590
+ workUnitOutcomesByIndex[index] = recomputed;
1591
+ await this.saveSuccessfulWorkUnitCache({
1592
+ runId: createdRunRow.id,
1593
+ syncId,
1594
+ connectionId: job.connectionId,
1595
+ sourceKey: job.sourceKey,
1596
+ inputHash: outcomeForIntegration.cacheInputHash,
1597
+ outcome: recomputed,
1383
1598
  });
1384
- emitStageProgress('integration', 83, result.status === 'repaired'
1385
- ? `Repaired semantic gate for ${context.unitKey}`
1386
- : `Semantic gate repair failed for ${context.unitKey}`);
1387
- return result;
1388
- },
1389
- });
1390
- if (integration.textualResolution) {
1391
- isolatedDiffSummary.resolverAttempts += integration.textualResolution.attempts;
1392
- if (integration.textualResolution.status === 'repaired') {
1393
- isolatedDiffSummary.textualConflicts += 1;
1394
- isolatedDiffSummary.resolverRepairs += 1;
1599
+ refreshWorkUnitState();
1600
+ if (recomputed.status !== 'success' || !recomputed.patchPath) {
1601
+ activeFailureDetails = undefined;
1602
+ break;
1603
+ }
1604
+ outcomeForIntegration = recomputed;
1605
+ recomputedCachedPatch = true;
1606
+ continue;
1395
1607
  }
1396
- else {
1397
- isolatedDiffSummary.resolverFailures += 1;
1608
+ if (integration.status === 'textual_conflict') {
1609
+ isolatedDiffSummary.textualConflicts += 1;
1610
+ await this.deps.runs.markFailed(runRow.id);
1611
+ cleanupOutcome = 'conflict';
1612
+ activeFailureDetails = {
1613
+ ...integrationFailureDetails,
1614
+ touchedPaths: integration.touchedPaths,
1615
+ reason: integration.reason,
1616
+ };
1617
+ throw new Error(`isolated diff textual conflict in ${outcomeForIntegration.unitKey}: ${integration.reason}`);
1398
1618
  }
1399
- }
1400
- if (integration.gateRepair) {
1401
- isolatedDiffSummary.gateRepairAttempts += integration.gateRepair.attempts;
1402
- if (integration.gateRepair.status === 'repaired') {
1619
+ if (integration.status === 'semantic_conflict') {
1403
1620
  isolatedDiffSummary.semanticConflicts += 1;
1404
- isolatedDiffSummary.gateRepairs += 1;
1621
+ await this.deps.runs.markFailed(runRow.id);
1622
+ cleanupOutcome = 'conflict';
1623
+ activeFailureDetails = {
1624
+ ...integrationFailureDetails,
1625
+ touchedPaths: integration.touchedPaths,
1626
+ reason: integration.reason,
1627
+ };
1628
+ throw new Error(`isolated diff semantic conflict in ${outcomeForIntegration.unitKey}: ${integration.reason}`);
1405
1629
  }
1406
- else {
1407
- isolatedDiffSummary.gateRepairFailures += 1;
1630
+ activeFailureDetails = undefined;
1631
+ if (integration.touchedPaths.length > 0) {
1632
+ isolatedDiffSummary.acceptedPatches += 1;
1633
+ integratedPatchCount += 1;
1408
1634
  }
1635
+ emitStageProgress('integration', 83, `Integrated ${integratedPatchCount}/${integrablePatchCount} patches`);
1636
+ break;
1409
1637
  }
1410
- if (integration.status === 'textual_conflict') {
1411
- isolatedDiffSummary.textualConflicts += 1;
1412
- await this.deps.runs.markFailed(runRow.id);
1413
- cleanupOutcome = 'conflict';
1414
- activeFailureDetails = {
1415
- ...integrationFailureDetails,
1416
- touchedPaths: integration.touchedPaths,
1417
- reason: integration.reason,
1418
- };
1419
- throw new Error(`isolated diff textual conflict in ${outcome.unitKey}: ${integration.reason}`);
1420
- }
1421
- if (integration.status === 'semantic_conflict') {
1422
- isolatedDiffSummary.semanticConflicts += 1;
1423
- await this.deps.runs.markFailed(runRow.id);
1424
- cleanupOutcome = 'conflict';
1425
- activeFailureDetails = {
1426
- ...integrationFailureDetails,
1427
- touchedPaths: integration.touchedPaths,
1428
- reason: integration.reason,
1429
- };
1430
- throw new Error(`isolated diff semantic conflict in ${outcome.unitKey}: ${integration.reason}`);
1431
- }
1432
- activeFailureDetails = undefined;
1433
- if (integration.touchedPaths.length > 0) {
1434
- isolatedDiffSummary.acceptedPatches += 1;
1435
- integratedPatchCount += 1;
1436
- }
1437
- emitStageProgress('integration', 83, `Integrated ${integratedPatchCount}/${integrablePatchCount} patches`);
1438
1638
  }
1439
1639
  }
1440
1640
  const carryForwardResult = contextReport && this.deps.contextCandidateCarryforward
@@ -1888,6 +2088,19 @@ export class IngestBundleRunner {
1888
2088
  ...finalizationTouchedSources.map((source) => source.connectionId),
1889
2089
  ]),
1890
2090
  ].sort();
2091
+ const preWikiSlRefRepairSha = await sessionWorktree.git.revParseHead();
2092
+ const preWikiSlRefRepairPaths = preReconciliationSha && preWikiSlRefRepairSha && preReconciliationSha !== preWikiSlRefRepairSha
2093
+ ? (await sessionWorktree.git.diffNameStatus(preReconciliationSha, preWikiSlRefRepairSha)).map((entry) => entry.path)
2094
+ : [];
2095
+ const wikiPageKeysForFinalPrune = this.uniqueWikiPageKeys([
2096
+ ...(isolatedDiffEnabled ? projectionChangedWikiPageKeys : []),
2097
+ ...workUnitOutcomes
2098
+ .flatMap((outcome) => outcome.patchTouchedPaths ?? [])
2099
+ .flatMap((path) => this.wikiPageKeysFromPaths([path])),
2100
+ ...this.wikiPageKeysFromActions(reconcileActions),
2101
+ ...finalizationChangedWikiPageKeys,
2102
+ ...preWikiSlRefRepairPaths.flatMap((path) => this.wikiPageKeysFromPaths([path])),
2103
+ ]);
1891
2104
  activePhase = 'wiki_sl_ref_repair';
1892
2105
  emitStageProgress('wiki_sl_ref_repair', 88, 'Repairing wiki semantic-layer references');
1893
2106
  wikiSlRefRepairResult = await traceTimed(runTrace, 'wiki_sl_ref_repair', 'wiki_sl_refs_repair', { connectionIds: repairConnectionIds }, () => repairWikiSlRefs({
@@ -1895,6 +2108,7 @@ export class IngestBundleRunner {
1895
2108
  semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1896
2109
  configService: sessionWorktree.config,
1897
2110
  connectionIds: repairConnectionIds,
2111
+ deferGlobalPageKeys: wikiPageKeysForFinalPrune,
1898
2112
  }));
1899
2113
  await runTrace.event('debug', 'wiki_sl_ref_repair', 'wiki_sl_refs_repaired', {
1900
2114
  repairCount: wikiSlRefRepairResult.repairs.length,
@@ -1970,76 +2184,77 @@ export class IngestBundleRunner {
1970
2184
  activePhase = 'final_gates';
1971
2185
  activeFailureDetails = finalArtifactGateTraceData;
1972
2186
  emitStageProgress('final_gates', 89, 'Running final artifact gates');
1973
- const runFinalArtifactGates = async () => {
1974
- await validateFinalIngestArtifacts({
1975
- connectionIds: repairConnectionIds,
1976
- changedWikiPageKeys: finalChangedWikiPageKeys,
1977
- touchedSlSources: finalTouchedSlSources,
1978
- wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
2187
+ let finalGatePrunedReferences = [];
2188
+ let finalGateDroppedSources = [];
2189
+ const runFinalArtifactGates = async (touchedSources = finalTouchedSlSources) => validateFinalIngestArtifacts({
2190
+ connectionIds: repairConnectionIds,
2191
+ changedWikiPageKeys: finalChangedWikiPageKeys,
2192
+ touchedSlSources: touchedSources,
2193
+ wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
2194
+ semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
2195
+ validateTouchedSources: (touched) => validateWuTouchedSources({
1979
2196
  semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
1980
- validateTouchedSources: (touched) => validateWuTouchedSources({
1981
- semanticLayerService: this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir),
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),
1990
- });
1991
- };
1992
- try {
1993
- await traceTimed(runTrace, 'final_gates', 'final_artifact_gates', finalArtifactGateTraceData, runFinalArtifactGates);
1994
- }
1995
- catch (error) {
1996
- const gateError = this.errorMessage(error);
1997
- const repairPaths = finalGateRepairPaths({
1998
- changedWikiPageKeys: finalChangedWikiPageKeys,
1999
- touchedSlSourcePaths: await this.touchedSlSourcePaths(sessionWorktree.workdir, finalTouchedSlSources),
2000
- });
2001
- emitStageProgress('final_gates', 89, 'Repairing final artifact gates');
2002
- const gateRepair = await repairFinalGateFailure({
2003
- agentRunner: this.deps.agentRunner,
2197
+ connections: this.deps.connections,
2198
+ configService: sessionWorktree.config,
2199
+ gitService: sessionWorktree.git,
2200
+ slSourcesRepository: this.deps.slSourcesRepository,
2201
+ probeRowCount: this.deps.settings.probeRowCount,
2202
+ slValidator: this.deps.slValidator,
2203
+ }, touched),
2204
+ tableExists: (connectionId, tableRef) => this.tableRefExistsInSemanticLayer(this.deps.semanticLayerService.forWorktree(sessionWorktree.workdir), [connectionId], tableRef),
2205
+ });
2206
+ const firstGate = await traceTimed(runTrace, 'final_gates', 'final_artifact_gates', finalArtifactGateTraceData, () => runFinalArtifactGates());
2207
+ if (!firstGate.ok) {
2208
+ emitStageProgress('final_gates', 89, 'Pruning final artifact gates');
2209
+ const firstPrune = await pruneFinalGateFindings({
2004
2210
  workdir: sessionWorktree.workdir,
2005
- gateError,
2006
- allowedPaths: repairPaths,
2211
+ semanticLayerFiles: sessionWorktree.config,
2212
+ findings: firstGate.findings,
2213
+ droppedSources: [],
2007
2214
  trace: runTrace,
2008
- repairKind: 'final_artifact_gate',
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,
2019
- stepBudget: 16,
2020
- abortSignal: ctx?.abortSignal,
2215
+ author: this.deps.storage.systemGitAuthor,
2216
+ wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
2021
2217
  });
2022
- isolatedDiffSummary.gateRepairAttempts += gateRepair.attempts;
2023
- if (gateRepair.status === 'failed') {
2024
- isolatedDiffSummary.gateRepairFailures += 1;
2025
- activeFailureDetails = {
2026
- ...finalArtifactGateTraceData,
2027
- gateRepair,
2028
- gateError,
2029
- };
2030
- throw new Error(`${gateError}\ngate repair failed: ${gateRepair.reason}`);
2218
+ finalGateDroppedSources = firstPrune.droppedSources;
2219
+ finalGatePrunedReferences = firstPrune.prunedReferences;
2220
+ const droppedKeys = new Set(finalGateDroppedSources.map((source) => `${source.connectionId}:${source.sourceName}`));
2221
+ const touchedAfterDrop = finalTouchedSlSources.filter((source) => !droppedKeys.has(`${source.connectionId}:${source.sourceName}`));
2222
+ const secondGate = await runFinalArtifactGates(touchedAfterDrop);
2223
+ if (!secondGate.ok) {
2224
+ const secondPrune = await pruneFinalGateFindings({
2225
+ workdir: sessionWorktree.workdir,
2226
+ semanticLayerFiles: sessionWorktree.config,
2227
+ findings: secondGate.findings.filter((finding) => finding.kind !== 'invalid_source'),
2228
+ droppedSources: finalGateDroppedSources,
2229
+ trace: runTrace,
2230
+ author: this.deps.storage.systemGitAuthor,
2231
+ wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
2232
+ });
2233
+ finalGateDroppedSources = secondPrune.droppedSources;
2234
+ finalGatePrunedReferences = [...finalGatePrunedReferences, ...secondPrune.prunedReferences];
2235
+ }
2236
+ const pruneTouchedPaths = await sessionWorktree.git.changedPaths();
2237
+ if (pruneTouchedPaths.length > 0) {
2238
+ const pruneCommit = await sessionWorktree.git.commitFiles(pruneTouchedPaths, `ingest(${job.sourceKey}): prune final gate findings syncId=${syncId}`, this.deps.storage.systemGitAuthor.name, this.deps.storage.systemGitAuthor.email);
2239
+ await runTrace.event('debug', 'final_gates', 'final_gate_prune_committed', {
2240
+ commitSha: pruneCommit.created ? pruneCommit.commitHash : null,
2241
+ touchedPaths: pruneTouchedPaths,
2242
+ });
2031
2243
  }
2032
- // The repair loop re-ran the gates via `verify` before reporting
2033
- // success, so a repaired status here means the tree already passed.
2034
- isolatedDiffSummary.gateRepairs += 1;
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);
2036
- if (!repairCommit.created) {
2037
- isolatedDiffSummary.gateRepairFailures += 1;
2038
- throw new Error('final gate repair produced no committable changes');
2244
+ const confirmGate = await runFinalArtifactGates(touchedAfterDrop);
2245
+ if (!confirmGate.ok) {
2246
+ throw new Error(`final artifact gates still failed after prune:\n${formatFinalArtifactGateFindings(confirmGate.findings)}`);
2039
2247
  }
2040
- await runTrace.event('debug', 'final_gates', 'final_gate_repair_committed', {
2041
- commitSha: repairCommit.commitHash,
2042
- repairedPaths: gateRepair.changedPaths,
2248
+ await runTrace.event('info', 'final_gates', 'final_gate_prune_finished', {
2249
+ prunedReferences: finalGatePrunedReferences,
2250
+ droppedSources: finalGateDroppedSources,
2251
+ });
2252
+ this.markFinalGateDroppedSourceWorkUnits({
2253
+ stageIndex,
2254
+ workUnitOutcomes,
2255
+ failedWorkUnits,
2256
+ droppedSources: finalGateDroppedSources,
2257
+ fallbackConnectionId: job.connectionId,
2043
2258
  });
2044
2259
  }
2045
2260
  activeFailureDetails = undefined;
@@ -2228,6 +2443,8 @@ export class IngestBundleRunner {
2228
2443
  provenanceRows: reportProvenanceRows,
2229
2444
  toolTranscripts: reportToolTranscripts,
2230
2445
  finalization: finalizationOutcome,
2446
+ finalGatePrunedReferences,
2447
+ finalGateDroppedSources,
2231
2448
  wikiSlRefRepairs: wikiSlRefRepairResult.repairs,
2232
2449
  wikiSlRefRepairWarnings: wikiSlRefRepairResult.warnings,
2233
2450
  ...(reportMemoryFlow ? { memoryFlow: reportMemoryFlow } : {}),
@@ -2333,6 +2550,8 @@ export class IngestBundleRunner {
2333
2550
  failedWorkUnits,
2334
2551
  artifactsWritten: provenanceRows.filter((r) => r.actionType !== 'skipped').length,
2335
2552
  commitSha,
2553
+ finalGatePrunedReferences,
2554
+ finalGateDroppedSources,
2336
2555
  };
2337
2556
  }
2338
2557
  finally {