@wildo-ai/saas-backend-lib 1.1.0 → 1.1.2

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 (265) hide show
  1. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts +14 -3
  2. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts.map +1 -1
  3. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js +115 -22
  4. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js.map +1 -1
  5. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts +3 -1
  6. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts.map +1 -1
  7. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js +8 -3
  8. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js.map +1 -1
  9. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts +11 -0
  10. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts.map +1 -1
  11. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js +11 -2
  12. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js.map +1 -1
  13. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts +1 -0
  14. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts.map +1 -1
  15. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js +6 -1
  16. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js.map +1 -1
  17. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.d.ts.map +1 -1
  18. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js +16 -0
  19. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js.map +1 -1
  20. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts +20 -6
  21. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts.map +1 -1
  22. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js +88 -32
  23. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js.map +1 -1
  24. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts +3 -1
  25. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts.map +1 -1
  26. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js +32 -7
  27. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js.map +1 -1
  28. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts +8 -5
  29. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts.map +1 -1
  30. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.js.map +1 -1
  31. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts +31 -0
  32. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts.map +1 -1
  33. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js +127 -5
  34. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js.map +1 -1
  35. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts +71 -1
  36. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts.map +1 -1
  37. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js +62 -2
  38. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js.map +1 -1
  39. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts +21 -0
  40. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts.map +1 -1
  41. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js +126 -4
  42. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js.map +1 -1
  43. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts +3 -26
  44. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts.map +1 -1
  45. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js +3 -68
  46. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js.map +1 -1
  47. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts.map +1 -1
  48. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js +4 -2
  49. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js.map +1 -1
  50. package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js +6 -6
  51. package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js.map +1 -1
  52. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts +38 -0
  53. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts.map +1 -1
  54. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js +40 -0
  55. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js.map +1 -1
  56. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts +20 -0
  57. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts.map +1 -1
  58. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js +28 -0
  59. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js.map +1 -1
  60. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts +12 -0
  61. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts.map +1 -0
  62. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js +118 -0
  63. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js.map +1 -0
  64. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.d.ts.map +1 -1
  65. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js +8 -5
  66. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js.map +1 -1
  67. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.d.ts.map +1 -1
  68. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js +14 -0
  69. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js.map +1 -1
  70. package/dist/esm/config/custom/application-config-controller.backend.service.d.ts.map +1 -1
  71. package/dist/esm/config/custom/application-config-controller.backend.service.js +7 -1
  72. package/dist/esm/config/custom/application-config-controller.backend.service.js.map +1 -1
  73. package/dist/esm/config/models/app-config.backend.schemas.d.ts +4 -0
  74. package/dist/esm/config/models/app-config.backend.schemas.d.ts.map +1 -1
  75. package/dist/esm/config/services/app-configuration.backend.utils.d.ts +10 -1
  76. package/dist/esm/config/services/app-configuration.backend.utils.d.ts.map +1 -1
  77. package/dist/esm/config/services/app-configuration.backend.utils.js +13 -2
  78. package/dist/esm/config/services/app-configuration.backend.utils.js.map +1 -1
  79. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.d.ts +2 -2
  80. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js +2 -2
  81. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js.map +1 -1
  82. package/dist/esm/databases/models/filter.backend.schemas.js +2 -2
  83. package/dist/esm/databases/models/filter.backend.schemas.js.map +1 -1
  84. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.d.ts.map +1 -1
  85. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js +20 -1
  86. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js.map +1 -1
  87. package/dist/esm/databases/services/redis.service.d.ts +27 -0
  88. package/dist/esm/databases/services/redis.service.d.ts.map +1 -1
  89. package/dist/esm/databases/services/redis.service.js +29 -0
  90. package/dist/esm/databases/services/redis.service.js.map +1 -1
  91. package/dist/esm/errors/services/error-handler.backend.service.d.ts.map +1 -1
  92. package/dist/esm/errors/services/error-handler.backend.service.js +5 -0
  93. package/dist/esm/errors/services/error-handler.backend.service.js.map +1 -1
  94. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts +41 -0
  95. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts.map +1 -0
  96. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js +40 -0
  97. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js.map +1 -0
  98. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts +64 -0
  99. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts.map +1 -0
  100. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js +193 -0
  101. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js.map +1 -0
  102. package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts +13 -2
  103. package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts.map +1 -1
  104. package/dist/esm/external-data/external-data-pipeline-run.backend.service.js +8 -2
  105. package/dist/esm/external-data/external-data-pipeline-run.backend.service.js.map +1 -1
  106. package/dist/esm/external-data/external-data-transform.backend.d.ts.map +1 -1
  107. package/dist/esm/external-data/external-data-transform.backend.js +7 -1
  108. package/dist/esm/external-data/external-data-transform.backend.js.map +1 -1
  109. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts +2 -0
  110. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts.map +1 -1
  111. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js +13 -0
  112. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js.map +1 -1
  113. package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +14 -0
  114. package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
  115. package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +6 -0
  116. package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
  117. package/dist/esm/files/controllers/file.controller.d.ts +24 -1
  118. package/dist/esm/files/controllers/file.controller.d.ts.map +1 -1
  119. package/dist/esm/files/controllers/file.controller.js +66 -2
  120. package/dist/esm/files/controllers/file.controller.js.map +1 -1
  121. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts +17 -0
  122. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts.map +1 -0
  123. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js +1 -0
  124. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js.map +1 -0
  125. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts +5 -1
  126. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts.map +1 -1
  127. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js +42 -3
  128. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js.map +1 -1
  129. package/dist/esm/files/services/file-operation-handler.backend.service.d.ts +18 -0
  130. package/dist/esm/files/services/file-operation-handler.backend.service.d.ts.map +1 -1
  131. package/dist/esm/files/services/file-operation-handler.backend.service.js +36 -28
  132. package/dist/esm/files/services/file-operation-handler.backend.service.js.map +1 -1
  133. package/dist/esm/files/services/file-upload-grant.backend.service.d.ts +163 -0
  134. package/dist/esm/files/services/file-upload-grant.backend.service.d.ts.map +1 -0
  135. package/dist/esm/files/services/file-upload-grant.backend.service.js +442 -0
  136. package/dist/esm/files/services/file-upload-grant.backend.service.js.map +1 -0
  137. package/dist/esm/files/services/files.backend.service.d.ts +19 -0
  138. package/dist/esm/files/services/files.backend.service.d.ts.map +1 -1
  139. package/dist/esm/files/services/files.backend.service.js +78 -15
  140. package/dist/esm/files/services/files.backend.service.js.map +1 -1
  141. package/dist/esm/files/utils/file-uploader.utils.d.ts.map +1 -1
  142. package/dist/esm/files/utils/file-uploader.utils.js +12 -0
  143. package/dist/esm/files/utils/file-uploader.utils.js.map +1 -1
  144. package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js +5 -5
  145. package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js.map +1 -1
  146. package/dist/esm/flows-actors/services/agents.backend.service.d.ts +79 -0
  147. package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
  148. package/dist/esm/flows-actors/services/agents.backend.service.js +319 -42
  149. package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
  150. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +151 -41
  151. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
  152. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +300 -139
  153. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
  154. package/dist/esm/index.d.ts +4 -1
  155. package/dist/esm/index.d.ts.map +1 -1
  156. package/dist/esm/index.js +14 -1
  157. package/dist/esm/index.js.map +1 -1
  158. package/dist/esm/inject/models/container.d.ts.map +1 -1
  159. package/dist/esm/inject/models/container.js +4 -0
  160. package/dist/esm/inject/models/container.js.map +1 -1
  161. package/dist/esm/inject/models/service-types.d.ts +3 -0
  162. package/dist/esm/inject/models/service-types.d.ts.map +1 -1
  163. package/dist/esm/inject/models/service-types.js +2 -0
  164. package/dist/esm/inject/models/service-types.js.map +1 -1
  165. package/dist/esm/mcp/controllers/mcp.controller.d.ts.map +1 -1
  166. package/dist/esm/mcp/controllers/mcp.controller.js +12 -4
  167. package/dist/esm/mcp/controllers/mcp.controller.js.map +1 -1
  168. package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts +34 -0
  169. package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts.map +1 -1
  170. package/dist/esm/mcp/models/mcp-tool.backend.schemas.js +42 -0
  171. package/dist/esm/mcp/models/mcp-tool.backend.schemas.js.map +1 -1
  172. package/dist/esm/mcp/services/mcp-server.backend.service.d.ts +29 -1
  173. package/dist/esm/mcp/services/mcp-server.backend.service.d.ts.map +1 -1
  174. package/dist/esm/mcp/services/mcp-server.backend.service.js +131 -3
  175. package/dist/esm/mcp/services/mcp-server.backend.service.js.map +1 -1
  176. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts +64 -2
  177. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts.map +1 -1
  178. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js +130 -6
  179. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js.map +1 -1
  180. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts +9 -0
  181. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts.map +1 -1
  182. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js +11 -36
  183. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js.map +1 -1
  184. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.d.ts.map +1 -1
  185. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js +9 -7
  186. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js.map +1 -1
  187. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts +5 -5
  188. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts.map +1 -1
  189. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js +14 -13
  190. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js.map +1 -1
  191. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts +2 -1
  192. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts.map +1 -1
  193. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js +2 -2
  194. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js.map +1 -1
  195. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.d.ts.map +1 -1
  196. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js +9 -7
  197. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js.map +1 -1
  198. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.d.ts.map +1 -1
  199. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js +4 -1
  200. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js.map +1 -1
  201. package/dist/esm/requests/service/websocket.service.d.ts.map +1 -1
  202. package/dist/esm/requests/service/websocket.service.js +56 -7
  203. package/dist/esm/requests/service/websocket.service.js.map +1 -1
  204. package/dist/esm/resources/models/custom-service-implementations.backend.definition.d.ts.map +1 -1
  205. package/dist/esm/resources/models/custom-service-implementations.backend.definition.js +2 -0
  206. package/dist/esm/resources/models/custom-service-implementations.backend.definition.js.map +1 -1
  207. package/dist/esm/resources/services/resource-id.backend.service.js +3 -3
  208. package/dist/esm/resources/services/resource-id.backend.service.js.map +1 -1
  209. package/dist/esm/resources/services/resources-registry.backend.service.js +1 -1
  210. package/dist/esm/resources/services/resources-registry.backend.service.js.map +1 -1
  211. package/dist/esm/runtime/services/execution-context-creator.backend.service.d.ts.map +1 -1
  212. package/dist/esm/runtime/services/execution-context-creator.backend.service.js +13 -7
  213. package/dist/esm/runtime/services/execution-context-creator.backend.service.js.map +1 -1
  214. package/dist/esm/runtime/services/start-application.backend.service.d.ts +28 -0
  215. package/dist/esm/runtime/services/start-application.backend.service.d.ts.map +1 -1
  216. package/dist/esm/runtime/services/start-application.backend.service.js +82 -0
  217. package/dist/esm/runtime/services/start-application.backend.service.js.map +1 -1
  218. package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts +3 -23
  219. package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts.map +1 -1
  220. package/dist/esm/security/authentications/models/jwt.backend.schemas.js +20 -45
  221. package/dist/esm/security/authentications/models/jwt.backend.schemas.js.map +1 -1
  222. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts +9 -1
  223. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts.map +1 -1
  224. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js +9 -1
  225. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js.map +1 -1
  226. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.d.ts.map +1 -1
  227. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js +5 -3
  228. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js.map +1 -1
  229. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts +25 -1
  230. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts.map +1 -1
  231. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js +55 -4
  232. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js.map +1 -1
  233. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.d.ts.map +1 -1
  234. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js +24 -1
  235. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js.map +1 -1
  236. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts +117 -0
  237. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts.map +1 -0
  238. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js +104 -0
  239. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js.map +1 -0
  240. package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts +101 -6
  241. package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts.map +1 -1
  242. package/dist/esm/security/authorizations/services/authorizations.backend.service.js +210 -30
  243. package/dist/esm/security/authorizations/services/authorizations.backend.service.js.map +1 -1
  244. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts +18 -1
  245. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts.map +1 -1
  246. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js +110 -74
  247. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js.map +1 -1
  248. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts +22 -0
  249. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts.map +1 -0
  250. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js +22 -0
  251. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js.map +1 -0
  252. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts +17 -3
  253. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts.map +1 -1
  254. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js +12 -0
  255. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js.map +1 -1
  256. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts +20 -0
  257. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts.map +1 -1
  258. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js +58 -5
  259. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js.map +1 -1
  260. package/dist/esm/security/jwt/services/jwt.backend.service.d.ts.map +1 -1
  261. package/dist/esm/security/jwt/services/jwt.backend.service.js +1 -18
  262. package/dist/esm/security/jwt/services/jwt.backend.service.js.map +1 -1
  263. package/dist/tsconfig.build.tsbuildinfo +1 -1
  264. package/package.json +15 -9
  265. package/dist/esm/.builder.pid +0 -9
@@ -0,0 +1,193 @@
1
+ /**
2
+ * `ExternalDataAdminBackendController` — the operator door onto external-data pipelines.
3
+ *
4
+ * ## Why this exists, and why it is not merely "nice to have"
5
+ *
6
+ * A pipeline could only ever be started by its own `scheduleCron`. That makes the capability
7
+ * UNDEVELOPABLE against a real remote system: authoring a mapping for SAP or Dynamics is a loop of
8
+ * change → sync → look, run dozens of times, and an hourly cron makes each turn of that loop an
9
+ * hour long. The gap was not theoretical — verifying Wonder Todos' first pipeline required either
10
+ * waiting for the hour or editing committed app config, which is what showed the door was missing.
11
+ *
12
+ * It is equally the operator surface: "the client list is wrong, sync it now" has no answer when
13
+ * the only trigger is a clock.
14
+ *
15
+ * ## Posture — deliberately identical to the data-seeding admin surface
16
+ *
17
+ * Root-mounted under `/internal/...` rather than `/api/v1/...` (operator surface, not consumer
18
+ * API), gated on `APPLICATION_ADMIN_SECRET` — the SAME key data-seeding uses, because an operator
19
+ * administering one application should hold one secret, and minting a second means two rotations to
20
+ * remember and one of them forgotten. Unset ⇒ every endpoint answers 503, so a fresh deployment
21
+ * never accidentally exposes a door that reaches a third party's production system.
22
+ *
23
+ * The secret compare hashes BOTH sides before `timingSafeEqual`, so the buffers are always 32 bytes
24
+ * and no length-mismatch timing oracle exists (the sibling's audit L-2 lesson, inherited rather
25
+ * than re-derived).
26
+ *
27
+ * There is deliberately NO in-process rate limit: like the sibling, this belongs behind a
28
+ * network-level limiter, and a run is already single-flighted per pipeline by the run service, so a
29
+ * caller hammering `/run` gets `already-running` rather than a stampede on the remote system.
30
+ */
31
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
32
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
33
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
34
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
35
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
36
+ };
37
+ var __metadata = (this && this.__metadata) || function (k, v) {
38
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
39
+ };
40
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
41
+ return function (target, key) { decorator(target, key, paramIndex); }
42
+ };
43
+ import 'reflect-metadata';
44
+ import { createHash, timingSafeEqual } from 'crypto';
45
+ import { inject, injectable } from 'inversify';
46
+ import { z } from 'zod';
47
+ import { ErrorCustomMessageReference, ErrorType } from '@wildo-ai/saas-models';
48
+ import { AbstractControllerBackendService } from '../../base-classes/services/controllers/abstract-controller.backend.service.js';
49
+ import { ErrorBuilderBackendService } from '../../errors/services/error-builder.backend.service.js';
50
+ import { ErrorHandlerService } from '../../errors/services/error-handler.backend.service.js';
51
+ import { LoggerService } from '../../monitoring/logger/services/logger.service.js';
52
+ import { SAAS_SERVICE_TYPES } from '../../inject/models/service-types.js';
53
+ import { ExecutionContextCreatorBackendService } from '../../runtime/services/execution-context-creator.backend.service.js';
54
+ import { RepositoriesRegistryHandlerBackendService } from '../../base-classes/services/repositories/repositories-registry-handler.backend.service.js';
55
+ import { BackendResourceType } from '../../resources/models/resources-types.backend.schemas.js';
56
+ import { CoreResourceOperation } from '@wildo-ai/saas-models';
57
+ import { DataSeedTriggerOrigin } from '../../data-seeding/models/data-seed.shared.schemas.js';
58
+ import { ExternalDataAdminEndpointEnvKey, ExternalDataAdminEndpointHeaderKey, ExternalDataAdminEndpointMountBasePath, ExternalDataAdminEndpointPath, } from './external-data-admin.backend.contracts.js';
59
+ /** `POST /run` body. `resourceType` names the DESTINATION resource declaring the pipeline. */
60
+ const RunPipelineRequestSchema = z.object({
61
+ resourceType: z.string().min(1),
62
+ }).strict();
63
+ let ExternalDataAdminBackendController = class ExternalDataAdminBackendController extends AbstractControllerBackendService {
64
+ pipelineRunService;
65
+ repositoriesRegistry;
66
+ executionContextCreator;
67
+ constructor(logger, errorBuilder, errorHandler, pipelineRunService, repositoriesRegistry, executionContextCreator) {
68
+ super(logger, errorBuilder, errorHandler, 'ExternalDataAdminController');
69
+ this.pipelineRunService = pipelineRunService;
70
+ this.repositoriesRegistry = repositoriesRegistry;
71
+ this.executionContextCreator = executionContextCreator;
72
+ }
73
+ /** Root-level mount, mirroring `/health` and the data-seeding admin surface. */
74
+ getCustomMountBasePath() {
75
+ return ExternalDataAdminEndpointMountBasePath;
76
+ }
77
+ setupRoutes() {
78
+ this.router.use(this.requireAdminSecret.bind(this));
79
+ this.router.get(ExternalDataAdminEndpointPath.PIPELINES, this.asyncHandler(this.handleListPipelines.bind(this)));
80
+ this.logRouteRegistration('GET', ExternalDataAdminEndpointPath.PIPELINES, 'List declared external-data pipelines with cursor + last run');
81
+ this.router.post(ExternalDataAdminEndpointPath.RUN, this.asyncHandler(this.handleRunPipeline.bind(this)));
82
+ this.logRouteRegistration('POST', ExternalDataAdminEndpointPath.RUN, 'Run one external-data pipeline now');
83
+ }
84
+ /**
85
+ * Same guard as the data-seeding admin surface, for the same reasons. Hash BOTH inputs so the
86
+ * buffers handed to `timingSafeEqual` are a fixed 32 bytes — an early length compare would leak
87
+ * the secret's length.
88
+ */
89
+ requireAdminSecret(req, _res, next) {
90
+ const expectedSecret = process.env[ExternalDataAdminEndpointEnvKey];
91
+ if (!expectedSecret || expectedSecret.length === 0) {
92
+ throw this.errorBuilder.buildError(ErrorType.INFRASTRUCTURE, undefined, {
93
+ customMessageReference: ErrorCustomMessageReference.INFRASTRUCTURE,
94
+ context: {
95
+ message: `external-data admin endpoints disabled (${ExternalDataAdminEndpointEnvKey} unset)`,
96
+ envKey: ExternalDataAdminEndpointEnvKey,
97
+ },
98
+ });
99
+ }
100
+ const providedSecret = req.headers[ExternalDataAdminEndpointHeaderKey.toLowerCase()];
101
+ if (!providedSecret) {
102
+ throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {
103
+ customMessageReference: ErrorCustomMessageReference.SEED_ADMIN_SECRET_REQUIRED,
104
+ context: { headerKey: ExternalDataAdminEndpointHeaderKey },
105
+ });
106
+ }
107
+ const providedDigest = createHash('sha256').update(providedSecret).digest();
108
+ const expectedDigest = createHash('sha256').update(expectedSecret).digest();
109
+ if (!timingSafeEqual(providedDigest, expectedDigest)) {
110
+ throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {
111
+ customMessageReference: ErrorCustomMessageReference.SEED_ADMIN_SECRET_INVALID,
112
+ context: { headerKey: ExternalDataAdminEndpointHeaderKey },
113
+ });
114
+ }
115
+ next();
116
+ }
117
+ /**
118
+ * GET /pipelines — every DECLARED pipeline, joined with its persisted state.
119
+ *
120
+ * Declaration-first rather than state-first on purpose: a pipeline that has never run has no
121
+ * state row, and it is exactly the one an operator is looking for when something is wrong.
122
+ * Listing from state would make it invisible.
123
+ */
124
+ async handleListPipelines(_req, res) {
125
+ const stateContext = await this.executionContextCreator.createForInternalRepositoryOperation({
126
+ resourceType: BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE,
127
+ operationIdentifier: CoreResourceOperation.READ,
128
+ });
129
+ const stateRepo = this.repositoriesRegistry.getRepositoryOrThrow(BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE, stateContext);
130
+ const pipelines = await Promise.all(this.pipelineRunService.listDeclaredPipelines().map(async ({ resourceType, declaration }) => {
131
+ const state = await stateRepo.read(stateContext, { pipelineResourceType: resourceType });
132
+ return {
133
+ resourceType,
134
+ providerRef: declaration.binding.providerRef,
135
+ remoteEntityRef: declaration.binding.entityRef,
136
+ dialect: declaration.binding.dialect,
137
+ population: declaration.population,
138
+ orphanPolicy: declaration.orphanPolicy,
139
+ scheduleCron: declaration.scheduleCron ?? null,
140
+ maxRowsPerRun: declaration.maxRowsPerRun,
141
+ // `cursor !== null` means a pass is IN FLIGHT — and therefore that the next run will not
142
+ // sweep orphans. Surfaced because it is the single most confusing state to debug blind.
143
+ cursor: state?.cursor ?? null,
144
+ passStartedAt: state?.passStartedAt ?? null,
145
+ lastRunAt: state?.lastRunAt ?? null,
146
+ lastRunSummary: state?.lastRunSummary ?? null,
147
+ };
148
+ }));
149
+ res.json({ pipelines });
150
+ }
151
+ /** POST /run — run one pipeline now, and answer with the run report. */
152
+ async handleRunPipeline(req, res) {
153
+ const parsed = RunPipelineRequestSchema.safeParse(req.body);
154
+ if (!parsed.success) {
155
+ throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {
156
+ customMessageReference: ErrorCustomMessageReference.VALIDATION,
157
+ context: { issues: parsed.error.issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`) },
158
+ });
159
+ }
160
+ const { resourceType } = parsed.data;
161
+ const declared = this.pipelineRunService.listDeclaredPipelines().some((entry) => entry.resourceType === resourceType);
162
+ if (!declared) {
163
+ throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {
164
+ customMessageReference: ErrorCustomMessageReference.VALIDATION,
165
+ context: {
166
+ message: `Resource '${resourceType}' declares no externalDataPipeline. GET ${ExternalDataAdminEndpointMountBasePath}${ExternalDataAdminEndpointPath.PIPELINES} lists the ones that do.`,
167
+ },
168
+ });
169
+ }
170
+ // ADMIN_MANUAL, not SCHEDULED_RERUN: the run ledger should record that a human asked.
171
+ const report = await this.pipelineRunService.runPipeline({
172
+ resourceType,
173
+ triggerOrigin: DataSeedTriggerOrigin.ADMIN_MANUAL,
174
+ triggeredByUserId: null,
175
+ });
176
+ res.json(report);
177
+ }
178
+ };
179
+ ExternalDataAdminBackendController = __decorate([
180
+ injectable(),
181
+ __param(0, inject(SAAS_SERVICE_TYPES.Logger)),
182
+ __param(1, inject(SAAS_SERVICE_TYPES.ErrorBuilderService)),
183
+ __param(2, inject(SAAS_SERVICE_TYPES.ErrorHandlerService)),
184
+ __param(3, inject(SAAS_SERVICE_TYPES.ExternalDataPipelineRunService)),
185
+ __param(4, inject(SAAS_SERVICE_TYPES.RepositoriesRegistryHandlerService)),
186
+ __param(5, inject(SAAS_SERVICE_TYPES.ExecutionContextCreatorBackendService)),
187
+ __metadata("design:paramtypes", [LoggerService,
188
+ ErrorBuilderBackendService,
189
+ ErrorHandlerService, Function, RepositoriesRegistryHandlerBackendService,
190
+ ExecutionContextCreatorBackendService])
191
+ ], ExternalDataAdminBackendController);
192
+ export { ExternalDataAdminBackendController };
193
+ //# sourceMappingURL=external-data-admin.backend.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-data-admin.backend.controller.js","sourceRoot":"","sources":["../../../../../src/external-data/controllers/external-data-admin.backend.controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ExternalDataAdminBackendController` — the operator door onto external-data pipelines.\n *\n * ## Why this exists, and why it is not merely \"nice to have\"\n *\n * A pipeline could only ever be started by its own `scheduleCron`. That makes the capability\n * UNDEVELOPABLE against a real remote system: authoring a mapping for SAP or Dynamics is a loop of\n * change → sync → look, run dozens of times, and an hourly cron makes each turn of that loop an\n * hour long. The gap was not theoretical — verifying Wonder Todos' first pipeline required either\n * waiting for the hour or editing committed app config, which is what showed the door was missing.\n *\n * It is equally the operator surface: \"the client list is wrong, sync it now\" has no answer when\n * the only trigger is a clock.\n *\n * ## Posture — deliberately identical to the data-seeding admin surface\n *\n * Root-mounted under `/internal/...` rather than `/api/v1/...` (operator surface, not consumer\n * API), gated on `APPLICATION_ADMIN_SECRET` — the SAME key data-seeding uses, because an operator\n * administering one application should hold one secret, and minting a second means two rotations to\n * remember and one of them forgotten. Unset ⇒ every endpoint answers 503, so a fresh deployment\n * never accidentally exposes a door that reaches a third party's production system.\n *\n * The secret compare hashes BOTH sides before `timingSafeEqual`, so the buffers are always 32 bytes\n * and no length-mismatch timing oracle exists (the sibling's audit L-2 lesson, inherited rather\n * than re-derived).\n *\n * There is deliberately NO in-process rate limit: like the sibling, this belongs behind a\n * network-level limiter, and a run is already single-flighted per pipeline by the run service, so a\n * caller hammering `/run` gets `already-running` rather than a stampede on the remote system.\n */\n\nimport 'reflect-metadata';\nimport { createHash, timingSafeEqual } from 'crypto';\nimport { inject, injectable } from 'inversify';\nimport type { NextFunction, Request, Response } from 'express';\nimport { z } from 'zod';\nimport { ErrorCustomMessageReference, ErrorType, type ResourceType } from '@wildo-ai/saas-models';\n\nimport { AbstractControllerBackendService } from '~wildo-backend-lib/base-classes/services/controllers/abstract-controller.backend.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { ErrorHandlerService } from '~wildo-backend-lib/errors/services/error-handler.backend.service';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { ExecutionContextCreatorBackendService } from '~wildo-backend-lib/runtime/services/execution-context-creator.backend.service';\nimport { RepositoriesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/repositories/repositories-registry-handler.backend.service';\nimport { BackendResourceType } from '~wildo-backend-lib/resources/models/resources-types.backend.schemas';\nimport { CoreResourceOperation } from '@wildo-ai/saas-models';\nimport { DataSeedTriggerOrigin } from '~wildo-backend-lib/data-seeding/models/data-seed.shared.schemas';\nimport type { ExternalDataPipelineState } from '~wildo-backend-lib/external-data/models/external-data-pipeline-state.backend.schemas';\nimport type { ExternalDataPipelineRunBackendService } from '~wildo-backend-lib/external-data/external-data-pipeline-run.backend.service';\n\nimport {\n ExternalDataAdminEndpointEnvKey,\n ExternalDataAdminEndpointHeaderKey,\n ExternalDataAdminEndpointMountBasePath,\n ExternalDataAdminEndpointPath,\n} from './external-data-admin.backend.contracts';\n\n/** `POST /run` body. `resourceType` names the DESTINATION resource declaring the pipeline. */\nconst RunPipelineRequestSchema = z.object({\n resourceType: z.string().min(1),\n}).strict();\n\n@injectable()\nexport class ExternalDataAdminBackendController extends AbstractControllerBackendService {\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.ErrorHandlerService) errorHandler: ErrorHandlerService,\n @inject(SAAS_SERVICE_TYPES.ExternalDataPipelineRunService)\n private readonly pipelineRunService: ExternalDataPipelineRunBackendService,\n @inject(SAAS_SERVICE_TYPES.RepositoriesRegistryHandlerService)\n private readonly repositoriesRegistry: RepositoriesRegistryHandlerBackendService,\n @inject(SAAS_SERVICE_TYPES.ExecutionContextCreatorBackendService)\n private readonly executionContextCreator: ExecutionContextCreatorBackendService,\n ) {\n super(logger, errorBuilder, errorHandler, 'ExternalDataAdminController');\n }\n\n /** Root-level mount, mirroring `/health` and the data-seeding admin surface. */\n public getCustomMountBasePath(): string {\n return ExternalDataAdminEndpointMountBasePath;\n }\n\n protected setupRoutes(): void {\n this.router.use(this.requireAdminSecret.bind(this));\n\n this.router.get(\n ExternalDataAdminEndpointPath.PIPELINES,\n this.asyncHandler(this.handleListPipelines.bind(this)),\n );\n this.logRouteRegistration('GET', ExternalDataAdminEndpointPath.PIPELINES, 'List declared external-data pipelines with cursor + last run');\n\n this.router.post(\n ExternalDataAdminEndpointPath.RUN,\n this.asyncHandler(this.handleRunPipeline.bind(this)),\n );\n this.logRouteRegistration('POST', ExternalDataAdminEndpointPath.RUN, 'Run one external-data pipeline now');\n }\n\n /**\n * Same guard as the data-seeding admin surface, for the same reasons. Hash BOTH inputs so the\n * buffers handed to `timingSafeEqual` are a fixed 32 bytes — an early length compare would leak\n * the secret's length.\n */\n private requireAdminSecret(req: Request, _res: Response, next: NextFunction): void {\n const expectedSecret = process.env[ExternalDataAdminEndpointEnvKey];\n if (!expectedSecret || expectedSecret.length === 0) {\n throw this.errorBuilder.buildError(ErrorType.INFRASTRUCTURE, undefined, {\n customMessageReference: ErrorCustomMessageReference.INFRASTRUCTURE,\n context: {\n message: `external-data admin endpoints disabled (${ExternalDataAdminEndpointEnvKey} unset)`,\n envKey: ExternalDataAdminEndpointEnvKey,\n },\n });\n }\n\n const providedSecret = req.headers[ExternalDataAdminEndpointHeaderKey.toLowerCase()] as string | undefined;\n if (!providedSecret) {\n throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.SEED_ADMIN_SECRET_REQUIRED,\n context: { headerKey: ExternalDataAdminEndpointHeaderKey },\n });\n }\n\n const providedDigest = createHash('sha256').update(providedSecret).digest();\n const expectedDigest = createHash('sha256').update(expectedSecret).digest();\n if (!timingSafeEqual(providedDigest, expectedDigest)) {\n throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.SEED_ADMIN_SECRET_INVALID,\n context: { headerKey: ExternalDataAdminEndpointHeaderKey },\n });\n }\n\n next();\n }\n\n /**\n * GET /pipelines — every DECLARED pipeline, joined with its persisted state.\n *\n * Declaration-first rather than state-first on purpose: a pipeline that has never run has no\n * state row, and it is exactly the one an operator is looking for when something is wrong.\n * Listing from state would make it invisible.\n */\n private async handleListPipelines(_req: Request, res: Response): Promise<void> {\n const stateContext = await this.executionContextCreator.createForInternalRepositoryOperation({\n resourceType: BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE,\n operationIdentifier: CoreResourceOperation.READ,\n } as never);\n const stateRepo = this.repositoriesRegistry.getRepositoryOrThrow(\n BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE as unknown as ResourceType,\n stateContext,\n );\n\n const pipelines = await Promise.all(\n this.pipelineRunService.listDeclaredPipelines().map(async ({ resourceType, declaration }) => {\n const state = await stateRepo.read<ExternalDataPipelineState>(stateContext, { pipelineResourceType: resourceType });\n return {\n resourceType,\n providerRef: declaration.binding.providerRef,\n remoteEntityRef: declaration.binding.entityRef,\n dialect: declaration.binding.dialect,\n population: declaration.population,\n orphanPolicy: declaration.orphanPolicy,\n scheduleCron: declaration.scheduleCron ?? null,\n maxRowsPerRun: declaration.maxRowsPerRun,\n // `cursor !== null` means a pass is IN FLIGHT — and therefore that the next run will not\n // sweep orphans. Surfaced because it is the single most confusing state to debug blind.\n cursor: state?.cursor ?? null,\n passStartedAt: state?.passStartedAt ?? null,\n lastRunAt: state?.lastRunAt ?? null,\n lastRunSummary: state?.lastRunSummary ?? null,\n };\n }),\n );\n\n res.json({ pipelines });\n }\n\n /** POST /run — run one pipeline now, and answer with the run report. */\n private async handleRunPipeline(req: Request, res: Response): Promise<void> {\n const parsed = RunPipelineRequestSchema.safeParse(req.body);\n if (!parsed.success) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: { issues: parsed.error.issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`) },\n });\n }\n const { resourceType } = parsed.data;\n\n const declared = this.pipelineRunService.listDeclaredPipelines().some((entry) => entry.resourceType === resourceType);\n if (!declared) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: {\n message: `Resource '${resourceType}' declares no externalDataPipeline. GET ${ExternalDataAdminEndpointMountBasePath}${ExternalDataAdminEndpointPath.PIPELINES} lists the ones that do.`,\n },\n });\n }\n\n // ADMIN_MANUAL, not SCHEDULED_RERUN: the run ledger should record that a human asked.\n const report = await this.pipelineRunService.runPipeline({\n resourceType,\n triggerOrigin: DataSeedTriggerOrigin.ADMIN_MANUAL,\n triggeredByUserId: null,\n });\n\n res.json(report);\n }\n}\n"]}
@@ -45,6 +45,7 @@
45
45
  */
46
46
  import { type ExternalDataPipelineDeclaration } from '@wildo-ai/saas-models/external-data';
47
47
  import { DataSeedTriggerOrigin } from '../data-seeding/models/data-seed.shared.schemas';
48
+ import { DataSeedRunStatus } from '../data-seeding/models/data-seed-run.shared.schemas';
48
49
  import type { SyncSeedEngineBackendService } from '../data-seeding/services/engines/sync-seed-engine.backend.service';
49
50
  import type { UpgradeSeedEngineBackendService } from '../data-seeding/services/engines/upgrade-seed-engine.backend.service';
50
51
  import type { ExecutionContext } from '../runtime/models/execution-context.backend.schemas';
@@ -66,8 +67,18 @@ export interface ExternalDataPipelineRunReport {
66
67
  outcome: 'completed-pass' | 'partial-pass' | 'already-running';
67
68
  extractedRows: number;
68
69
  rejectedRows: number;
69
- /** Declared entries handed to the seed engine (extracted − rejected). */
70
- loadedEntries: number;
70
+ /**
71
+ * Declared entries HANDED TO the seed engine (extracted − rejected) — what was submitted, never
72
+ * what was written. Named `submittedEntries` rather than `loadedEntries` because the old name
73
+ * read as a write count and was believed as one: a run reporting `loadedEntries: 2` had in fact
74
+ * written one row and failed the other. `loadStatus` is the field that answers what landed.
75
+ */
76
+ submittedEntries: number;
77
+ /**
78
+ * The seed engine's verdict on the LOAD — `PARTIAL`/`FAILED` means go read `seedRunId`. Distinct
79
+ * from `outcome`, which reports only whether the EXTRACTION pass completed.
80
+ */
81
+ loadStatus: DataSeedRunStatus | null;
71
82
  /** `_id` of the `DataSeedRun` ledger row (per-entry outcome detail); null when none ran. */
72
83
  seedRunId: string | null;
73
84
  /** The persisted continuation — non-null exactly for `partial-pass`. */
@@ -1 +1 @@
1
- {"version":3,"file":"external-data-pipeline-run.backend.service.d.ts","sourceRoot":"","sources":["../../../../src/external-data/external-data-pipeline-run.backend.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"external-data-pipeline-run.backend.service.d.ts","sourceRoot":"","sources":["../../../../src/external-data/external-data-pipeline-run.backend.service.ts"],"names":[],"mappings":"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"}
@@ -106,7 +106,7 @@ let ExternalDataPipelineRunBackendService = class ExternalDataPipelineRunBackend
106
106
  if (this.inFlight.has(input.resourceType)) {
107
107
  return {
108
108
  outcome: 'already-running', extractedRows: 0, rejectedRows: 0,
109
- loadedEntries: 0, seedRunId: null, cursor: null, durationMs: 0,
109
+ submittedEntries: 0, loadStatus: null, seedRunId: null, cursor: null, durationMs: 0,
110
110
  };
111
111
  }
112
112
  this.inFlight.add(input.resourceType);
@@ -204,6 +204,10 @@ let ExternalDataPipelineRunBackendService = class ExternalDataPipelineRunBackend
204
204
  // ── Persist the state row: the cursor IS the pass continuation ──────────────
205
205
  const durationMs = Date.now() - startedAtMs;
206
206
  const seedRunId = seedReport.runId ?? null;
207
+ // The LOAD verdict, carried separately from `outcome` (which is about the EXTRACTION pass).
208
+ // Both are reported: a full pass that failed to write part of what it read is neither a clean
209
+ // success nor a partial pass, and collapsing them would hide exactly that case.
210
+ const loadStatus = seedReport.status ?? null;
207
211
  await stateRepo.update(stateContext, state._id, {
208
212
  cursor: passComplete ? null : (cursor ?? null),
209
213
  passStartedAt: passComplete ? null : passStartedAt,
@@ -212,6 +216,7 @@ let ExternalDataPipelineRunBackendService = class ExternalDataPipelineRunBackend
212
216
  completedPass: passComplete,
213
217
  extractedRows: extractedRows.length,
214
218
  rejectedRows: rejections.length,
219
+ loadStatus,
215
220
  seedRunId,
216
221
  durationMs,
217
222
  },
@@ -225,7 +230,8 @@ let ExternalDataPipelineRunBackendService = class ExternalDataPipelineRunBackend
225
230
  outcome: passComplete ? 'completed-pass' : 'partial-pass',
226
231
  extractedRows: extractedRows.length,
227
232
  rejectedRows: rejections.length,
228
- loadedEntries: entries.length,
233
+ submittedEntries: entries.length,
234
+ loadStatus,
229
235
  seedRunId,
230
236
  cursor: passComplete ? null : (cursor ?? null),
231
237
  durationMs,
@@ -1 +1 @@
1
- {"version":3,"file":"external-data-pipeline-run.backend.service.js","sourceRoot":"","sources":["../../../../src/external-data/external-data-pipeline-run.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ETL pipeline RUN service (partial-resource paper v3.1, PR-5/PR-6/PR-9/PR-10/PR-12) — the\n * orchestrator that turns a declared `externalDataPipeline` into destination rows: E (extraction source over\n * the shared read client) → T (pure transform) → L (the data-seeding engines' imperative doors).\n *\n * ## The one decision that carries the paper's ⚠ traps: MODE SELECTION (PR-6)\n *\n * A run that COMPLETES a pass over the remote population loads through `runSyncSeed` — the full\n * reconcile INCLUDING the Stage-4 orphan walk (a destination row whose remote sibling vanished is\n * handled per the declared `orphanPolicy`). A run that stops mid-pass (`maxRowsPerRun` reached)\n * loads through `runUpgradeSeed` — the SAME reconcile loop with NO orphan stage, because \"absent\n * from a partial extraction\" is not evidence of remote deletion. A partial run orphans NOTHING;\n * the persisted cursor lets the next run continue the pass where this one stopped.\n *\n * ## Change detection (PR-30)\n *\n * The assembled seed pins `version: 1` forever and declares\n * `reconcileStrategy: CONTENT` — a pipeline's change signal is the REMOTE VALUE, not a version\n * bump. Managed-field content comparison decides write-vs-skip per row, so an unchanged remote\n * population costs zero destination writes (and zero RAG re-ingestions) per pass.\n *\n * ## Ledger split (PR-9)\n *\n * Per-entry load outcome lives on the `DataSeedRun` ledger row every run writes through the\n * engines (one row per pipeline — `seedKey` + constant version, recovered and reset each run).\n * The `ExternalDataPipelineState` row carries what data-seeding cannot know: the extraction CURSOR\n * (surviving restarts, so populations larger than one run's budget eventually sync end to end)\n * and the ETL-side last-run summary (extracted/rejected happen BEFORE the seed engine sees\n * anything). Transform rejections are logged per row (bounded) and counted on the summary; the\n * rejected rows' previous destination versions simply survive.\n *\n * ## Attribution (PR-10)\n *\n * ONE read-through audit row per run through the same lazily-resolved seam as the virtual\n * repository (`logHttpApiReadThroughAccess`), carrying the run outcome counts — a pipeline run\n * is bulk read-through access to an external system and lands in the same audit lane.\n *\n * ## Concurrency\n *\n * Single-flight per pipeline is IN-PROCESS (a second `runPipeline` for the same resource while\n * one is in flight answers `already-running`). A cross-instance double run is deliberately not\n * locked out: every load is an idempotent business-key MERGE and extraction pages are\n * drift-safe, so the second run wastes work but corrupts nothing — the same posture as\n * concurrent seed passes.\n */\n\nimport { inject, injectable, optional, unmanaged } from 'inversify';\nimport {\n CoreResourceOperation,\n ErrorCustomMessageReference,\n ErrorType,\n type ResourceType,\n} from '@wildo-ai/saas-models';\nimport {\n ExternalDataDestinationPopulation,\n ExternalDataOrphanPolicy,\n type ExternalDataPipelineDeclaration,\n} from '@wildo-ai/saas-models/external-data';\n\nimport {\n DataSeedMode,\n DataSeedReconcileStrategy,\n DataSeedScopeKind,\n DataSeedSyncOrphanPolicy,\n DataSeedTriggerKind,\n DataSeedTriggerOrigin,\n type DataSeedDefinition,\n type DataSeedStaticEntry,\n} from '~wildo-backend-lib/data-seeding/models/data-seed.shared.schemas';\nimport type { DataSeedScopeAnchor } from '~wildo-backend-lib/data-seeding/services/shared/data-seed-scope-anchor.shared.types';\nimport type { SyncSeedEngineBackendService } from '~wildo-backend-lib/data-seeding/services/engines/sync-seed-engine.backend.service';\nimport type { UpgradeSeedEngineBackendService } from '~wildo-backend-lib/data-seeding/services/engines/upgrade-seed-engine.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport type { ExecutionContextCreatorBackendService } from '~wildo-backend-lib/runtime/services/execution-context-creator.backend.service';\nimport type { RepositoriesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/repositories/repositories-registry-handler.backend.service';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport type { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport type { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { BackendResourceType } from '~wildo-backend-lib/resources/models/resources-types.backend.schemas';\nimport type { ExternalDataPipelineState } from '~wildo-backend-lib/external-data/models/external-data-pipeline-state.backend.schemas';\nimport { HttpApiReadClient } from '~wildo-backend-lib/base-classes/services/repositories/http-api/http-api-read-client.backend';\nimport { ExternalProvidersHttpApiAccessResolver } from '~wildo-backend-lib/base-classes/services/repositories/http-api/http-api-provider-access.backend';\nimport type { HttpApiAuditLogsResolver } from '~wildo-backend-lib/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service';\nimport { HttpApiExternalDataExtractionSource, type ExternalDataExtractionSource } from './external-data-extraction-source.backend';\n/*\n * entrypoint-closure-waiver: `etl-transform.backend` is published only by `./external-data-models` (one\n * door per room), and nothing nominal crosses into this root-exported module's public surface —\n * `ExternalDataTransformedRow`/`ExternalDataRowRejection` are structural interfaces and `ExternalDataPipelineRunReport`\n * names none of them nominally. Probed 2026-08-28 after the barrel split:\n * `tsc -p tsconfig.build.json --noEmit` with declaration emit — zero TS2742/TS2883/TS7056.\n */\nimport { transformExternalDataPage, type ExternalDataRowRejection } from './external-data-transform.backend';\n\n/** How many per-row rejection reasons are LOGGED per run — the count is always exact. */\nconst MAX_LOGGED_REJECTIONS = 25;\n\nexport interface ExternalDataPipelineRunInput {\n /** The DESTINATION resource type declaring the `externalDataPipeline`. */\n resourceType: string;\n /** `ADMIN_MANUAL` for the on-demand door, `SCHEDULED_RERUN` for the cron batch (PR-12). */\n triggerOrigin: DataSeedTriggerOrigin.ADMIN_MANUAL | DataSeedTriggerOrigin.SCHEDULED_RERUN;\n triggeredByUserId: string | null;\n}\n\nexport interface ExternalDataPipelineRunReport {\n outcome: 'completed-pass' | 'partial-pass' | 'already-running';\n extractedRows: number;\n rejectedRows: number;\n /** Declared entries handed to the seed engine (extracted − rejected). */\n loadedEntries: number;\n /** `_id` of the `DataSeedRun` ledger row (per-entry outcome detail); null when none ran. */\n seedRunId: string | null;\n /** The persisted continuation — non-null exactly for `partial-pass`. */\n cursor: string | null;\n durationMs: number;\n}\n\n/** Test seam: build the extraction source for one run (production default: the HTTP source). */\nexport type ExternalDataExtractionSourceFactory = (\n declaration: ExternalDataPipelineDeclaration,\n internalContext: ExecutionContext<any>,\n) => ExternalDataExtractionSource;\n\n@injectable()\nexport class ExternalDataPipelineRunBackendService {\n private readonly inFlight = new Set<string>();\n private readonly logDebug: (message: string, data?: unknown) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.RepositoriesRegistryHandlerService) private readonly repositoriesRegistry: RepositoriesRegistryHandlerBackendService,\n @inject(SAAS_SERVICE_TYPES.ResourceRegistryService) private readonly resourcesRegistry: ResourcesRegistryBackendService,\n @inject(SAAS_SERVICE_TYPES.SyncSeedEngine) private readonly syncEngine: SyncSeedEngineBackendService,\n @inject(SAAS_SERVICE_TYPES.UpgradeSeedEngine) private readonly upgradeEngine: UpgradeSeedEngineBackendService,\n @inject(SAAS_SERVICE_TYPES.ExecutionContextCreatorBackendService) private readonly executionContextCreator: ExecutionContextCreatorBackendService,\n /**\n * Resolves provider credentials for the extraction client. `@optional()` so a container\n * without external-connector bindings still constructs the service — a run then fails with\n * the resolver's own actionable error, never at boot.\n */\n @inject(SAAS_SERVICE_TYPES.ExternalProvidersRegistryService) @optional() private readonly externalProvidersRegistry?: unknown,\n /**\n * The read-through audit seam (PR-10) — same structural, lazily-resolved contract as the\n * virtual repository's: absent binding means no emission, and emission failure never fails\n * the run it observes.\n */\n /*\n * `@unmanaged()`, because this class IS container-constructed and the two seams below are not the\n * container's to resolve. The contract was copied from `HttpApiResourceRepositoryBackendService`,\n * where an undecorated optional parameter is fine — that class carries no `@injectable()` and is\n * built with `new`. Under `@injectable()` the same shape is a boot failure: Inversify validates\n * EVERY constructor parameter, so an undecorated one aborts container setup with\n * \"unexpected missing metadata\" long before any ETL code runs.\n */\n @unmanaged() private readonly auditLogsResolver?: HttpApiAuditLogsResolver,\n /** Test seam — production leaves it unset and gets the HTTP source over the shared client. */\n @unmanaged() private readonly extractionSourceFactory?: ExternalDataExtractionSourceFactory,\n ) {\n this.logDebug = this.logger.debugByType('ExternalDataPipelineRun');\n this.logger.setDebugTypeLogging('ExternalDataPipelineRun', false);\n }\n\n /** The on-demand door (PR-12). The scheduled batch calls this per due pipeline. */\n public async runPipeline(input: ExternalDataPipelineRunInput): Promise<ExternalDataPipelineRunReport> {\n if (this.inFlight.has(input.resourceType)) {\n return {\n outcome: 'already-running', extractedRows: 0, rejectedRows: 0,\n loadedEntries: 0, seedRunId: null, cursor: null, durationMs: 0,\n };\n }\n this.inFlight.add(input.resourceType);\n try {\n return await this.executeRun(input);\n } finally {\n this.inFlight.delete(input.resourceType);\n }\n }\n\n /** Every registered resource that declares an `externalDataPipeline` — the scheduled batch's population. */\n public listDeclaredPipelines(): Array<{ resourceType: string; declaration: ExternalDataPipelineDeclaration }> {\n return (this.resourcesRegistry.getResourceTypes() as string[])\n .flatMap((resourceType) => {\n const configuration = this.resourcesRegistry.getResourceConfiguration(resourceType as ResourceType) as\n | { externalDataPipeline?: ExternalDataPipelineDeclaration }\n | undefined;\n return configuration?.externalDataPipeline ? [{ resourceType, declaration: configuration.externalDataPipeline }] : [];\n });\n }\n\n private async executeRun(input: ExternalDataPipelineRunInput): Promise<ExternalDataPipelineRunReport> {\n const startedAtMs = Date.now();\n const { resourceType, triggerOrigin, triggeredByUserId } = input;\n\n const configuration = this.resourcesRegistry.getResourceConfiguration(resourceType as ResourceType) as {\n externalDataPipeline?: ExternalDataPipelineDeclaration;\n };\n const declaration = configuration?.externalDataPipeline;\n if (!declaration) {\n throw this.errorBuilder.buildError(ErrorType.CONFIGURATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.RESOURCES_REGISTRY_INITIALIZATION_FAILED,\n context: { reason: 'etl_pipeline_without_declaration', resourceType },\n });\n }\n\n const internalContext = await this.executionContextCreator.createForInternalSystemOperation();\n const stateContext = await this.executionContextCreator.createForInternalRepositoryOperation({\n resourceType: BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE,\n operationIdentifier: CoreResourceOperation.CREATE,\n });\n const stateRepo = this.repositoriesRegistry.getRepositoryOrThrow(\n BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE as unknown as ResourceType,\n stateContext,\n );\n\n let state = await stateRepo.read<ExternalDataPipelineState>(stateContext, { pipelineResourceType: resourceType });\n if (!state) {\n state = (await stateRepo.create(stateContext, {\n pipelineResourceType: resourceType,\n cursor: null,\n passStartedAt: null,\n lastRunAt: null,\n lastRunSummary: null,\n } as never)) as unknown as ExternalDataPipelineState;\n }\n const resumingCursor = state.cursor ?? undefined;\n const passStartedAt = resumingCursor === undefined ? new Date() : (state.passStartedAt ?? new Date());\n\n // ── E: bounded page walk from the persisted cursor ──────────────────────────\n const source = (this.extractionSourceFactory ?? this.buildHttpExtractionSource.bind(this))(declaration, internalContext);\n // PR-21 — WARN-only drift probe, once per run, before extraction. A probe failure is\n // swallowed: drift detection must never be the reason a run fails.\n try {\n for (const warning of (await source.describeContractDrift?.()) ?? []) {\n this.logger.warn(`ExternalDataPipelineRun[${resourceType}]: schema drift — ${warning}`);\n }\n } catch {\n // Diagnostic only.\n }\n const extractedRows: Array<Record<string, unknown>> = [];\n let cursor = resumingCursor;\n let passComplete = false;\n while (extractedRows.length < declaration.maxRowsPerRun) {\n const page = await source.extractPage(cursor, Math.min(declaration.maxRowsPerRun - extractedRows.length, 1_000));\n extractedRows.push(...page.rows);\n cursor = page.nextCursor;\n if (page.complete) {\n passComplete = true;\n break;\n }\n }\n\n // ── T: pure transform; per-row rejections reported, never fatal ─────────────\n const { rows, rejections } = transformExternalDataPage(declaration, extractedRows);\n this.logRejections(resourceType, rejections);\n const entries: DataSeedStaticEntry[] = rows.map((row) => ({\n businessKey: { [declaration.remoteKeyLocalField]: row.businessKeyValue },\n data: row.data,\n }));\n\n // ── L: mode selection is THE PR-6 guard — a partial run orphans NOTHING ─────\n const seed = this.assembleSeedDefinition(resourceType, declaration, passComplete);\n const scope = await this.resolveApplicationScopeAnchor(internalContext);\n const engineInput = {\n seed,\n trigger: { triggerKind: DataSeedTriggerKind.ON_SCHEDULED_REFRESH as const },\n triggerOrigin,\n scope,\n entries,\n triggeredByUserId,\n rowMetadataSource: `external-data:${resourceType}`,\n internalContext,\n };\n const seedReport = passComplete\n ? await this.syncEngine.runSyncSeed(engineInput)\n : await this.upgradeEngine.runUpgradeSeed(engineInput);\n\n // ── Persist the state row: the cursor IS the pass continuation ──────────────\n const durationMs = Date.now() - startedAtMs;\n const seedRunId = seedReport.runId ?? null;\n await stateRepo.update(stateContext, state._id, {\n cursor: passComplete ? null : (cursor ?? null),\n passStartedAt: passComplete ? null : passStartedAt,\n lastRunAt: new Date(startedAtMs),\n lastRunSummary: {\n completedPass: passComplete,\n extractedRows: extractedRows.length,\n rejectedRows: rejections.length,\n seedRunId,\n durationMs,\n },\n } as never);\n\n this.recordRunReadThroughAccess(internalContext, resourceType, declaration, {\n extractedRows: extractedRows.length,\n rejectedRows: rejections.length,\n completedPass: passComplete,\n });\n\n return {\n outcome: passComplete ? 'completed-pass' : 'partial-pass',\n extractedRows: extractedRows.length,\n rejectedRows: rejections.length,\n loadedEntries: entries.length,\n seedRunId,\n cursor: passComplete ? null : (cursor ?? null),\n durationMs,\n };\n }\n\n /**\n * The assembled seed is EPHEMERAL (never registered): `version` is pinned at 1 forever and\n * `reconcileStrategy: CONTENT` carries change detection (PR-30) — bumping the version per run\n * would route every row through the migrator path and rewrite the whole corpus each pass.\n */\n private assembleSeedDefinition(\n resourceType: string,\n declaration: ExternalDataPipelineDeclaration,\n passComplete: boolean,\n ): DataSeedDefinition {\n const managedFields = [\n declaration.remoteKeyLocalField,\n ...declaration.mapping.map((entry) => entry.localField),\n ];\n return {\n seedKey: `external-data:${resourceType}`,\n scope: { kind: DataSeedScopeKind.APPLICATION },\n resourceType,\n triggers: [{ triggerKind: DataSeedTriggerKind.ON_SCHEDULED_REFRESH }],\n mode: passComplete ? DataSeedMode.SYNC : DataSeedMode.UPGRADE,\n version: 1,\n managedFields,\n reconcileStrategy: DataSeedReconcileStrategy.CONTENT,\n ...(passComplete ? { onOrphan: this.mapOrphanPolicy(this.effectiveOrphanPolicy(declaration)) } : {}),\n } as DataSeedDefinition;\n }\n\n /**\n * The seam between the two closed vocabularies (never merged by value overlap — the ETL\n * declaration must not depend on data-seeding's enum, and each member's semantics are owned\n * where it is declared).\n */\n private mapOrphanPolicy(orphanPolicy: ExternalDataOrphanPolicy): DataSeedSyncOrphanPolicy {\n switch (orphanPolicy) {\n case ExternalDataOrphanPolicy.DELETE:\n return DataSeedSyncOrphanPolicy.DELETE;\n case ExternalDataOrphanPolicy.DEPRECATE:\n return DataSeedSyncOrphanPolicy.DEPRECATE;\n case ExternalDataOrphanPolicy.REPORT:\n return DataSeedSyncOrphanPolicy.REPORT;\n }\n }\n\n /**\n * ⚠ OPEN-population caveat, named where it is decided: the SYNC orphan walk considers every\n * row of the destination, including caller-minted rows on an OPEN population. Those rows have\n * no `__seed` metadata and a business key outside the declared set, so DELETE/DEPRECATE would\n * hit them. Slice 1 therefore downgrades OPEN populations' orphan handling to REPORT.\n */\n private effectiveOrphanPolicy(declaration: ExternalDataPipelineDeclaration): ExternalDataOrphanPolicy {\n return declaration.population === ExternalDataDestinationPopulation.OPEN ? ExternalDataOrphanPolicy.REPORT : declaration.orphanPolicy;\n }\n\n private buildHttpExtractionSource(\n declaration: ExternalDataPipelineDeclaration,\n internalContext: ExecutionContext<any>,\n ): ExternalDataExtractionSource {\n const readClient = new HttpApiReadClient(\n declaration.binding,\n new ExternalProvidersHttpApiAccessResolver(this.externalProvidersRegistry as never),\n );\n return new HttpApiExternalDataExtractionSource(declaration, readClient, internalContext);\n }\n\n private async resolveApplicationScopeAnchor(internalContext: ExecutionContext<any>): Promise<DataSeedScopeAnchor> {\n const appRepo = this.repositoriesRegistry.getRepositoryOrThrow('applications' as ResourceType, internalContext);\n const application = (await appRepo.read<{ _id: string }>(internalContext, {})) ?? null;\n if (!application) {\n throw this.errorBuilder.buildError(ErrorType.INTERNAL, internalContext, {\n customMessageReference: ErrorCustomMessageReference.INTERNAL,\n context: { message: 'ExternalDataPipelineRun: no APPLICATION row exists — bootstrap order violation.' },\n });\n }\n return { scopeKind: DataSeedScopeKind.APPLICATION, relatedId: application._id, relatedResourceType: null };\n }\n\n private logRejections(resourceType: string, rejections: ExternalDataRowRejection[]): void {\n if (rejections.length === 0) return;\n this.logger.warn(`ExternalDataPipelineRun[${resourceType}]: ${rejections.length} row(s) rejected by transform (previous versions survive).`, {\n rejections: rejections.slice(0, MAX_LOGGED_REJECTIONS),\n truncated: rejections.length > MAX_LOGGED_REJECTIONS,\n });\n }\n\n /** PR-10 — one audit row per run, fire-and-forget through the shared seam. */\n private recordRunReadThroughAccess(\n executionContext: ExecutionContext<any>,\n resourceType: string,\n declaration: ExternalDataPipelineDeclaration,\n counts: { extractedRows: number; rejectedRows: number; completedPass: boolean },\n ): void {\n try {\n const auditLogsService = this.auditLogsResolver?.();\n if (typeof auditLogsService?.logHttpApiReadThroughAccess !== 'function') return;\n void auditLogsService.logHttpApiReadThroughAccess({\n resourceType,\n operationIdentifier: 'externalDataPipelineRun',\n providerRef: declaration.binding.providerRef,\n remoteEntityRef: declaration.binding.entityRef,\n dialect: declaration.binding.dialect,\n rowCount: counts.extractedRows,\n rejectedRows: counts.rejectedRows,\n completedPass: counts.completedPass,\n }, executionContext)?.catch?.(() => undefined);\n } catch {\n // Observation only — never the reason a run fails.\n }\n }\n}\n"]}
1
+ {"version":3,"file":"external-data-pipeline-run.backend.service.js","sourceRoot":"","sources":["../../../../src/external-data/external-data-pipeline-run.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ETL pipeline RUN service (partial-resource paper v3.1, PR-5/PR-6/PR-9/PR-10/PR-12) — the\n * orchestrator that turns a declared `externalDataPipeline` into destination rows: E (extraction source over\n * the shared read client) → T (pure transform) → L (the data-seeding engines' imperative doors).\n *\n * ## The one decision that carries the paper's ⚠ traps: MODE SELECTION (PR-6)\n *\n * A run that COMPLETES a pass over the remote population loads through `runSyncSeed` — the full\n * reconcile INCLUDING the Stage-4 orphan walk (a destination row whose remote sibling vanished is\n * handled per the declared `orphanPolicy`). A run that stops mid-pass (`maxRowsPerRun` reached)\n * loads through `runUpgradeSeed` — the SAME reconcile loop with NO orphan stage, because \"absent\n * from a partial extraction\" is not evidence of remote deletion. A partial run orphans NOTHING;\n * the persisted cursor lets the next run continue the pass where this one stopped.\n *\n * ## Change detection (PR-30)\n *\n * The assembled seed pins `version: 1` forever and declares\n * `reconcileStrategy: CONTENT` — a pipeline's change signal is the REMOTE VALUE, not a version\n * bump. Managed-field content comparison decides write-vs-skip per row, so an unchanged remote\n * population costs zero destination writes (and zero RAG re-ingestions) per pass.\n *\n * ## Ledger split (PR-9)\n *\n * Per-entry load outcome lives on the `DataSeedRun` ledger row every run writes through the\n * engines (one row per pipeline — `seedKey` + constant version, recovered and reset each run).\n * The `ExternalDataPipelineState` row carries what data-seeding cannot know: the extraction CURSOR\n * (surviving restarts, so populations larger than one run's budget eventually sync end to end)\n * and the ETL-side last-run summary (extracted/rejected happen BEFORE the seed engine sees\n * anything). Transform rejections are logged per row (bounded) and counted on the summary; the\n * rejected rows' previous destination versions simply survive.\n *\n * ## Attribution (PR-10)\n *\n * ONE read-through audit row per run through the same lazily-resolved seam as the virtual\n * repository (`logHttpApiReadThroughAccess`), carrying the run outcome counts — a pipeline run\n * is bulk read-through access to an external system and lands in the same audit lane.\n *\n * ## Concurrency\n *\n * Single-flight per pipeline is IN-PROCESS (a second `runPipeline` for the same resource while\n * one is in flight answers `already-running`). A cross-instance double run is deliberately not\n * locked out: every load is an idempotent business-key MERGE and extraction pages are\n * drift-safe, so the second run wastes work but corrupts nothing — the same posture as\n * concurrent seed passes.\n */\n\nimport { inject, injectable, optional, unmanaged } from 'inversify';\nimport {\n CoreResourceOperation,\n ErrorCustomMessageReference,\n ErrorType,\n type ResourceType,\n} from '@wildo-ai/saas-models';\nimport {\n ExternalDataDestinationPopulation,\n ExternalDataOrphanPolicy,\n type ExternalDataPipelineDeclaration,\n} from '@wildo-ai/saas-models/external-data';\n\nimport {\n DataSeedMode,\n DataSeedReconcileStrategy,\n DataSeedScopeKind,\n DataSeedSyncOrphanPolicy,\n DataSeedTriggerKind,\n DataSeedTriggerOrigin,\n type DataSeedDefinition,\n type DataSeedStaticEntry,\n} from '~wildo-backend-lib/data-seeding/models/data-seed.shared.schemas';\nimport { DataSeedRunStatus } from '~wildo-backend-lib/data-seeding/models/data-seed-run.shared.schemas';\nimport type { DataSeedScopeAnchor } from '~wildo-backend-lib/data-seeding/services/shared/data-seed-scope-anchor.shared.types';\nimport type { SyncSeedEngineBackendService } from '~wildo-backend-lib/data-seeding/services/engines/sync-seed-engine.backend.service';\nimport type { UpgradeSeedEngineBackendService } from '~wildo-backend-lib/data-seeding/services/engines/upgrade-seed-engine.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport type { ExecutionContextCreatorBackendService } from '~wildo-backend-lib/runtime/services/execution-context-creator.backend.service';\nimport type { RepositoriesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/repositories/repositories-registry-handler.backend.service';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport type { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport type { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { BackendResourceType } from '~wildo-backend-lib/resources/models/resources-types.backend.schemas';\nimport type { ExternalDataPipelineState } from '~wildo-backend-lib/external-data/models/external-data-pipeline-state.backend.schemas';\nimport { HttpApiReadClient } from '~wildo-backend-lib/base-classes/services/repositories/http-api/http-api-read-client.backend';\nimport { ExternalProvidersHttpApiAccessResolver } from '~wildo-backend-lib/base-classes/services/repositories/http-api/http-api-provider-access.backend';\nimport type { HttpApiAuditLogsResolver } from '~wildo-backend-lib/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service';\nimport { HttpApiExternalDataExtractionSource, type ExternalDataExtractionSource } from './external-data-extraction-source.backend';\n/*\n * entrypoint-closure-waiver: `etl-transform.backend` is published only by `./external-data-models` (one\n * door per room), and nothing nominal crosses into this root-exported module's public surface —\n * `ExternalDataTransformedRow`/`ExternalDataRowRejection` are structural interfaces and `ExternalDataPipelineRunReport`\n * names none of them nominally. Probed 2026-08-28 after the barrel split:\n * `tsc -p tsconfig.build.json --noEmit` with declaration emit — zero TS2742/TS2883/TS7056.\n */\nimport { transformExternalDataPage, type ExternalDataRowRejection } from './external-data-transform.backend';\n\n/** How many per-row rejection reasons are LOGGED per run — the count is always exact. */\nconst MAX_LOGGED_REJECTIONS = 25;\n\nexport interface ExternalDataPipelineRunInput {\n /** The DESTINATION resource type declaring the `externalDataPipeline`. */\n resourceType: string;\n /** `ADMIN_MANUAL` for the on-demand door, `SCHEDULED_RERUN` for the cron batch (PR-12). */\n triggerOrigin: DataSeedTriggerOrigin.ADMIN_MANUAL | DataSeedTriggerOrigin.SCHEDULED_RERUN;\n triggeredByUserId: string | null;\n}\n\nexport interface ExternalDataPipelineRunReport {\n outcome: 'completed-pass' | 'partial-pass' | 'already-running';\n extractedRows: number;\n rejectedRows: number;\n /**\n * Declared entries HANDED TO the seed engine (extracted − rejected) — what was submitted, never\n * what was written. Named `submittedEntries` rather than `loadedEntries` because the old name\n * read as a write count and was believed as one: a run reporting `loadedEntries: 2` had in fact\n * written one row and failed the other. `loadStatus` is the field that answers what landed.\n */\n submittedEntries: number;\n /**\n * The seed engine's verdict on the LOAD — `PARTIAL`/`FAILED` means go read `seedRunId`. Distinct\n * from `outcome`, which reports only whether the EXTRACTION pass completed.\n */\n loadStatus: DataSeedRunStatus | null;\n /** `_id` of the `DataSeedRun` ledger row (per-entry outcome detail); null when none ran. */\n seedRunId: string | null;\n /** The persisted continuation — non-null exactly for `partial-pass`. */\n cursor: string | null;\n durationMs: number;\n}\n\n/** Test seam: build the extraction source for one run (production default: the HTTP source). */\nexport type ExternalDataExtractionSourceFactory = (\n declaration: ExternalDataPipelineDeclaration,\n internalContext: ExecutionContext<any>,\n) => ExternalDataExtractionSource;\n\n@injectable()\nexport class ExternalDataPipelineRunBackendService {\n private readonly inFlight = new Set<string>();\n private readonly logDebug: (message: string, data?: unknown) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.RepositoriesRegistryHandlerService) private readonly repositoriesRegistry: RepositoriesRegistryHandlerBackendService,\n @inject(SAAS_SERVICE_TYPES.ResourceRegistryService) private readonly resourcesRegistry: ResourcesRegistryBackendService,\n @inject(SAAS_SERVICE_TYPES.SyncSeedEngine) private readonly syncEngine: SyncSeedEngineBackendService,\n @inject(SAAS_SERVICE_TYPES.UpgradeSeedEngine) private readonly upgradeEngine: UpgradeSeedEngineBackendService,\n @inject(SAAS_SERVICE_TYPES.ExecutionContextCreatorBackendService) private readonly executionContextCreator: ExecutionContextCreatorBackendService,\n /**\n * Resolves provider credentials for the extraction client. `@optional()` so a container\n * without external-connector bindings still constructs the service — a run then fails with\n * the resolver's own actionable error, never at boot.\n */\n @inject(SAAS_SERVICE_TYPES.ExternalProvidersRegistryService) @optional() private readonly externalProvidersRegistry?: unknown,\n /**\n * The read-through audit seam (PR-10) — same structural, lazily-resolved contract as the\n * virtual repository's: absent binding means no emission, and emission failure never fails\n * the run it observes.\n */\n /*\n * `@unmanaged()`, because this class IS container-constructed and the two seams below are not the\n * container's to resolve. The contract was copied from `HttpApiResourceRepositoryBackendService`,\n * where an undecorated optional parameter is fine — that class carries no `@injectable()` and is\n * built with `new`. Under `@injectable()` the same shape is a boot failure: Inversify validates\n * EVERY constructor parameter, so an undecorated one aborts container setup with\n * \"unexpected missing metadata\" long before any ETL code runs.\n */\n @unmanaged() private readonly auditLogsResolver?: HttpApiAuditLogsResolver,\n /** Test seam — production leaves it unset and gets the HTTP source over the shared client. */\n @unmanaged() private readonly extractionSourceFactory?: ExternalDataExtractionSourceFactory,\n ) {\n this.logDebug = this.logger.debugByType('ExternalDataPipelineRun');\n this.logger.setDebugTypeLogging('ExternalDataPipelineRun', false);\n }\n\n /** The on-demand door (PR-12). The scheduled batch calls this per due pipeline. */\n public async runPipeline(input: ExternalDataPipelineRunInput): Promise<ExternalDataPipelineRunReport> {\n if (this.inFlight.has(input.resourceType)) {\n return {\n outcome: 'already-running', extractedRows: 0, rejectedRows: 0,\n submittedEntries: 0, loadStatus: null, seedRunId: null, cursor: null, durationMs: 0,\n };\n }\n this.inFlight.add(input.resourceType);\n try {\n return await this.executeRun(input);\n } finally {\n this.inFlight.delete(input.resourceType);\n }\n }\n\n /** Every registered resource that declares an `externalDataPipeline` — the scheduled batch's population. */\n public listDeclaredPipelines(): Array<{ resourceType: string; declaration: ExternalDataPipelineDeclaration }> {\n return (this.resourcesRegistry.getResourceTypes() as string[])\n .flatMap((resourceType) => {\n const configuration = this.resourcesRegistry.getResourceConfiguration(resourceType as ResourceType) as\n | { externalDataPipeline?: ExternalDataPipelineDeclaration }\n | undefined;\n return configuration?.externalDataPipeline ? [{ resourceType, declaration: configuration.externalDataPipeline }] : [];\n });\n }\n\n private async executeRun(input: ExternalDataPipelineRunInput): Promise<ExternalDataPipelineRunReport> {\n const startedAtMs = Date.now();\n const { resourceType, triggerOrigin, triggeredByUserId } = input;\n\n const configuration = this.resourcesRegistry.getResourceConfiguration(resourceType as ResourceType) as {\n externalDataPipeline?: ExternalDataPipelineDeclaration;\n };\n const declaration = configuration?.externalDataPipeline;\n if (!declaration) {\n throw this.errorBuilder.buildError(ErrorType.CONFIGURATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.RESOURCES_REGISTRY_INITIALIZATION_FAILED,\n context: { reason: 'etl_pipeline_without_declaration', resourceType },\n });\n }\n\n const internalContext = await this.executionContextCreator.createForInternalSystemOperation();\n const stateContext = await this.executionContextCreator.createForInternalRepositoryOperation({\n resourceType: BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE,\n operationIdentifier: CoreResourceOperation.CREATE,\n });\n const stateRepo = this.repositoriesRegistry.getRepositoryOrThrow(\n BackendResourceType.EXTERNAL_DATA_PIPELINE_STATE as unknown as ResourceType,\n stateContext,\n );\n\n let state = await stateRepo.read<ExternalDataPipelineState>(stateContext, { pipelineResourceType: resourceType });\n if (!state) {\n state = (await stateRepo.create(stateContext, {\n pipelineResourceType: resourceType,\n cursor: null,\n passStartedAt: null,\n lastRunAt: null,\n lastRunSummary: null,\n } as never)) as unknown as ExternalDataPipelineState;\n }\n const resumingCursor = state.cursor ?? undefined;\n const passStartedAt = resumingCursor === undefined ? new Date() : (state.passStartedAt ?? new Date());\n\n // ── E: bounded page walk from the persisted cursor ──────────────────────────\n const source = (this.extractionSourceFactory ?? this.buildHttpExtractionSource.bind(this))(declaration, internalContext);\n // PR-21 — WARN-only drift probe, once per run, before extraction. A probe failure is\n // swallowed: drift detection must never be the reason a run fails.\n try {\n for (const warning of (await source.describeContractDrift?.()) ?? []) {\n this.logger.warn(`ExternalDataPipelineRun[${resourceType}]: schema drift — ${warning}`);\n }\n } catch {\n // Diagnostic only.\n }\n const extractedRows: Array<Record<string, unknown>> = [];\n let cursor = resumingCursor;\n let passComplete = false;\n while (extractedRows.length < declaration.maxRowsPerRun) {\n const page = await source.extractPage(cursor, Math.min(declaration.maxRowsPerRun - extractedRows.length, 1_000));\n extractedRows.push(...page.rows);\n cursor = page.nextCursor;\n if (page.complete) {\n passComplete = true;\n break;\n }\n }\n\n // ── T: pure transform; per-row rejections reported, never fatal ─────────────\n const { rows, rejections } = transformExternalDataPage(declaration, extractedRows);\n this.logRejections(resourceType, rejections);\n const entries: DataSeedStaticEntry[] = rows.map((row) => ({\n businessKey: { [declaration.remoteKeyLocalField]: row.businessKeyValue },\n data: row.data,\n }));\n\n // ── L: mode selection is THE PR-6 guard — a partial run orphans NOTHING ─────\n const seed = this.assembleSeedDefinition(resourceType, declaration, passComplete);\n const scope = await this.resolveApplicationScopeAnchor(internalContext);\n const engineInput = {\n seed,\n trigger: { triggerKind: DataSeedTriggerKind.ON_SCHEDULED_REFRESH as const },\n triggerOrigin,\n scope,\n entries,\n triggeredByUserId,\n rowMetadataSource: `external-data:${resourceType}`,\n internalContext,\n };\n const seedReport = passComplete\n ? await this.syncEngine.runSyncSeed(engineInput)\n : await this.upgradeEngine.runUpgradeSeed(engineInput);\n\n // ── Persist the state row: the cursor IS the pass continuation ──────────────\n const durationMs = Date.now() - startedAtMs;\n const seedRunId = seedReport.runId ?? null;\n // The LOAD verdict, carried separately from `outcome` (which is about the EXTRACTION pass).\n // Both are reported: a full pass that failed to write part of what it read is neither a clean\n // success nor a partial pass, and collapsing them would hide exactly that case.\n const loadStatus = seedReport.status ?? null;\n await stateRepo.update(stateContext, state._id, {\n cursor: passComplete ? null : (cursor ?? null),\n passStartedAt: passComplete ? null : passStartedAt,\n lastRunAt: new Date(startedAtMs),\n lastRunSummary: {\n completedPass: passComplete,\n extractedRows: extractedRows.length,\n rejectedRows: rejections.length,\n loadStatus,\n seedRunId,\n durationMs,\n },\n } as never);\n\n this.recordRunReadThroughAccess(internalContext, resourceType, declaration, {\n extractedRows: extractedRows.length,\n rejectedRows: rejections.length,\n completedPass: passComplete,\n });\n\n return {\n outcome: passComplete ? 'completed-pass' : 'partial-pass',\n extractedRows: extractedRows.length,\n rejectedRows: rejections.length,\n submittedEntries: entries.length,\n loadStatus,\n seedRunId,\n cursor: passComplete ? null : (cursor ?? null),\n durationMs,\n };\n }\n\n /**\n * The assembled seed is EPHEMERAL (never registered): `version` is pinned at 1 forever and\n * `reconcileStrategy: CONTENT` carries change detection (PR-30) — bumping the version per run\n * would route every row through the migrator path and rewrite the whole corpus each pass.\n */\n private assembleSeedDefinition(\n resourceType: string,\n declaration: ExternalDataPipelineDeclaration,\n passComplete: boolean,\n ): DataSeedDefinition {\n const managedFields = [\n declaration.remoteKeyLocalField,\n ...declaration.mapping.map((entry) => entry.localField),\n ];\n return {\n seedKey: `external-data:${resourceType}`,\n scope: { kind: DataSeedScopeKind.APPLICATION },\n resourceType,\n triggers: [{ triggerKind: DataSeedTriggerKind.ON_SCHEDULED_REFRESH }],\n mode: passComplete ? DataSeedMode.SYNC : DataSeedMode.UPGRADE,\n version: 1,\n managedFields,\n reconcileStrategy: DataSeedReconcileStrategy.CONTENT,\n ...(passComplete ? { onOrphan: this.mapOrphanPolicy(this.effectiveOrphanPolicy(declaration)) } : {}),\n } as DataSeedDefinition;\n }\n\n /**\n * The seam between the two closed vocabularies (never merged by value overlap — the ETL\n * declaration must not depend on data-seeding's enum, and each member's semantics are owned\n * where it is declared).\n */\n private mapOrphanPolicy(orphanPolicy: ExternalDataOrphanPolicy): DataSeedSyncOrphanPolicy {\n switch (orphanPolicy) {\n case ExternalDataOrphanPolicy.DELETE:\n return DataSeedSyncOrphanPolicy.DELETE;\n case ExternalDataOrphanPolicy.DEPRECATE:\n return DataSeedSyncOrphanPolicy.DEPRECATE;\n case ExternalDataOrphanPolicy.REPORT:\n return DataSeedSyncOrphanPolicy.REPORT;\n }\n }\n\n /**\n * ⚠ OPEN-population caveat, named where it is decided: the SYNC orphan walk considers every\n * row of the destination, including caller-minted rows on an OPEN population. Those rows have\n * no `__seed` metadata and a business key outside the declared set, so DELETE/DEPRECATE would\n * hit them. Slice 1 therefore downgrades OPEN populations' orphan handling to REPORT.\n */\n private effectiveOrphanPolicy(declaration: ExternalDataPipelineDeclaration): ExternalDataOrphanPolicy {\n return declaration.population === ExternalDataDestinationPopulation.OPEN ? ExternalDataOrphanPolicy.REPORT : declaration.orphanPolicy;\n }\n\n private buildHttpExtractionSource(\n declaration: ExternalDataPipelineDeclaration,\n internalContext: ExecutionContext<any>,\n ): ExternalDataExtractionSource {\n const readClient = new HttpApiReadClient(\n declaration.binding,\n new ExternalProvidersHttpApiAccessResolver(this.externalProvidersRegistry as never),\n );\n return new HttpApiExternalDataExtractionSource(declaration, readClient, internalContext);\n }\n\n private async resolveApplicationScopeAnchor(internalContext: ExecutionContext<any>): Promise<DataSeedScopeAnchor> {\n const appRepo = this.repositoriesRegistry.getRepositoryOrThrow('applications' as ResourceType, internalContext);\n const application = (await appRepo.read<{ _id: string }>(internalContext, {})) ?? null;\n if (!application) {\n throw this.errorBuilder.buildError(ErrorType.INTERNAL, internalContext, {\n customMessageReference: ErrorCustomMessageReference.INTERNAL,\n context: { message: 'ExternalDataPipelineRun: no APPLICATION row exists — bootstrap order violation.' },\n });\n }\n return { scopeKind: DataSeedScopeKind.APPLICATION, relatedId: application._id, relatedResourceType: null };\n }\n\n private logRejections(resourceType: string, rejections: ExternalDataRowRejection[]): void {\n if (rejections.length === 0) return;\n this.logger.warn(`ExternalDataPipelineRun[${resourceType}]: ${rejections.length} row(s) rejected by transform (previous versions survive).`, {\n rejections: rejections.slice(0, MAX_LOGGED_REJECTIONS),\n truncated: rejections.length > MAX_LOGGED_REJECTIONS,\n });\n }\n\n /** PR-10 — one audit row per run, fire-and-forget through the shared seam. */\n private recordRunReadThroughAccess(\n executionContext: ExecutionContext<any>,\n resourceType: string,\n declaration: ExternalDataPipelineDeclaration,\n counts: { extractedRows: number; rejectedRows: number; completedPass: boolean },\n ): void {\n try {\n const auditLogsService = this.auditLogsResolver?.();\n if (typeof auditLogsService?.logHttpApiReadThroughAccess !== 'function') return;\n void auditLogsService.logHttpApiReadThroughAccess({\n resourceType,\n operationIdentifier: 'externalDataPipelineRun',\n providerRef: declaration.binding.providerRef,\n remoteEntityRef: declaration.binding.entityRef,\n dialect: declaration.binding.dialect,\n rowCount: counts.extractedRows,\n rejectedRows: counts.rejectedRows,\n completedPass: counts.completedPass,\n }, executionContext)?.catch?.(() => undefined);\n } catch {\n // Observation only — never the reason a run fails.\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"external-data-transform.backend.d.ts","sourceRoot":"","sources":["../../../../src/external-data/external-data-transform.backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,EAGL,KAAK,+BAA+B,EACrC,MAAM,qCAAqC,CAAC;AAE7C,2FAA2F;AAC3F,MAAM,WAAW,0BAA0B;IACzC,wFAAwF;IACxF,gBAAgB,EAAE,MAAM,CAAC;IACzB,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,wEAAwE;AACxE,MAAM,WAAW,wBAAwB;IACvC,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,0BAA0B,EAAE,CAAC;IACnC,UAAU,EAAE,wBAAwB,EAAE,CAAC;CACxC;AAED,wEAAwE;AACxE,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,+BAA+B,EAC5C,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACjD,2BAA2B,CAqC7B"}
1
+ {"version":3,"file":"external-data-transform.backend.d.ts","sourceRoot":"","sources":["../../../../src/external-data/external-data-transform.backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,EAGL,KAAK,+BAA+B,EACrC,MAAM,qCAAqC,CAAC;AAE7C,2FAA2F;AAC3F,MAAM,WAAW,0BAA0B;IACzC,wFAAwF;IACxF,gBAAgB,EAAE,MAAM,CAAC;IACzB,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,wEAAwE;AACxE,MAAM,WAAW,wBAAwB;IACvC,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,0BAA0B,EAAE,CAAC;IACnC,UAAU,EAAE,wBAAwB,EAAE,CAAC;CACxC;AAED,wEAAwE;AACxE,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,+BAA+B,EAC5C,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACjD,2BAA2B,CA2C7B"}
@@ -30,7 +30,13 @@ export function transformExternalDataPage(declaration, remoteRows) {
30
30
  try {
31
31
  const keyComponents = declaration.binding.keyFields.map((keyField) => remoteRow[keyField.remoteField]);
32
32
  businessKeyValue = synthesizeHttpApiResourceId(declaration.binding, keyComponents);
33
- const data = { [declaration.remoteKeyLocalField]: businessKeyValue };
33
+ // The remote-key field is IDENTITY, not a value, and is deliberately ABSENT from `data`.
34
+ // It travels as `businessKeyValue`, and the load supplies it through the repository's
35
+ // `$setOnInsert`. Putting it in the payload too made MongoDB refuse the very first insert of
36
+ // every pipeline — `$set` and `$setOnInsert` may not touch one path
37
+ // (`ConflictingUpdateOperators`). Caught only by the real-datastore load proof: the conflict
38
+ // is a property of the driver's update document, not of our shapes, so no mock can raise it.
39
+ const data = {};
34
40
  for (const entry of declaration.mapping) {
35
41
  if (entry.kind === ExternalDataMappingEntryKind.CONSTANT) {
36
42
  data[entry.localField] = entry.constant;
@@ -1 +1 @@
1
- {"version":3,"file":"external-data-transform.backend.js","sourceRoot":"","sources":["../../../../src/external-data/external-data-transform.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ETL TRANSFORM step (partial-resource paper v3.1 §3, PR-8 / D-P4) — the T of the pipeline.\n *\n * Pure and schema-blind by design: every schema-dependent decision was already made at AUTHORING\n * time by the resource factory (mapped targets exist and are remote-authoritative; absence-capable\n * targets verified; the Odoo `false`→null default RESOLVED into the declaration with the boolean\n * exemption). What runs here is only the resolved declaration against raw remote rows:\n *\n * - **row identity**: the destination business-key value IS `synthesizeHttpApiResourceId`\n * output — byte-identical to what a VIRTUAL sibling of the same binding would carry as `_id`\n * (PR-29's shared-identity half), so a later promotion from pipeline to virtual (or a\n * cross-reference between the two) never re-keys anything;\n * - **per-row rejection, never per-run failure**: a row the binding cannot identify, a required\n * remote field absent, or a coercion fed a malformed value REJECTS that one row with a\n * reported reason (run ledger, PR-9) — the destination's previous version of the row simply\n * survives untouched, and every other row in the page still lands;\n * - **declared coercions only**: `ODOO_FALSE_AS_NULL` (Odoo's absent-sentinel), and\n * `ISO_STRING_TO_DATE`. An ABSENT remote value on a non-required mapping lands as explicit\n * `null` — the managed-field overlay treats `undefined` as \"not managed\", so absence must be\n * spelled `null` to actually CLEAR a previously-synced value (A1 strict ownership).\n */\n\nimport {\n synthesizeHttpApiResourceId,\n} from '@wildo-ai/saas-models';\nimport {\n ExternalDataFieldCoercion,\n ExternalDataMappingEntryKind,\n type ExternalDataPipelineDeclaration,\n} from '@wildo-ai/saas-models/external-data';\n\n/** One transformed destination row: the MERGE key and the managed-field values to load. */\nexport interface ExternalDataTransformedRow {\n /** The synthesized remote id — the value of the destination's `remoteKeyLocalField`. */\n businessKeyValue: string;\n /** `remoteKeyLocalField` + every mapped local field (absent remote values as explicit null). */\n data: Record<string, unknown>;\n}\n\n/** One rejected remote row, attributed as far as identification got. */\nexport interface ExternalDataRowRejection {\n /** Index within the extracted page (stable attribution even when the row has no usable key). */\n rowIndex: number;\n /** The synthesized remote id, when identification succeeded before the rejection. */\n businessKeyValue?: string;\n reason: string;\n}\n\nexport interface ExternalDataTransformResult {\n rows: ExternalDataTransformedRow[];\n rejections: ExternalDataRowRejection[];\n}\n\n/** Transform one extracted page. Pure — no I/O, no schema, no clock. */\nexport function transformExternalDataPage(\n declaration: ExternalDataPipelineDeclaration,\n remoteRows: ReadonlyArray<Record<string, unknown>>,\n): ExternalDataTransformResult {\n const rows: ExternalDataTransformedRow[] = [];\n const rejections: ExternalDataRowRejection[] = [];\n\n remoteRows.forEach((remoteRow, rowIndex) => {\n let businessKeyValue: string | undefined;\n try {\n const keyComponents = declaration.binding.keyFields.map((keyField) => remoteRow[keyField.remoteField]);\n businessKeyValue = synthesizeHttpApiResourceId(declaration.binding, keyComponents);\n\n const data: Record<string, unknown> = { [declaration.remoteKeyLocalField]: businessKeyValue };\n for (const entry of declaration.mapping) {\n if (entry.kind === ExternalDataMappingEntryKind.CONSTANT) {\n data[entry.localField] = entry.constant;\n continue;\n }\n const remoteValue = remoteRow[entry.remoteField];\n if (remoteValue === undefined) {\n if (entry.required) {\n throw new Error(`required remote field '${entry.remoteField}' is absent on the row.`);\n }\n data[entry.localField] = null;\n continue;\n }\n data[entry.localField] = applyCoercion(entry.remoteField, entry.coercion, remoteValue);\n }\n rows.push({ businessKeyValue, data });\n } catch (error) {\n rejections.push({\n rowIndex,\n ...(businessKeyValue === undefined ? {} : { businessKeyValue }),\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n });\n\n return { rows, rejections };\n}\n\nfunction applyCoercion(remoteField: string, coercion: ExternalDataFieldCoercion, value: unknown): unknown {\n switch (coercion) {\n case ExternalDataFieldCoercion.NONE:\n return value;\n case ExternalDataFieldCoercion.ODOO_FALSE_AS_NULL:\n // Odoo spells \"no value\" as literal `false` on non-boolean fields. The factory exempted\n // genuine boolean targets from this coercion, so `false` here is always the sentinel.\n return value === false ? null : value;\n case ExternalDataFieldCoercion.ISO_STRING_TO_DATE: {\n if (value === null) return null;\n if (typeof value !== 'string') {\n throw new Error(`remote field '${remoteField}': ISO_STRING_TO_DATE expects a string, received ${typeof value}.`);\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n throw new Error(`remote field '${remoteField}': '${value}' is not a parseable timestamp.`);\n }\n return parsed;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"external-data-transform.backend.js","sourceRoot":"","sources":["../../../../src/external-data/external-data-transform.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ETL TRANSFORM step (partial-resource paper v3.1 §3, PR-8 / D-P4) — the T of the pipeline.\n *\n * Pure and schema-blind by design: every schema-dependent decision was already made at AUTHORING\n * time by the resource factory (mapped targets exist and are remote-authoritative; absence-capable\n * targets verified; the Odoo `false`→null default RESOLVED into the declaration with the boolean\n * exemption). What runs here is only the resolved declaration against raw remote rows:\n *\n * - **row identity**: the destination business-key value IS `synthesizeHttpApiResourceId`\n * output — byte-identical to what a VIRTUAL sibling of the same binding would carry as `_id`\n * (PR-29's shared-identity half), so a later promotion from pipeline to virtual (or a\n * cross-reference between the two) never re-keys anything;\n * - **per-row rejection, never per-run failure**: a row the binding cannot identify, a required\n * remote field absent, or a coercion fed a malformed value REJECTS that one row with a\n * reported reason (run ledger, PR-9) — the destination's previous version of the row simply\n * survives untouched, and every other row in the page still lands;\n * - **declared coercions only**: `ODOO_FALSE_AS_NULL` (Odoo's absent-sentinel), and\n * `ISO_STRING_TO_DATE`. An ABSENT remote value on a non-required mapping lands as explicit\n * `null` — the managed-field overlay treats `undefined` as \"not managed\", so absence must be\n * spelled `null` to actually CLEAR a previously-synced value (A1 strict ownership).\n */\n\nimport {\n synthesizeHttpApiResourceId,\n} from '@wildo-ai/saas-models';\nimport {\n ExternalDataFieldCoercion,\n ExternalDataMappingEntryKind,\n type ExternalDataPipelineDeclaration,\n} from '@wildo-ai/saas-models/external-data';\n\n/** One transformed destination row: the MERGE key and the managed-field values to load. */\nexport interface ExternalDataTransformedRow {\n /** The synthesized remote id — the value of the destination's `remoteKeyLocalField`. */\n businessKeyValue: string;\n /** `remoteKeyLocalField` + every mapped local field (absent remote values as explicit null). */\n data: Record<string, unknown>;\n}\n\n/** One rejected remote row, attributed as far as identification got. */\nexport interface ExternalDataRowRejection {\n /** Index within the extracted page (stable attribution even when the row has no usable key). */\n rowIndex: number;\n /** The synthesized remote id, when identification succeeded before the rejection. */\n businessKeyValue?: string;\n reason: string;\n}\n\nexport interface ExternalDataTransformResult {\n rows: ExternalDataTransformedRow[];\n rejections: ExternalDataRowRejection[];\n}\n\n/** Transform one extracted page. Pure — no I/O, no schema, no clock. */\nexport function transformExternalDataPage(\n declaration: ExternalDataPipelineDeclaration,\n remoteRows: ReadonlyArray<Record<string, unknown>>,\n): ExternalDataTransformResult {\n const rows: ExternalDataTransformedRow[] = [];\n const rejections: ExternalDataRowRejection[] = [];\n\n remoteRows.forEach((remoteRow, rowIndex) => {\n let businessKeyValue: string | undefined;\n try {\n const keyComponents = declaration.binding.keyFields.map((keyField) => remoteRow[keyField.remoteField]);\n businessKeyValue = synthesizeHttpApiResourceId(declaration.binding, keyComponents);\n\n // The remote-key field is IDENTITY, not a value, and is deliberately ABSENT from `data`.\n // It travels as `businessKeyValue`, and the load supplies it through the repository's\n // `$setOnInsert`. Putting it in the payload too made MongoDB refuse the very first insert of\n // every pipeline — `$set` and `$setOnInsert` may not touch one path\n // (`ConflictingUpdateOperators`). Caught only by the real-datastore load proof: the conflict\n // is a property of the driver's update document, not of our shapes, so no mock can raise it.\n const data: Record<string, unknown> = {};\n for (const entry of declaration.mapping) {\n if (entry.kind === ExternalDataMappingEntryKind.CONSTANT) {\n data[entry.localField] = entry.constant;\n continue;\n }\n const remoteValue = remoteRow[entry.remoteField];\n if (remoteValue === undefined) {\n if (entry.required) {\n throw new Error(`required remote field '${entry.remoteField}' is absent on the row.`);\n }\n data[entry.localField] = null;\n continue;\n }\n data[entry.localField] = applyCoercion(entry.remoteField, entry.coercion, remoteValue);\n }\n rows.push({ businessKeyValue, data });\n } catch (error) {\n rejections.push({\n rowIndex,\n ...(businessKeyValue === undefined ? {} : { businessKeyValue }),\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n });\n\n return { rows, rejections };\n}\n\nfunction applyCoercion(remoteField: string, coercion: ExternalDataFieldCoercion, value: unknown): unknown {\n switch (coercion) {\n case ExternalDataFieldCoercion.NONE:\n return value;\n case ExternalDataFieldCoercion.ODOO_FALSE_AS_NULL:\n // Odoo spells \"no value\" as literal `false` on non-boolean fields. The factory exempted\n // genuine boolean targets from this coercion, so `false` here is always the sentinel.\n return value === false ? null : value;\n case ExternalDataFieldCoercion.ISO_STRING_TO_DATE: {\n if (value === null) return null;\n if (typeof value !== 'string') {\n throw new Error(`remote field '${remoteField}': ISO_STRING_TO_DATE expects a string, received ${typeof value}.`);\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n throw new Error(`remote field '${remoteField}': '${value}' is not a parseable timestamp.`);\n }\n return parsed;\n }\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { CoreResourceOperation, type ResourceConfiguration, type ResourceRelationship } from '@wildo-ai/saas-models';
3
+ import { DataSeedRunStatus } from '../../data-seeding/models/data-seed-run.shared.schemas';
3
4
  /**
4
5
  * ============================================================================
5
6
  * ExternalDataPipelineState — system resource (partial-resource coverage v3, PR-9)
@@ -34,6 +35,7 @@ export declare const ExternalDataPipelineStateSchema: z.ZodObject<{
34
35
  completedPass: z.ZodBoolean;
35
36
  extractedRows: z.ZodNumber;
36
37
  rejectedRows: z.ZodNumber;
38
+ loadStatus: z.ZodNullable<z.ZodEnum<typeof DataSeedRunStatus>>;
37
39
  seedRunId: z.ZodNullable<z.ZodString>;
38
40
  durationMs: z.ZodNumber;
39
41
  }, z.core.$strip>>;
@@ -1 +1 @@
1
- {"version":3,"file":"external-data-pipeline-state.backend.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/external-data/models/external-data-pipeline-state.backend.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEL,qBAAqB,EAIrB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAC;AAe/B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;iBAoC1C,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,oBAAY,mCAAmC;IAC7C,QAAQ,aAAa;CACtB;AAED,KAAK,wCAAwC,GACzC,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,CAAC;AAWjC,eAAO,MAAM,oEAAoE,GAC/E,wBAAwB,oBAAoB,EAAE,KAC7C,qBAAqB,CAAC,SAAS,EAAE,wCAAwC,EAAE,OAAO,+BAA+B,CA0BnH,CAAC"}
1
+ {"version":3,"file":"external-data-pipeline-state.backend.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/external-data/models/external-data-pipeline-state.backend.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEL,qBAAqB,EAIrB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAC;AAU/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wDAAwD,CAAC;AAM3F;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;iBAgD1C,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,oBAAY,mCAAmC;IAC7C,QAAQ,aAAa;CACtB;AAED,KAAK,wCAAwC,GACzC,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,CAAC;AAWjC,eAAO,MAAM,oEAAoE,GAC/E,wBAAwB,oBAAoB,EAAE,KAC7C,qBAAqB,CAAC,SAAS,EAAE,wCAAwC,EAAE,OAAO,+BAA+B,CA0BnH,CAAC"}
@@ -8,6 +8,7 @@ import { CORE_APP_ROLES, CoreResourceOperation, createResourceConfiguration_Init
8
8
  * `ExternalDataPipelineState` is a structural z.infer. Probed 2026-08-28 per the rule:
9
9
  * `tsc -p tsconfig.build.json --noEmit` with declaration emit — zero TS2742/TS2883/TS7056.
10
10
  */
11
+ import { DataSeedRunStatus } from '../../data-seeding/models/data-seed-run.shared.schemas.js';
11
12
  import { BackendResourceFieldIdentifier, BackendResourceType, } from '../../resources/models/resources-types.backend.schemas.js';
12
13
  /**
13
14
  * ============================================================================
@@ -60,6 +61,18 @@ export const ExternalDataPipelineStateSchema = z.object({
60
61
  extractedRows: z.number().int().nonnegative(),
61
62
  /** Rows the transform rejected (per-row reasons are logged; previous versions survive). */
62
63
  rejectedRows: z.number().int().nonnegative(),
64
+ /**
65
+ * The seed engine's own verdict on the LOAD — a different axis from `completedPass`, which is
66
+ * about the EXTRACTION pass, and the two must never be merged: a run can complete a full pass
67
+ * over the remote population and still fail to write some of it.
68
+ *
69
+ * Recorded because omitting it made the admin run response answer `completed-pass` while the
70
+ * ledger said `partial` with a failed entry — a caller reading only the response would have
71
+ * believed the sync succeeded. One scalar is enough here: `PARTIAL` or `FAILED` is the signal
72
+ * to go read `seedRunId`, which owns the per-entry detail this summary deliberately does not
73
+ * duplicate.
74
+ */
75
+ loadStatus: z.enum(DataSeedRunStatus).nullable(),
63
76
  /** `_id` of the `DataSeedRun` ledger row carrying the per-entry load outcome. */
64
77
  seedRunId: z.string().nullable(),
65
78
  durationMs: z.number().int().nonnegative(),